Ciao, Habr!
Nel nostro lavoro, la nostra azienda si confronta molto spesso con diversi strumenti di analisi statica del codice (SAST). Di default, tutti funzionano mediamente. Naturalmente, tutto dipende dal progetto e dalle tecnologie utilizzate, così come da quanto bene queste tecnologie siano coperte dalle regole di analisi. A mio avviso, uno dei criteri principali nella scelta di uno strumento SAST è la possibilità di personalizzarlo in base alle specificità delle proprie applicazioni, ovvero scrivere e modificare le regole di analisi o, come vengono più comunemente chiamate, Custom Queries.

Utilizziamo più spesso Checkmarx: un analizzatore di codice molto interessante e potente. In questo articolo condividerò la mia esperienza nella scrittura delle regole di analisi per esso.
Indice
Introduzione
Per iniziare, vorrei raccomandare uno dei pochi articoli in lingua russa sulle peculiarità della scrittura delle query per Checkmarx. È stato pubblicato su Habr alla fine del 2019 con il titolo: .
In esso viene analizzato in dettaglio come scrivere le prime query nel linguaggio CxQL (Checkmarx Query Language) per una certa applicazione di prova e vengono mostrati i principi fondamentali del funzionamento delle regole di analisi.
Non ripeterò quanto descritto in esso, anche se alcune sovrapposizioni ci saranno comunque. Nel mio articolo cercherò di comporre una sorta di “raccolta di ricette”, un elenco di soluzioni a problemi specifici con cui mi sono confrontato durante il mio lavoro con Checkmarx. Molti di questi problemi mi hanno fatto riflettere a lungo. A volte mancavano dati nella documentazione, altre volte era difficile capire come fare ciò che era necessario. Spero che la mia esperienza e le notti insonni non saranno vani, e che questa “raccolta di ricette di Custom Queries” ti farà risparmiare alcune ore o un paio di neuroni. Quindi, cominciamo!
Informazioni generali sulle regole
Iniziamo esaminando alcuni concetti di base e il processo di lavoro con le regole, per una migliore comprensione di ciò che accadrà in seguito. E anche perché nella documentazione non viene detto nulla o è molto disperso nella struttura, il che non è molto pratico.
Le regole si applicano durante la scansione a seconda del preset selezionato all'avvio (un insieme di regole attive). È possibile creare un numero illimitato di preset e la loro struttura dipende dalle caratteristiche del tuo processo. Puoi raggrupparli per lingue o creare preset per ciascun progetto. Il numero di regole attive influisce sulla velocità e sull'accuratezza della scansione.
Impostazione del Preset nell'interfaccia CheckmarxLe regole vengono modificate in uno strumento speciale chiamato CxAuditor. Si tratta di un'applicazione desktop che si connette al server con Checkmarx. Questo strumento ha due modalità di funzionamento: modifica delle regole e analisi dei risultati della scansione già effettuata.
Interfaccia CxAuditLe regole in Checkmarx sono suddivise per lingue, quindi per ogni lingua esiste un proprio insieme di query. Ci sono anche alcune regole comuni che si applicano indipendentemente dalla lingua, queste sono chiamate query di base. Nella maggior parte dei casi, le query di base contengono la ricerca di informazioni utilizzate da altre regole.
Divisione delle regole per lingueLe regole sono di tipo “Executable” e “Non-Executable” (Eseguibili e Non eseguibili). Non è un termine del tutto corretto, a mio avviso, ma è così. La sostanza è che il risultato dell'esecuzione delle regole “Executable” sarà visualizzato nei risultati della scansione nell'interfaccia utente, mentre le regole “Non-Executable” servono solo per utilizzare i loro risultati in altre query (praticamente — solo una funzione).
Definizione del tipo di regola durante la creazioneÈ possibile creare nuove regole o modificare/ridistribuire quelle esistenti. Per riscrivere una regola, è necessario trovarla nell'albero, fare clic destro e nel menu a discesa selezionare l'opzione “Override”. È importante ricordare che le nuove regole non sono inizialmente incluse nei preset e non sono attive. Per iniziare a utilizzarle, è necessario attivarle nel menu “Preset Manager” dello strumento. Le regole riscritte mantengono le proprie impostazioni, cioè, se una regola era attiva, rimarrà tale e sarà applicata immediatamente.
Esempio di nuova regola nell'interfaccia del Preset ManagerDurante l'esecuzione viene costruito un “albero” delle richieste, che dipende da cosa. Vengono eseguite per prime le regole che raccolgono informazioni, seguite da quelle che le utilizzano. Il risultato dell'esecuzione viene memorizzato in cache, quindi se è possibile utilizzare i risultati di una regola esistente, è meglio farlo, in quanto ciò ridurrà il tempo di scansione.
Le regole possono essere applicate a diversi livelli:
A livello di sistema — sarà utilizzato per qualsiasi scansione di qualsiasi progetto
A livello di team (Team) — sarà applicato solo per la scansione dei progetti nel team selezionato.
A livello di progetto — sarà applicato in un progetto specifico
Determinazione del livello al quale sarà applicata la regola
“Glossario“ per principianti
Inizierò con alcune cose che mi hanno suscitato domande e mostrerò una serie di trucchi che semplificheranno notevolmente la vita.
Operazioni con le liste
- sottrazione di uno dall'altro (list2 - list1)
* intersezione delle liste (list1 * list2)
+ somma delle liste (list1 + list2)
& (AND logico) - unisce le liste per corrispondenza (list1 & list2), in modo simile all'intersezione (list1 * list2)
| (OR logico) - unisce le liste per ricerca ampia (list1 | list2)
Non funziona con le liste: ^ && || % / Tutti gli elementi trovati
Nel linguaggio analizzato è possibile ottenere un elenco di tutti gli elementi che Checkmarx ha definito (stringhe, funzioni, classi, metodi ecc.). Questo è uno spazio di oggetti al quale si può accedere tramite All. Quindi, per cercare un oggetto con un nome specifico searchMe, è possibile effettuare una ricerca, ad esempio, per nome tra tutti gli oggetti trovati:
// Такой запрос выдаст все элементы
result = All;
// Такой запрос выдаст все элементы, в имени которых присутствует “searchMe“
result = All.FindByName("searchMe");Tuttavia, se è necessario cercare in un'altra lingua, che per qualche motivo non è stata inclusa nella scansione (ad esempio groovy in un progetto Android), è possibile ampliare il nostro spazio di oggetti tramite la variabile:
result = AllMembers.All.FindByName("searchMe");Funzioni per l'analisi del flusso
Queste funzioni sono utilizzate in molte regole e ecco un piccolo promemoria su cosa significano:
// Какие данные second влияют на first.
// Другими словами - ТО (second) что влияет на МЕНЯ (first).
result = first.DataInfluencedBy(second);
// Какие данные first влияют на second.
// Другими словами - Я (first) влияю на ТО (second).
result = first.DataInfluencingOn(second);Ottenere nome/percorso del file
Ci sono diversi attributi che possono essere ottenuti dai risultati dell'esecuzione della richiesta (nome del file in cui è stata trovata l'occorrenza, riga ecc.), ma come ottenerli e usarli non è specificato nella documentazione. Quindi, per farlo, è necessario accedere alla proprietà LinePragma e all'interno di essa si troveranno gli oggetti di cui abbiamo bisogno:
// Для примера найдем все методы
CxList methods = Find_Methods();
// В методах найдем по имени метод scope
CxList scope = methods.FindByName("scope");
// Таким образом можо получить путь к файлу
string current_filename = scope.GetFirstGraph().LinePragma.FileName;
// А вот таким - строку, где нашлось срабатывание
int current_line = scope.GetFirstGraph().LinePragma.Line;
// Эти параметры можно использовать по разному
// Например получить все объекты в файле
CxList inFile = All.FindByFileName(current_filename);
// Или найти что происходит в конкретной строке
CxList inLine = inFile.FindByPosition(current_line);È importante tenere presente che FileName contiene in realtà il percorso del file, poiché abbiamo utilizzato il metodo GetFirstGraph.
Risultato dell'esecuzione
All'interno di CxQL è prevista una variabile speciale risultato, che restituisce il risultato dell'esecuzione della regola che hai scritto. È inizializzata immediatamente e puoi registrare al suo interno risultati intermedi, modificandoli e affinando li durante il processo di lavoro. Tuttavia, se all'interno della regola non ci sarà un'assegnazione per questa variabile o funzione returnil risultato dell'esecuzione sarà sempre nullo.
La seguente richiesta non ci restituirà nulla come risultato dell'esecuzione e sarà sempre vuota:
// Находим элементы foo
CxList libraries = All.FindByName("foo");Tuttavia, assegnando il risultato dell'esecuzione alla variabile magica result, vedremo cosa restituisce questa chiamata:
// Находим элементы foo
CxList libraries = All.FindByName("foo");
// Выводим, как результат выполнения правила
result = libraries
// Или еще короче
result = All.FindByName("foo");Utilizzo dei risultati di esecuzione di altre regole
Le regole in Checkmarx possono essere considerate l'equivalente delle funzioni in un linguaggio di programmazione comune. Quando scrivi una regola, puoi tranquillamente utilizzare i risultati di altre richieste. Ad esempio, non c'è bisogno di cercare ogni volta tutte le chiamate ai metodi nel codice, basta chiamare la regola giusta:
// Получаем результат выполнения другого правила
CxList methods = Find_Methods();
// Ищем внутри метод foo.
// Второй параметр false означает, что ищем без чувствительности к регистру
result = methods.FindByShortName("foo", false);Questo approccio consente di ridurre il codice e di diminuire notevolmente il tempo di esecuzione della regola.
Risoluzione dei problemi
Registrazione
Quando si utilizza lo strumento, a volte non si riesce a scrivere subito la richiesta necessaria e si deve sperimentare, provando diverse varianti. Per questo caso, lo strumento prevede il logging, che può essere attivato nel seguente modo:
// Находим что-то
CxList toLog = All.FindByShortName("log");
// Формируем строку и отправляем в лог
cxLog.WriteDebugMessage (“number of DOM elements =” + All.Count);Ma è importante ricordare che questo metodo accetta solo stringhe, quindi non sarà possibile visualizzare l'elenco completo degli elementi trovati a seguito dell'operazione iniziale. Un secondo modo, utilizzato per il debug, è quello di assegnare di tanto in tanto alla variabile magica risultato il risultato dell'esecuzione della richiesta e vedere cosa ne esce. Questo approccio non è molto pratico, bisogna essere certi che nel codice successivo non ci siano ridefinizioni o operazioni su di essa risultato oppure basta commentare il codice sottostante. O, come me, dimenticare di rimuovere dalla regola finale alcune di queste chiamate e chiedersi perché non funziona nulla.
Un modo più pratico è chiamare il metodo return con il parametro necessario. In questo caso, l'esecuzione della regola terminerà e potremo vedere cosa abbiamo ottenuto dal nostro lavoro:
// Находим что-то
CxList toLog = All.FindByShortName("log");
// Выводим результат выполнения
return toLog
//Все, что написано дальше не будет выполнено
result = All.DataInfluencedBy(toLog)Problema di login
Ci sono situazioni in cui non riesci ad accedere allo strumento CxAudit (utilizzato per la scrittura delle regole). Le ragioni possono essere molteplici, come un arresto anomalo del lavoro, un aggiornamento improvviso di Windows, BSOD e altre situazioni impreviste che esulano dal nostro controllo. In tal caso, talvolta rimane una sessione non conclusa nel database, che impedisce un successivo accesso. Per risolvere il problema, è necessario eseguire alcune query:
Per Checkmarx fino alla 8.6:
// Проверяем, что есть залогиненые пользователи, выполнив запрос в БД
SELECT COUNT(*) FROM [CxDB].[dbo].LoggedinUser WHERE [ClientType] = 6;
// Если что-то есть, а на самом деле даже если и нет, попробовать выполнить запрос
DELETE FROM [CxDB].[dbo].LoggedinUser WHERE [ClientType] = 6;
Per Checkmarx dopo la 8.6:
// Проверяем, что есть залогиненые пользователи, выполнив запрос в БД
SELECT COUNT(*) FROM LoggedinUser WHERE (ClientType = 'Audit');
// Если что-то есть, а на самом деле даже если и нет, попробовать выполнить запрос
DELETE FROM [CxDB].[dbo].LoggedinUser WHERE (ClientType = 'Audit');Scrittura delle regole
Siamo finalmente arrivati alla parte più interessante. Quando si inizia a scrivere regole in CxQL, spesso non manca tanto la documentazione quanto esempi concreti di soluzioni a compiti specifici e la descrizione del processo di lavoro delle query nel complesso.
Cercherò di semplificare un po' la vita a coloro che stanno iniziando a immergersi nel linguaggio delle query e fornirò alcuni esempi di utilizzo delle Query Personalizzate per risolvere compiti specifici. Alcuni di essi sono piuttosto generali e possono essere applicati nella vostra azienda praticamente senza modifiche, altri sono più specifici, ma possono comunque essere utilizzati adattando il codice alle specificità delle vostre applicazioni.
Ecco quindi con quali problemi ci siamo trovati ad affrontare più spesso:
Obiettivo: Nei risultati dell'esecuzione della regola ci sono diversi Flow e uno di essi è un'imbottitura dell'altro; è necessario lasciare uno di essi.
Soluzione: In effetti, a volte Checkmarx mostra diversi Flow di movimento dei dati che possono sovrapporsi e rappresentare una versione accorciata di altri. Per tali casi esiste un metodo speciale ReduceFlow. A seconda del parametro, selezionerà il Flow più corto o il più lungo:
// Оставить только длинные Flow
result = result.ReduceFlow(CxList.ReduceFlowType.ReduceSmallFlow);
// Оставить только короткие Flow
result = result.ReduceFlow(CxList.ReduceFlowType.ReduceBigFlow);Obiettivo: Ampliare l'elenco dei dati sensibili a cui lo strumento risponde
Soluzione: In Checkmarx ci sono regole di base, i cui risultati vengono utilizzati da molte altre query. Aggiungendo alcuni di questi regole con dati specifici per la vostra applicazione, è possibile migliorare immediatamente i risultati della scansione. Ecco un esempio di regola da cui si può iniziare:
General_privacy_violation_list
Aggiungeremo alcune variabili utilizzate nella nostra applicazione per memorizzare informazioni sensibili:
// Получаем результат выполнения базового правила
result = base.General_privacy_violation_list();
// Ищем элементы, которые попадают под простые регулярные выражения. Можно дополнить характерными для вас паттернами.
CxList personalList = All.FindByShortNames(new List<string> {
"*securityToken*", "*sessionId*"}, false);
// Добавляем к конечному результату
result.Add(personalList);Obiettivo: Ampliare l'elenco delle variabili con le password
Soluzione: Ti consiglio di prestare subito attenzione alla regola fondamentale per la definizione delle password nel codice e di aggiungere ad essa un elenco dei nomi delle variabili comunemente usati nella tua azienda.
Password_privacy_violation_list
CxList allStrings = All.FindByType("String");
allStrings.Add(All.FindByType(typeof(StringLiteral)));
allStrings.Add(Find_UnknownReference());
allStrings.Add(All.FindByType(typeof (Declarator)));
allStrings.Add(All.FindByType(typeof (MemberAccess)));
allStrings.Add(All.FindByType(typeof(EnumMemberDecl)));
allStrings.Add(Find_Methods().FindByShortName("get*"));
// Completiamo l'elenco predefinito delle variabili
List pswdIncludeList = new List{"*password*", "*psw", "psw*", "pwd*", "*pwd", "*authKey*", "pass*", "cipher*", "*cipher", "pass", "adgangskode", "benutzerkennwort", "chiffre", "clave", "codewort", "contrasena", "contrasenya", "geheimcode", "geslo", "heslo", "jelszo", "kennwort", "losenord", "losung", "losungswort", "lozinka", "modpas", "motdepasse", "parol", "parola", "parole", "pasahitza", "pasfhocal", "passe", "passord", "passwort", "pasvorto", "paswoord", "salasana", "schluessel", "schluesselwort", "senha", "sifre", "wachtwoord", "wagwoord", "watchword", "zugangswort", "PAROLACHIAVE", "PAROLA CHIAVE", "PAROLECHIAVI", "PAROLE CHIAVI", "paroladordine", "verschluesselt", "sisma",
"pincode",
"pin"};
List pswdExcludeList = new List{"*pass", "*passable*", "*passage*", "*passenger*", "*passer*", "*passing*", "*passion*", "*passive*", "*passover*", "*passport*", "*passed*", "*compass*", "*bypass*", "pass-through", "passthru", "passthrough", "passbytes", "passcount", "passratio"};
CxList tempResult = allStrings.FindByShortNames(pswdIncludeList, false);
CxList toRemove = tempResult.FindByShortNames(pswdExcludeList, false);
tempResult -= toRemove;
tempResult.Add(allStrings.FindByShortName("pass", false));
foreach (CxList r in tempResult)
{
CSharpGraph g = r.data.GetByIndex(0) as CSharpGraph;
if(g != null && g.ShortName != null && g.ShortName.Length < 50)
{
result.Add(r);
}
}Obiettivo: Aggiungere i framework utilizzati che non sono supportati da Checkmarx
Soluzione: Tutte le richieste in Checkmarx sono suddivise per linguaggi, quindi è necessario completare le regole per ciascun linguaggio. Di seguito alcuni esempi di tali regole.
Se si utilizzano librerie che completano o sostituiscono le funzionalità standard, è facile aggiungerle alla regola di base. In tal modo, tutti coloro che la utilizzano apprenderanno subito delle nuove informazioni. Ad esempio, librerie per la registrazione in Android — Timber e Loggi. Nella fornitura di base delle regole, non ci sono definizioni per le chiamate non di sistema, quindi se una password o un identificatore di sessione finisce nel log, non potremo saperlo. Cerchiamo di aggiungere alle regole di Checkmarx la definizione di tali metodi.
Esempio di codice di test che utilizza la libreria Timber per la registrazione:
pacchetto com.death.timberdemo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import timber.log.Timber;
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Timber.e("Messaggio di errore");
Timber.d("Messaggio di debug");
Timber.tag("Un tag diverso").e("E messaggio di errore");
}
}Ecco un esempio di richiesta per Checkmarx che consente di aggiungere la definizione delle chiamate ai metodi Timber come punto di uscita dei dati dall'applicazione:
FindAndroidOutputs
// Получаем результат выполнения базового правила
result = base.Find_Android_Outputs();
// Дополняем вызовами, которые приходят из библиотеки Timber
CxList timber = All.FindByExactMemberAccess("Timber.*") +
All.FindByShortName("Timber").GetMembersOfTarget();
// Добавляем к конечному результату
result.Add(timber);Si può anche integrare la regola adiacente, ma che riguarda specificamente il logging in Android:
FindAndroidLog_Outputs
// Получаем результат выполнения базового правила
result = base.Find_Android_Log_Outputs();
// Дополняем вызовами, которые приходят из библиотеки Timber
result.Add(
All.FindByExactMemberAccess("Timber.*") +
All.FindByShortName("Timber").GetMembersOfTarget()
);Se negli app Android si utilizza per operazioni asincrone, è opportuno informare Checkmarx di questa cosa, aggiungendo il metodo per ottenere i dati dal compito getInputData:
FindAndroidRead
// Получаем результат выполнения базового правила
result = base.Find_Android_Read();
// Дополняем вызовом функции getInputData, которая используется в WorkManager
CxList getInputData = All.FindByShortName("getInputData");
// Добавляем к конечному результату
result.Add(getInputData.GetMembersOfTarget());Obiettivo: Ricerca di dati sensibili in plist per progetti iOS
Soluzione: Spesso per memorizzare varie variabili e valori in iOS si utilizzano file speciali con estensione .plist. Non è consigliabile memorizzare password, token, chiavi e altri dati sensibili in questi file, in quanto possono essere estratti dal dispositivo senza troppe difficoltà.
I file plist hanno caratteristiche che non sono evidenti a colpo d'occhio, ma che sono importanti per Checkmarx. Scriviamo una regola che cercherà i dati di nostro interesse e ci avviserà se vengono menzionate password o token.
Ecco un esempio di file in cui è incorporato un token per comunicare con il servizio backend:
DeviceDictionary
phone
iPhone 6s
privatekey
MIICXAIBAAKBgQCqGKukO1De7zhZj6+E una regola per Checkmarx, in cui ci sono alcuni aspetti da considerare durante la scrittura:
// Используем результат выполнения правила по поиску файлов plist, чтобы уменьшить время работы правила и
CxList plist = Find_Plist_Elements();
// Инициализируем новую переменную
CxList dictionarySettings = All.NewCxList();
// Теперь добавим поиск всех интересующих нас значений. В дальнейшем можно расширять этот список.
// Для поиска значений, как ни странно, используется FindByMemberAccess - поиск обращений к методам. Второй параметр внутри функции, false, означает, что поиск нечувствителен к регистру
dictionarySettings.Add(plist.FindByMemberAccess("privatekey", false));
dictionarySettings.Add(plist.FindByMemberAccess("privatetoken", false));
// Для корректного поиска из-за особенностей структуры plist - нужно искать по типу "If statement"
CxList ifStatements = plist.FindByType(typeof(IfStmt));
// Добавляем в результат, перед этим получив родительский узел - для правильного отображения
result = dictionarySettings.FindByFathers(ifStatements);Obiettivo: Ricerca di informazioni in XML
Soluzione: In Checkmarx ci sono funzioni molto utili per lavorare con XML e per cercare valori, tag, attributi e altro. Ma nella documentazione, purtroppo, è stato fatto un errore che causa il mancato funzionamento di ogni esempio. Nonostante nella versione più recente della documentazione questo difetto sia stato corretto, è necessario prestare attenzione se si utilizzano versioni precedenti dei documenti.
Ecco un esempio errato dalla documentazione:
// Код работать не будет
result = All.FindXmlAttributesByNameAndValue("*.app", 8, “id”, "error- section", false, true);Di conseguenza, ci verrà restituito un errore che mostra che il All non esiste un metodo di questo tipo... Ed è vero, perché per utilizzare le funzioni per lavorare con XML c'è uno spazio oggetti specifico e separato — cxXPath. Ecco come apparirà la richiesta corretta per cercare l'impostazione in Android che consente l'uso del traffico HTTP:
// Правильный вариант с использованием cxXPath
result = cxXPath.FindXmlAttributesByNameAndValue("*.xml", 8, "cleartextTrafficPermitted", "true", false, true);Esaminiamo un po' più in dettaglio, poiché la sintassi è simile per tutte le funzioni; dopo aver capito una, basta solo scegliere quella giusta. Quindi, in modo sequenziale, per i parametri:
"*.xml"— il modello di file su cui è necessario eseguire la ricerca8— id della lingua a cui si applica la regola"cleartextTrafficPermitted"— nome dell'attributo in xml"true"— valore di questo attributofalse— utilizzo di un'espressione regolare durante la ricercatrue— significa che la ricerca verrà eseguita ignorando le maiuscole e le minuscole, quindi case-insensitive
Per esempio è stata utilizzata una regola che definisce le impostazioni di rete non sicure in Android, che consentono la comunicazione con il server tramite il protocollo HTTP. Esempio di impostazione che contiene l'attributo cleartextTrafficPermitted con valore true:
example.com
secure.example.comObiettivo: Limitare i risultati per nome/percorso del file
Soluzione: In uno dei grandi progetti legati allo sviluppo di un'applicazione mobile per Android, ci siamo imbattuti in falsi positivi della regola che definisce l'impostazione di offuscamento. Il motivo è che la regola di default cerca nel file build.gradle l'impostazione responsabile per l'applicazione delle regole di offuscamento per la versione di rilascio dell'applicazione.
Ma in grandi progetti a volte si trovano file secondari build.gradle, che appartengono a librerie incluse nel progetto. La particolarità è che anche se in questi file non è indicata la necessità di offuscamento, durante la compilazione verranno applicate le impostazioni del file di progetto principale.
Pertanto, l'obiettivo è filtrare le attivazioni nei file secondari che appartengono a librerie. Identificarli è possibile grazie alla presenza della stringa apply 'com.android.library'.
Esempio di codice dal file build.gradle, che definisce la necessità di offuscamento:
applica plugin: 'com.android.application'
android {
compileSdkVersion 24
buildToolsVersion "24.0.2"
defaultConfig {
...
}
buildTypes {
release {
minifyEnabled true
...
}
}
}
dependencies {
...
}Esempio di file build.gradle per la libreria inclusa nel progetto e senza una tale configurazione:
applica plugin: 'android-library'
dependencies {
compile 'com.android.support:support-v4:18.0.+'
}
android {
compileSdkVersion 14
buildToolsVersion '17.0.0'
...
}E la regola per Checkmarx:
ProGuardObfuscationNotInUse
// Поиск метода release среди всех методов в Gradle файлах
CxList releaseMethod = Find_Gradle_Method("release");
// Все объекты из файлов build.gradle
CxList gradleBuildObjects = Find_Gradle_Build_Objects();
// Поиск того, что находится внутри метода "release" среди всех объектов из файлов build.gradle
CxList methodInvokesUnderRelease = gradleBuildObjects.FindByType(typeof(MethodInvokeExpr)).GetByAncs(releaseMethod);
// Ищем внутри gradle-файлов строку "com.android.library" - это значит, что данный файл относится к библиотеке и его необходимо исключить из правила
CxList android_library = gradleBuildObjects.FindByName("com.android.library");
// Инициализация пустого массива
List<string> libraries_path = new List<string> {};
// Проходим через все найденные "дочерние" файлы
foreach(CxList library in android_library)
{
// Получаем путь к каждому файлу
string file_name_library = library.GetFirstGraph().LinePragma.FileName;
// Добавляем его в наш массив
libraries_path.Add(file_name_library);
}
// Ищем все вызовы включения обфускации в релизных настройках
CxList minifyEnabled = methodInvokesUnderRelease.FindByShortName("minifyEnabled");
// Получаем параметры этих вызовов
CxList minifyValue = gradleBuildObjects.GetParameters(minifyEnabled, 0);
// Ищем среди них включенные
CxList minifyValueTrue = minifyValue.FindByShortName("true");
// Немного магии, если не нашли стандартным способом :D
if (minifyValueTrue.Count == 0) {
minifyValue = minifyValue.FindByAbstractValue(abstractValue => abstractValue is TrueAbstractValue);
} else {
// А если всё-таки нашли, то предыдущий результат и оставляем
minifyValue = minifyValueTrue;
}
// Если не нашлось таких методов
if (minifyValue.Count == 0)
{
// Для более корректного отображения места срабатывания в файле ищем или buildTypes или android
CxList tempResult = All.NewCxList();
CxList buildTypes = Find_Gradle_Method("buildTypes");
if (buildTypes.Count > 0) {
tempResult = buildTypes;
} else {
tempResult = Find_Gradle_Method("android");
}
// Для каждого из найденных мест срабатывания проходим и определяем, дочерний или основной файлы сборки
foreach(CxList res in tempResult)
{
// Определяем, в каком файле был найден buildType или android методы
string file_name_result = res.GetFirstGraph().LinePragma.FileName;
// Если такого файла нет в нашем списке "дочерних" файлов - значит это основной файл и его можно добавить в результат
if (libraries_path.Contains(file_name_result) == false){
result.Add(res);
}
}
}Questo approccio può essere abbastanza universale e utile non solo per le applicazioni Android, ma anche per altre situazioni in cui è necessario determinare l'appartenenza del risultato a un file specifico.
Obiettivo: Aggiungi il supporto per la libreria di terze parti, se la sintassi non è completamente supportata
Soluzione: Il numero di vari framework utilizzati durante la scrittura del codice è semplicemente abnorme. Certamente, Checkmarx non conosce sempre la loro esistenza e il nostro compito è insegnargli a capire che determinati metodi appartengono proprio a questo framework. A volte questo è complicato dal fatto che i framework usano nomi di funzioni molto comuni, rendendo impossibile determinare in modo univoco il collegamento di una chiamata a una libreria specifica.
La complessità sta nel fatto che la sintassi di tali librerie non viene sempre riconosciuta correttamente e si deve sperimentare per evitare un numero elevato di falsi positivi. Ci sono diverse opzioni per migliorare la precisione della scansione e risolvere il problema posto:
La prima opzione, sappiamo con certezza che la libreria è utilizzata in un progetto specifico e possiamo applicare la regola a livello di team. Ma se il team decide di adottare un altro approccio o utilizza più librerie in cui si sovrappongono i nomi delle funzioni, possiamo ottenere un'immagine poco piacevole di numerosi falsi positivi.
La seconda opzione consiste nell'applicare la ricerca nei file in cui avviene esplicitamente l'importazione della libreria. Con questo approccio, possiamo essere certi che in questo file viene utilizzata proprio la libreria di cui abbiamo bisogno.
E la terza opzione è l'uso di due dei suddetti approcci insieme.
Come esempio, analizziamo una libreria nota in ambiti ristretti per il linguaggio di programmazione Scala, e in particolare la funzionalità . In generale, per passare i parametri in una query SQL è necessario utilizzare un operatore $, che inserisce i dati in una query SQL preformulata. Cioè, di fatto, è un diretto equivalente del Prepared Statement in Java. Tuttavia, nel caso sia necessario costruire dinamicamente una query SQL, ad esempio se si devono passare i nomi delle tabelle, è possibile utilizzare l'operatore #$, che inserirà direttamente i dati nella query (praticamente, come una concatenazione di stringhe).
Esempio di codice:
// В общем случае - значения, контролируемые пользователем
val table = "coffees"
sql"select * from #$table where name = $name".as[Coffee].headOptionCheckmarx attualmente non è in grado di rilevare l'uso di Splicing Literal Values e salta gli operatori #$, quindi proveremo a insegnargli a identificare potenziali SQL injection e ad evidenziare i punti necessari nel codice:
// Находим все импорты
CxList imports = All.FindByType(typeof(Import));
// Ищем по имени, есть ли в импортах slick
CxList slick = imports.FindByShortName("slick");
// Некоторый флаг, определяющий, что импорт библиотеки в коде присутствует
// Для более точного определения - можно применить подход с именем файла
bool not_empty_list = false;
foreach (CxList r in slick)
{
// Если встретили импорт, считаем, что slick используется
not_empty_list = true;
}
if (not_empty_list) {
// Ищем вызовы, в которые передается SQL-строка
CxList sql = All.FindByShortName("sql");
sql.Add(All.FindByShortName("sqlu"));
// Определяем данные, которые попадают в эти вызовы
CxList data_sql = All.DataInfluencingOn(sql);
// Так как синтакис не поддерживается, можно применить подход с регулярными выражениями
// RegExp стоит использовать крайне осторожно и не применять его на большом количестве данных, так как это может сильно повлиять на производительность
CxList find_possible_inj = data_sql.FindByRegex(@"#$", true, true, true);
// Избавляемся от лишних срабатываний, если они есть и выводим в результат
result = find_possible_inj.FindByType(typeof(BinaryExpr));
}Obiettivo: Ricerca delle funzioni vulnerabili utilizzate nelle librerie Open-Source
Soluzione: Molte aziende utilizzano strumenti per il monitoraggio dell'Open-Source (pratica OSA), che consentono di rilevare l'uso di versioni vulnerabili di librerie nelle applicazioni in fase di sviluppo. A volte non è possibile aggiornare tale libreria a una versione sicura. In alcuni casi ci sono limitazioni funzionali, in altri non esiste per niente una versione sicura. In tal caso può essere utile una combinazione delle pratiche SAST e OSA, che consente di determinare che le funzioni che portano all'exploitation della vulnerabilità non sono utilizzate nel codice.
Ma a volte, soprattutto se consideriamo JavaScript, questo può non essere un compito del tutto banale. Di seguito è presentata una soluzione, forse non ideale, ma comunque funzionante, utilizzando come esempio vulnerabilità nel componente lodash nei metodi template e *set.
Esempi di codice potenzialmente vulnerabile in un file JS:
/**
* Template example
*/
'use strict';
var _ = require("./node_modules/lodash.js");
// Use the "interpolate" delimiter to create a compiled template.
var compiled = _.template('hello <%= js %>!');
console.log(compiled({ 'js': 'lodash' }));
// => 'hello lodash!'
// Use the internal `print` function in "evaluate" delimiters.
var compiled = _.template('<% print("hello " + js); %>!');
console.log(compiled({ 'js': 'lodash' }));
// => 'hello lodash!'E quando collegato direttamente in html:
<!DOCTYPE html>
<html>
<head>
<title>Tutorial di Lodash</title>
<script src="./node_modules/lodash.js"></script>
<script type="text/javascript">
// Lodash chunking array
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9];
let c1 = _.template('<% print("hello " + js); %>!');
console.log(c1);
let c2 = _.template('<% print("hello " + js); %>!');
console.log(c2);
</script>
</head>
<body></body>
</html>Cerchiamo tutti i nostri metodi vulnerabili, che sono elencati nelle vulnerabilità:
// Ищем все строки: в которых встречается строка lodash (предполагаем, что это объявление импорта библиотеки
CxList lodash_strings = Find_String_Literal().FindByShortName("*lodash*");
// Ищем все данные: которые взаимодействуют с этими строками
CxList data_on_lodash = All.InfluencedBy(lodash_strings);
// Задаем список уязвимых методов
List<string> vulnerable_methods = new List<string> {"template", "*set"};
// Ищем все наши уязвимые методы, которые перечисленны в уязвимостях и отфильтровываем их только там, где они вызывались
CxList vulnerableMethods = All.FindByShortNames(vulnerable_methods).FindByType(typeof(MethodInvokeExpr));
//Находим все данные: которые взаимодействуют с данными методами
CxList vulnFlow = All.InfluencedBy(vulnerableMethods);
// Если есть пересечение по этим данным - кладем в результат
result = vulnFlow * data_on_lodash;
// Формируем список путей по которым мы уже прошли, чтобы фильтровать в дальнейшем дубли
List<string> lodash_result_path = new List<string> {};
foreach(CxList lodash_result in result)
{
// Очередной раз получаем пути к файлам
string file_name = lodash_result.GetFirstGraph().LinePragma.FileName;
lodash_result_path.Add(file_name);
}
// Дальше идет часть относящаяся к html файлам, так как в них мы не можем проследить откуда именно идет вызов
// Формируем массив путей файлов, чтобы быть уверенными, что срабатывания уязвимых методов были именно в тех файлах, в которых объявлен lodash
List<string> lodash_path = new List<string> {};
foreach(CxList string_lodash in lodash_strings)
{
string file_name = string_lodash.GetFirstGraph().LinePragma.FileName;
lodash_path.Add(file_name);
}
// Перебираем все уязвимые методы и убеждаемся, что они вызваны в тех же файлах, что и объявление/включение lodash
foreach(CxList method in vulnerableMethods)
{
string file_name_method = method.GetFirstGraph().LinePragma.FileName;
if (lodash_path.Contains(file_name_method) == true && lodash_result_path.Contains(file_name_method) == false){
result.Add(method);
}
}
// Убираем все UknownReferences и оставляем самый "длинный" из путей, если такие встречаются
result = result.ReduceFlow(CxList.ReduceFlowType.ReduceSmallFlow) - result.FindByType(typeof(UnknownReference));Obiettivo: Ricerca di certificati hard-coded nell'applicazione
Soluzione: Spesso le applicazioni, soprattutto quelle mobili, utilizzano certificati o chiavi per accedere a vari server o verificare il SSL-Pinning. Se guardiamo da una prospettiva di sicurezza, memorizzare tali elementi nel codice non è la prassi migliore. Proveremo a scrivere una regola che cercherà file di questo tipo nel repository:
// Найдем все сертификаты по маске файла
CxList find_certs = All.FindByShortNames(new List<string> {"*.der", "*.cer", "*.pem", "*.key"}, false);
// Проверим, где в приложении они используются
CxList data_used_certs = All.DataInfluencedBy(find_certs);
// И для мобильных приложений - можем поискать методы, где вызывается чтение сертификатов
// Для других платформ и приложений могут быть различные методы
CxList methods = All.FindByMemberAccess("*.getAssets");
// Пересечение множеств даст нам результат по использованию локальных сертификатов в приложении
result = methods * data_used_certs;Obiettivo: Ricerca di token compromessi nell'applicazione
Soluzione: Spesso è necessario revocare token compromessi o altre informazioni importanti presenti nel codice. Certamente, conservarli all'interno dei sorgenti non è la migliore idea, ma le situazioni possono variare. Grazie alle query CxQL, trovare tali elementi è abbastanza semplice:
// Получаем все строки, которые содержатся в коде
CxList strings = base.Find_Strings();
// Ищем среди всех строк нужное нам значение. В примере токен в виде строки "qwerty12345"
result = strings.FindByShortName("qwerty12345");Conclusione
Spero che per chi sta iniziando a conoscere lo strumento Checkmarx questo articolo possa essere utile. Anche coloro che scrivono regole da tempo potrebbero trovare qualcosa di interessante in questa guida.
Purtroppo, al momento manca una risorsa dove poter raccogliere nuove idee durante lo sviluppo delle regole per Checkmarx. Pertanto, abbiamo creato , dove condivideremo i nostri sviluppi, affinché chiunque utilizzi CxQL possa trovare qualcosa di utile e avere anche la possibilità di condividere le proprie opere con la comunità. Il repository è in fase di completamento e strutturazione dei contenuti, quindi i contributori sono i benvenuti!
Grazie per l'attenzione!
Fonte: habr.com

Impostazione del Preset nell'interfaccia Checkmarx
Interfaccia CxAudit
Divisione delle regole per lingue
Definizione del tipo di regola durante la creazione
Esempio di nuova regola nell'interfaccia del Preset Manager
Determinazione del livello al quale sarà applicata la regola