Ciao, Habr!
Nel nostro lavoro, la nostra azienda si trova spesso a utilizzare vari strumenti di analisi statica del codice (SAST). Di default, funzionano tutti in modo mediocre. Certamente, dipende dal progetto e dalle tecnologie utilizzate, nonché da quanto bene queste tecnologie siano coperte dalle regole di analisi. A mio parere, uno dei criteri più importanti nella scelta di uno strumento SAST è la possibilità di personalizzarlo in base alle specificità delle proprie applicazioni, in particolare la possibilità di scrivere e modificare le regole di analisi, o come vengono chiamate più comunemente, Custom Queries.

Noi utilizziamo più frequentemente Checkmarx, un analizzatore di codice molto interessante e potente. In questo articolo parlerò della mia esperienza nella scrittura delle regole di analisi per questo strumento.
Indice
Introduzione
Per iniziare, vorrei raccomandare uno dei pochi articoli in russo sulle peculiarità della scrittura di query per Checkmarx. È stato pubblicato su Habr alla fine del 2019 con il titolo: .
In esso si discute in dettaglio come scrivere le prime query in CxQL (Checkmarx Query Language) per una specifica applicazione di test e vengono mostrati i principi di base del funzionamento delle regole di analisi.
Non ripeterò ciò che è descritto, anche se ci saranno alcune sovrapposizioni. Nel mio articolo cercherò di comporre una sorta di “raccolta di ricette”, un elenco di soluzioni a problemi specifici con cui mi sono trovato durante il mio lavoro con Checkmarx. Per molte di queste questioni ho dovuto riflettere a lungo. A volte mancavano dati nella documentazione, altre volte era davvero difficile capire come fare ciò che era richiesto. Spero che la mia esperienza e le notti insonni non siano state vane e che questa “raccolta di ricette Custom Queries” vi faccia risparmiare qualche ora o un paio di neuroni. Quindi, cominciamo!
Informazioni generali sulle regole
Per iniziare, esaminiamo alcuni concetti di base e il processo di lavoro con le regole, per una migliore comprensione di ciò che accadrà in seguito. Inoltre, questo è importante poiché nella documentazione non è trattato adeguatamente o è sparso in modo poco pratico nella struttura.
Le regole vengono applicate durante la scansione in base al preset selezionato all'avvio (set attivo di regole). È possibile creare un numero illimitato di preset e come strutturarli dipende dalle caratteristiche del vostro processo. Possono essere raggruppati per lingue o distinti per ciascun progetto. Il numero di regole attive influisce sulla velocità e sulla precisione della scansione.
Impostazione del Preset nell'interfaccia di CheckmarxLe regole vengono modificate in un particolare strumento chiamato CxAuditor. Si tratta di un'applicazione desktop che si connette al server con Checkmarx. Questo strumento ha due modalità operative: modifica delle regole e analisi dei risultati di una scansione già effettuata.
Interfaccia CxAuditLe regole in Checkmarx sono suddivise per linguaggi, il che significa che ogni linguaggio ha il proprio insieme di query. Ci sono anche alcune regole generali che si applicano indipendentemente dal linguaggio, le cosiddette query di base. Nella maggior parte dei casi, le query di base contengono la ricerca di informazioni utilizzate in altre regole.
Suddivisione delle regole per linguaggiLe regole possono essere 'Eseguibili' e 'Non Eseguibili'. Non è esattamente un nome corretto, a mio avviso, ma è quello che è. La sostanza è che il risultato dell'esecuzione delle regole 'Eseguibili' sarà visualizzato nei risultati di scansione nell'interfaccia utente, mentre le regole 'Non Eseguibili' sono necessarie solo per utilizzare i loro risultati in altre query (di fatto - solo una funzione).
Determinazione 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 con il tasto destro e selezionare "Override" dal menu a discesa. È 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 loro impostazioni, il che significa che se una regola era attiva, continuerà a esserlo e verrà applicata immediatamente.
Esempio di nuova regola nell'interfaccia di Preset ManagerDurante l'esecuzione, viene costruito un "albero" di richieste, mostrando le dipendenze. Prima vengono eseguite le regole che raccolgono informazioni, poi quelle che le utilizzano. Il risultato dell'esecuzione viene memorizzato nella cache, quindi se c'è la possibilità di usare i risultati di una regola esistente, è meglio farlo, poiché ciò ridurrà i tempi di scansione.
Le regole possono essere applicate a diversi livelli:
Per l'intero sistema — verrà utilizzato per qualsiasi scansione di qualsiasi progetto
A livello di team (Team) — verrà applicato solo per la scansione dei progetti nel team selezionato.
A livello di progetto — verrà applicato in un progetto specifico.
Definizione del livello al quale verrà applicata la regola.
“Glossario“ per i principianti
E inizierò con alcune cose che mi hanno incuriosito, e mostrerò una serie di tecniche 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)
& (logico E) - unisce le liste in base a corrispondenza (list1 & list2), in modo simile all'intersezione (list1 * list2)
| (logico O) - unisce le liste in base a ricerca ampia (list1 | list2)
Non funziona con le liste: ^ && || % / Tutti gli elementi trovati
Nell'ambito del linguaggio scansionato, è possibile ottenere un elenco di tutti gli elementi che Checkmarx ha definito (stringhe, funzioni, classi, metodi, ecc.). Questo è uno spazio di oggetti a cui è possibile accedere tramite Tutti. Cioè, per cercare un oggetto con un nome specifico searchMe, è possibile eseguire una ricerca, ad esempio, per nome in tutti gli oggetti trovati:
// Такой запрос выдаст все элементы
result = All;
// Такой запрос выдаст все элементы, в имени которых присутствует “searchMe“
result = All.FindByName("searchMe");Tuttavia, se è necessario eseguire una ricerca in un'altra lingua che per qualche motivo non è stata inclusa nella scansione (ad esempio groovy in un progetto per Android), possiamo estendere il nostro spazio 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 il nome/percorso del file
Ci sono diversi attributi che possono essere ottenuti dai risultati dell'esecuzione della query (il nome del file in cui è stata trovata l'istanza, la stringa, ecc.), ma come ottenerli e usarli non è specificato nella documentazione. Per fare ciò, è necessario accedere alla proprietà LinePragma e all'interno di essa ci saranno 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);Vale la pena tenere a mente che FileName in realtà contiene il percorso del file, poiché abbiamo utilizzato il metodo GetFirstGraph.
Risultato dell'esecuzione
All'interno di CxQL è prevista una variabile speciale result, che restituisce il risultato dell'esecuzione della regola scritta da te. È inizializzata subito ed è possibile scrivere in essa risultati intermedi, modificandoli e affinando durante il lavoro. Tuttavia, se all'interno della regola non ci sarà un'assegnazione a questa variabile o funzione return— il risultato dell'esecuzione sarà sempre nullo.
La seguente query non 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 dell'esecuzione di altre regole
Le regole in Checkmarx possono essere paragonate alle funzioni in un linguaggio di programmazione comune. Durante la scrittura di una regola, puoi utilizzare tranquillamente i risultati di altre query. Ad esempio, non è necessario cercare ogni volta tutte le chiamate ai metodi nel codice, è sufficiente richiamare la regola desiderata:
// Получаем результат выполнения другого правила
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 è possibile scrivere subito la query desiderata e si è costretti a sperimentare, provando varie opzioni. Per questo caso, lo strumento prevede un logging, che può essere richiamato nel seguente modo:
// Находим что-то
CxList toLog = All.FindByShortName("log");
// Формируем строку и отправляем в лог
cxLog.WriteDebugMessage (“number of DOM elements =” + All.Count);Ma è importante ricordare che questo metodo accetta solo in ingresso una stringa, quindi non sarà possibile visualizzare l'elenco completo degli elementi trovati a seguito dell'esecuzione della prima operazione. Un secondo approccio, utilizzato per il debugging, è quello di assegnare di tanto in tanto a una variabile magica result il risultato dell'esecuzione della query e vedere cosa produce. Questo approccio non è molto comodo, poiché è necessario essere certi che nel codice successivo non ci siano ridefinizioni o operazioni su questo result oppure semplicemente commentare il codice sottostante. O si può, come me, dimenticare di rimuovere da una regola già pronta alcune di queste chiamate e restare sorpresi del perché niente funzioni.
Un modo più comodo è chiamare il metodo return con il parametro corretto. In questo caso, l'esecuzione della regola terminerà e potremo vedere cosa abbiamo ottenuto come risultato di ciò che abbiamo scritto:
// Находим что-то
CxList toLog = All.FindByShortName("log");
// Выводим результат выполнения
return toLog
//Все, что написано дальше не будет выполнено
result = All.DataInfluencedBy(toLog)Problema di login
Ci sono situazioni in cui non è possibile accedere allo strumento CxAudit (utilizzato per scrivere le regole). Le ragioni possono essere molte, come la chiusura improvvisa dell'applicazione, un aggiornamento inaspettato di Windows, BSOD e altre situazioni imprevisti indipendenti dalla nostra volontà. In questi casi, a volte rimane una sessione non completata nel database, che impedisce l'accesso. Per risolvere, è 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
Eccoci arrivati alla parte più interessante. Quando si inizia a scrivere regole in CxQL, spesso non mancano tanto la documentazione quanto alcuni esempi pratici per risolvere specifici problemi e una descrizione del processo di funzionamento delle query in generale.
Cercherò di semplificare un po' la vita a chi inizia ad avvicinarsi al linguaggio delle query e fornirò alcuni esempi di utilizzo delle Custom Queries per risolvere determinate problematiche. Alcuni di essi sono abbastanza 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 con quali problemi ci siamo trovati più spesso:
Obiettivo: Nei risultati di esecuzione delle regole ci sono più Flow e uno di essi è un'inclusione dell'altro, è necessario mantenere uno di essi.
Soluzione: Infatti, a volte Checkmarx mostra diversi Flow di flusso di dati che possono sovrapporsi e essere versioni abbreviate di altri. Per tali casi esiste un metodo specifico ReduceFlow. In base al 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 che lo strumento analizza
Soluzione: In Checkmarx esistono regole di base, i risultati delle quali vengono utilizzati da molte altre richieste. Aggiungendo alcuni di questi regole con dati specifici per la tua applicazione, è possibile migliorare subito i risultati della scansione. Ecco un esempio di regola da cui partire:
General_privacy_violation_list
Aggiungiamo alcune variabili che vengono 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: Consiglio di prestare subito attenzione alla regola fondamentale per la definizione delle password nel codice e di aggiungere un elenco di nomi di variabili comunemente utilizzati nella vostra 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*"));
// Aggiungiamo 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 framework utilizzati che non sono supportati da Checkmarx
Soluzione: Tutte le richieste in Checkmarx sono suddivise per linguaggi, quindi è necessario integrare le regole per ciascun linguaggio. Di seguito alcuni esempi di tali regole.
Se vengono utilizzate librerie che arricchiscono o sostituiscono le funzionalità standard, possono essere facilmente aggiunte alla regola di base. In questo modo, tutti coloro che la utilizzano saranno immediatamente informati delle nuove indicazioni. Un esempio sono le librerie per il logging in Android: Timber e Loggi. Nella fornitura 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 ne saremo a conoscenza. Provaremo ad aggiungere delle definizioni di metodi a Checkmarx.
Esempio di codice di test che utilizza la libreria Timber per il logging:
package 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("Error Message");
Timber.d("Debug Message");
Timber.tag("Some Different tag").e("And error message");
}
}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 completare la regola vicina, questa volta riguardante specificamente il logging in Android:
FindAndroidLog_Outputs
// Получаем результат выполнения базового правила
result = base.Find_Android_Log_Outputs();
// Дополняем вызовами, которые приходят из библиотеки Timber
result.Add(
All.FindByExactMemberAccess("Timber.*") +
All.FindByShortName("Timber").GetMembersOfTarget()
);Inoltre, se nelle applicazioni Android si utilizza per operazioni asincrone, è utile comunicare anche a Checkmarx, aggiungendo il metodo per ottenere dati dal task 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 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, poiché possono essere estratti senza particolari difficoltà dal dispositivo.
I file plist hanno caratteristiche che non sono ovvie a prima vista, ma sono importanti per Checkmarx. Scriviamo una regola che cercherà i dati richiesti e ci avviserà se vengono menzionate password o token.
Ecco un esempio di tale file, in cui è incapsulato un token per comunicare con il servizio backend:
DeviceDictionary
phone
iPhone 6s
privatekey
MIICXAIBAAKBgQCqGKukO1De7zhZj6+E c'è una regola per Checkmarx che include alcune peculiarità 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: Checkmarx offre funzionalità molto utili per lavorare con XML e per cercare valori, tag, attributi e altro. Tuttavia, nella documentazione c'è purtroppo un errore che rende non funzionante nessun esempio. Sebbene l'errore sia stato corretto nell'ultima versione della documentazione, fate attenzione se utilizzate versioni precedenti dei documenti.
Ecco un esempio errato dalla documentazione:
// Код работать не будет
result = All.FindXmlAttributesByNameAndValue("*.app", 8, “id”, "error- section", false, true);Di conseguenza, durante il tentativo di esecuzione, riceveremo un errore che indica che non Tutti esiste un tale metodo… Ed è corretto, poiché per utilizzare le funzioni per lavorare con XML è necessario uno spazio oggetti specifico e separato — cxXPath. Ecco come appare la richiesta corretta per cercare un'impostazione su Android che consente l'uso del traffico HTTP:
// Правильный вариант с использованием cxXPath
result = cxXPath.FindXmlAttributesByNameAndValue("*.xml", 8, "cleartextTrafficPermitted", "true", false, true);Esaminiamo un po' più nel dettaglio, poiché la sintassi di tutte le funzioni è simile; una volta comprese, basta scegliere quella necessaria. Quindi, procediamo per parametri:
"*.xml"— maschera dei file su cui effettuare la ricerca8— id della lingua a cui si applica la regola"cleartextTrafficPermitted"— nome dell'attributo in xml"true"— valore di questo attributofalse— utilizzo di espressioni regolari nella ricercatrue— indica che la ricerca verrà eseguita ignorando le maiuscole, quindi case-insensitive
Per esempio, è stata utilizzata una regola che determina configurazioni di connessione di rete non corrette, dal punto di vista della sicurezza, in Android, che consentono la comunicazione con il server tramite il protocollo HTTP. Ecco un esempio di configurazione contenente l'attributo cleartextTrafficPermitted con il valore true:
example.com
secure.example.comObiettivo: Limitare i risultati per nome/percorso del file
Soluzione: In uno dei nostri grandi progetti legati allo sviluppo di un'app mobile per Android, ci siamo imbattuti in falsi positivi della regola che determina le impostazioni di offuscamento. Il fatto è che la regola di default cerca nel file build.gradle le impostazioni che riguardano l'applicazione delle regole di offuscamento per la versione di rilascio dell'app.
Tuttavia, in grandi progetti si incontrano a volte file secondari build.gradle, che appartengono a librerie incluse nel progetto. La caratteristica è che anche se in questi file non è specificata la necessità di offuscamento, durante la compilazione verranno applicate le impostazioni del file di build principale.
Pertanto, il compito è quello di escludere i falsi positivi nei file secondari che riguardano le librerie. È possibile identificarli dalla presenza della stringa apply 'com.android.library'.
Ecco un esempio di codice da un file build.gradle, che definisce la necessità di offuscamento:
apply plugin: 'com.android.application'
android {
compileSdkVersion 24
buildToolsVersion "24.0.2"
defaultConfig {
...
}
buildTypes {
release {
minifyEnabled true
...
}
}
}
dependencies {
...
}Ecco un esempio di file build.gradle per una libreria inclusa nel progetto che non possiede tale impostazione:
applica il plugin: 'android-library'
declarazione {
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 in altri casi in cui è necessario determinare l'appartenenza del risultato a un determinato file.
Obiettivo: Aggiungere supporto per librerie di terze parti, se la sintassi non è completamente supportata
Soluzione: Il numero di vari framework usati nel processo di scrittura del codice è semplicemente incredibile. Certo, Checkmarx non sempre è a conoscenza della loro esistenza e il nostro obiettivo è insegnargli a capire che determinati metodi appartengono proprio a questo framework. A volte questo è complicato dal fatto che i framework utilizzano nomi di funzioni che sono molto comuni e non è possibile determinare con certezza la relazione di una particolare chiamata a una specifica libreria.
La difficoltà risiede nel fatto che la sintassi di tali librerie non è sempre correttamente riconosciuta, quindi è necessario sperimentare per evitare un gran numero di falsi positivi. Esistono diverse opzioni per migliorare la precisione della scansione e affrontare il compito assegnato:
La prima opzione è che sappiamo con certezza che la libreria viene utilizzata in un progetto specifico e possiamo applicare una regola a livello di team. Tuttavia, nel caso in cui il team decida di adottare un approccio diverso o utilizzi più librerie con nomi di funzioni in comune, potremmo trovarci di fronte a una situazione spiacevole a causa dei numerosi falsi positivi.
La seconda opzione è applicare una ricerca nei file in cui viene esplicitamente importata la libreria. Con questo approccio, saremo certi che in quel file venga utilizzata la libreria di cui abbiamo bisogno.
La terza opzione è utilizzare insieme i due approcci sopra citati.
Come esempio, analizziamo una libreria conosciuta in ambito ristretto. per il linguaggio di programmazione Scala, in particolare, la funzionalità . In generale, per passare parametri in una query SQL è necessario utilizzare l'operatore $, che inserisce i dati in una query SQL precedentemente formata. In altre parole, è un diretto analogo del Prepared Statement in Java. Tuttavia, se è necessario costruire dinamicamente una query SQL, ad esempio se si devono passare nomi di tabelle, è possibile utilizzare l'operatore #$, che inserirà i dati direttamente nella query (praticamente, come la concatenazione di stringhe).
Esempio di codice:
// В общем случае - значения, контролируемые пользователем
val table = "coffees"
sql"select * from #$table where name = $name".as[Coffee].headOptionCheckmarx attualmente non è in grado di identificare l'uso di Splicing Literal Values e ignora gli operatori #$, quindi cerchiamo di insegnargli a identificare potenziali SQL injection e a evidenziare i punti critici 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 di funzioni vulnerabili utilizzate nelle librerie Open-Source
Soluzione: Molte aziende utilizzano strumenti per il controllo dell'Open Source (pratica OSA) che consentono di rilevare l'uso di versioni vulnerabili delle librerie nelle applicazioni in fase di sviluppo. A volte non è possibile aggiornare una libreria a una versione sicura. In alcuni casi ci sono limitazioni funzionali, in altri la versione sicura non esiste affatto. In questo caso, una combinazione delle pratiche SAST e OSA può aiutare a determinare che le funzioni che portano all'exploitation della vulnerabilità non sono utilizzate nel codice.
Tuttavia, a volte, specialmente quando si tratta di JavaScript, potrebbe non essere un compito del tutto banale. Di seguito è presentata una soluzione, forse non ideale, ma comunque funzionante, a titolo di esempio per le vulnerabilità in un 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 collegandosi 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 dei certificati incorporati nell'applicazione
Soluzione: Spesso le applicazioni, in particolare quelle mobili, utilizzano certificati o chiavi per accedere a vari server o per verificare SSL-Pinning. Dal punto di vista della sicurezza, conservare tali elementi nel codice non è la prassi migliore. Proviamo a scrivere una regola che cerchi file simili 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. Naturalmente, conservarli all'interno del codice sorgente non è la scelta migliore, ma le situazioni possono variare. Grazie a query CxQL, trovare tali elementi è piuttosto semplice:
// Получаем все строки, которые содержатся в коде
CxList strings = base.Find_Strings();
// Ищем среди всех строк нужное нам значение. В примере токен в виде строки "qwerty12345"
result = strings.FindByShortName("qwerty12345");Conclusione
Spero che questo articolo possa essere utile a coloro che iniziano a familiarizzare con lo strumento Checkmarx. Anche coloro che scrivono regole da tempo possono trovare qualcosa di utile in questa guida.
Purtroppo, attualmente manca un risorsa dove poter trarre nuove idee per lo sviluppo di regole per Checkmarx. Per questo motivo abbiamo creato , dove condivideremo i nostri progressi, affinché chi utilizza CxQL possa trovare in esso qualcosa di utile e avere la possibilità di condividere con la comunità i propri lavori. Il repository è in fase di riempimento e strutturazione dei contenuti, quindi i contributi sono benvenuti!
Grazie per l'attenzione!
Fonte: habr.com

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