Cum să scrieți reguli pentru Checkmarx fără a o lua razna

Salut, Habr!

În activitatea noastră, compania noastră se confruntă foarte des cu diverse instrumente de analiză statică a codului (SAST). Din cutie, acestea funcționează toate la un nivel mediu. Bineînțeles, totul depinde de proiect și de tehnologiile utilizate în acesta, precum și de cât de bine aceste tehnologii sunt acoperite de regulile de analiză. Din punctul meu de vedere, unul dintre cele mai importante criterii în alegerea unui instrument SAST este posibilitatea de a-l personaliza în funcție de particularitățile aplicațiilor tale, adică de a scrie și modifica regulile de analiză sau, cum le numesc adesea, Custom Queries.

Cum să scrieți reguli pentru Checkmarx fără a o lua razna

Cel mai frecvent folosim Checkmarx – un analizator de cod foarte interesant și puternic. În acest articol, voi vorbi despre experiența mea în scrierea regulilor de analiză pentru el.

Cuprins

Introducere

Pentru început, aș dori să recomand unul dintre puținele articole în limba română despre particularitățile scrierii interogărilor pentru Checkmarx. Acesta a fost publicat pe Habr la sfârșitul anului 2019 sub titlul: „Hello, Checkmarx!“. Cum să scrii o interogare pentru Checkmarx SAST și să găsești vulnerabilități interesante.

În articol, este detaliat cum să scrii primele interogări în limbajul CxQL (Checkmarx Query Language) pentru o aplicație de testare și sunt prezentate principiile de bază ale funcționării regulilor de analiză.

Nu voi repeta ceea ce este descris în el, deși vor exista unele intersecții. În articolul meu, voi încerca să compun un fel de „culegere de rețete”, o listă de soluții pentru probleme specifice cu care m-am confruntat în timpul muncii mele cu Checkmarx. La multe dintre aceste probleme, a trebuit să mă gândesc mult. Uneori, lipseau datele din documentație, iar alteori era pur și simplu greu de înțeles cum să fac ceea ce era necesar. Sper că experiența mea și nopțile nedormite nu vor fi în zadar, iar această „culegere de rețete Custom Queries” vă va economisi câteva ore sau câteva celule nervoase. Deci, să începem!

Informații generale despre reguli

Pentru început, să analizăm câteva concepte de bază și procesul de lucru cu regulile, pentru a înțelege mai bine ce se va întâmpla mai departe. De asemenea, acest lucru este necesar deoarece documentația nu abordează acest subiect sau îl tratează vag în structură, ceea ce nu este foarte convenabil.

  1. Regulile se aplică în timpul scanării, în funcție de presetul ales la început (un set de reguli active). Se pot crea un număr nelimitat de preseturi, iar modul în care sunt structurate depinde de specificul procesului dvs. Acestea pot fi grupate pe limbi sau pot fi create preseturi pentru fiecare proiect. Numărul de reguli active afectează viteza și precizia scanării.

    Cum să scrieți reguli pentru Checkmarx fără a o lua raznaConfigurarea Preset-ului în interfața Checkmarx

  2. Regulile se editează într-un instrument special numit CxAuditor. Aceasta este o aplicație desktop care se conectează la serverul cu Checkmarx. Acest instrument are două moduri de funcționare: editarea regulilor și analiza rezultatelor scanării deja efectuate.

    Cum să scrieți reguli pentru Checkmarx fără a o lua raznaInterfața CxAudit

  3. Regulile în Checkmarx sunt împărțite în funcție de limbă, adică pentru fiecare limbă există un set de interogări specific. De asemenea, există și reguli comune care se aplică indiferent de limbă, acestea fiind cunoscute sub numele de interogări de bază. În majoritatea cazurilor, interogările de bază includ căutarea informațiilor utilizate de alte reguli.

    Cum să scrieți reguli pentru Checkmarx fără a o lua raznaÎmpărțirea regulilor pe limbi

  4. Regulile pot fi „Executable” și „Non-Executable” (Executabile și Neexecutabile). Acesta nu este un nume foarte corect, în opinia mea, dar asta este. Esența este că rezultatul executării regulilor „Executable” va fi afișat în rezultatele scanării în UI, iar regulile „Non-Executable” sunt necesare doar pentru a folosi rezultatele lor în alte interogări (practic — doar o funcție).

    Cum să scrieți reguli pentru Checkmarx fără a o lua raznaDefinirea tipului de regulă la crearea acesteia

  5. Se pot crea reguli noi sau se pot completa/redefini regulile existente. Pentru a rescrie o regulă, trebuie să o găsiți în arbore, să faceți clic dreapta și să selectați opțiunea „Override” din meniul derulant. Este important de reținut că regulile noi nu sunt activate în mod implicit în preseturi. Pentru a începe să le folosiți, trebuie să le activați în meniul „Preset Manager” din instrument. Reguli rescrise își păstrează setările, adică, dacă o regulă a fost activă, va rămâne astfel și va fi aplicată imediat.

    Cum să scrieți reguli pentru Checkmarx fără a o lua raznaExemplu de nouă regulă în interfața Preset Manager

  6. În timpul execuției, se construiește un „arbore” de cereri, care depinde de ce. Primele sunt executate regulile care colectează informații, iar celelalte folosesc aceste informații. Rezultatul execuției este cache-uit, astfel că, dacă este posibil să folosești rezultatele unei reguli existente, este mai bine să procedezi astfel, deoarece va reduce timpul de scanare.

  7. Regulile pot fi aplicate la diferite niveluri:

  • Pentru întreaga sistemă — va fi utilizat pentru orice scanare a oricărui proiect

  • La nivelul echipei (Team) — va fi aplicat doar pentru scanarea proiectelor din echipa selectată.

  • La nivelul proiectului — va fi aplicat într-un anumit proiect

    Cum să scrieți reguli pentru Checkmarx fără a o lua raznaDefinirea nivelului la care va fi aplicată regula

„Dicționar“ pentru începători

Și voi începe cu câteva lucruri care mi-au trezit întrebări, precum și voi arăta o serie de trucuri care vor simplifica considerabil viața.

Operații cu liste

- scăderea unuia din altul (list2 - list1)
* intersecția listelor (list1 * list2)
+ adunarea listelor (list1 + list2)

& (logicul AND) - combină listele prin potrivire (list1 & list2), similar cu intersecția (list1 * list2)
| (logicul OR) - combină listele prin căutare largă (list1 | list2)

Nu funcționează cu liste: ^ && || % / 

Toate elementele găsite

În cadrul limbajului scanat, se poate obține o listă a tuturor elementelor definite de Checkmarx (linii, funcții, clase, metode etc.). Aceasta este o anumită zonă de obiecte, la care se poate accesa prin Toate. Adică, pentru a căuta un obiect cu un nume specific searchMe, se poate efectua o căutare, de exemplu, după nume în toate obiectele găsite:

// Такой запрос выдаст все элементы
result = All;

// Такой запрос выдаст все элементы, в имени которых присутствует “searchMe“
result = All.FindByName("searchMe");

Dar, dacă trebuie să efectuezi o căutare într-un alt limbaj, care din anumite motive nu a fost inclus în scanare (de exemplu, groovy într-un proiect pentru Android), se poate extinde zona noastră de obiecte prin variabila:

result = AllMembers.All.FindByName("searchMe");

Funcții pentru analiza Flow

Aceste funcții sunt utilizate în multe reguli și iată un mic ghid despre ce înseamnă ele:

// Какие данные second влияют на first.
// Другими словами - ТО (second) что влияет на  МЕНЯ (first).
result = first.DataInfluencedBy(second);

// Какие данные first влияют на second.
// Другими словами - Я (first) влияю на ТО (second).
result = first.DataInfluencingOn(second);

Obținerea numelui/calei fișierului

Există câteva atribute care pot fi obținute din rezultatele execuției cererii (numele fișierului în care a fost găsită apariția, linia etc.), dar cum să le obții și să le folosești nu este menționat în documentație. Astfel, pentru a face acest lucru, trebuie să accesezi proprietatea LinePragma și, deja în interiorul ei, vor fi obiectele de care avem nevoie:

// Для примера найдем все методы
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);

Este bine să ai în vedere că FileName în realitate conține calea către fișier, deoarece am folosit metoda GetFirstGraph.

Rezultatul execuției

În interiorul CxQL există o variabilă specială rezultat, care returnează rezultatul execuției regulii scrise de tine. Aceasta este inițializată imediat și poți înregistra rezultate intermediare, modificând și rafinându-le în timpul execuției. Dar, dacă în interiorul regulii nu există o atribuire pentru această variabilă sau funcție return— rezultatul execuției va fi întotdeauna zero.

Următoarea interogare nu ne va returna nimic în urma execuției și va fi întotdeauna goală:

// Находим элементы foo
CxList libraries = All.FindByName("foo");

Dar, atribuindo rezultatul execuției variabilei magice result — vom vedea ce ne returnează acest apel:

// Находим элементы foo
CxList libraries = All.FindByName("foo");

// Выводим, как результат выполнения правила
result = libraries

// Или еще короче
result = All.FindByName("foo");

Utilizarea rezultatelor execuției altor reguli

Regulile din Checkmarx pot fi considerate echivalente cu funcțiile dintr-o limbaj de programare obișnuit. Când scrii o regulă, poți folosi rezultatele altor interogări. De exemplu, nu este nevoie să cauți de fiecare dată toate apelurile de metode în cod, este suficient să apelezi regula necesară:

// Получаем результат выполнения другого правила
CxList methods = Find_Methods();

// Ищем внутри метод foo. 
// Второй параметр false означает, что ищем без чувствительности к регистру
result = methods.FindByShortName("foo", false);

Această abordare permite reducerea codului și diminuarea semnificativă a timpului de execuție al regulii.

Rezolvarea problemelor

Logare

Când lucrezi cu instrumentul, uneori nu reușești să scrii imediat interogarea necesară și trebuie să experimentezi, încercând diverse variante. În acest caz, este prevăzut un sistem de înregistrare, care se activează în felul următor:

// Находим что-то
CxList toLog = All.FindByShortName("log");

// Формируем строку и отправляем в лог
cxLog.WriteDebugMessage (“number of DOM elements =” + All.Count);

Dar trebuie să ții cont că acest metod acceptă doar șiruri, așa că nu poți obține lista completă a elementelor găsite în urma primei operații. A doua variantă folosită pentru depanare — este din când în când să atribui variabilei magice rezultat rezultatul execuției interogării și să vezi ce obții. Această abordare nu este foarte convenabilă, trebuie să fii sigur că nu există redefiniri sau operații cu aceasta după rezultat sau pur și simplu să comentezi codul situat mai jos. Sau poți, ca mine, să uiți să elimini din regula gata pregătită câteva astfel de apeluri și să te întrebi de ce nu funcționează nimic.

O modalitate mai convenabilă — este să apelezi metoda return cu parametrul necesar. În acest caz, execuția regulii se va termina și vom putea vedea ce am obținut ca rezultat al ceea ce am scris:

// Находим что-то
CxList toLog = All.FindByShortName("log");

// Выводим результат выполнения
return toLog

//Все, что написано дальше не будет выполнено
result = All.DataInfluencedBy(toLog)

Problema cu autentificarea

Există situații în care nu se poate accesa instrumentul CxAudit (utilizat pentru scrierea regulilor). Cauzele pot fi multiple: o închidere abruptă a aplicației, o actualizare neașteptată a Windows, BSOD și alte situații imprevizibile care nu depind de noi. În astfel de cazuri, uneori rămâne o sesiune nefinalizată în baza de date, ceea ce împiedică accesul din nou. Pentru a corecta această situație, trebuie efectuate câteva interogări:

Pentru Checkmarx până la 8.6:

// Проверяем, что есть залогиненые пользователи, выполнив запрос в БД
SELECT COUNT(*) FROM [CxDB].[dbo].LoggedinUser WHERE [ClientType] = 6;
 
// Если что-то есть, а на самом деле даже если и нет, попробовать выполнить запрос
DELETE FROM [CxDB].[dbo].LoggedinUser WHERE [ClientType] = 6;

Pentru Checkmarx după 8.6:

// Проверяем, что есть залогиненые пользователи, выполнив запрос в БД
SELECT COUNT(*) FROM LoggedinUser WHERE (ClientType = 'Audit');
 
// Если что-то есть, а на самом деле даже если и нет, попробовать выполнить запрос
DELETE FROM [CxDB].[dbo].LoggedinUser WHERE (ClientType = 'Audit');

Scrierea regulilor

Iată că am ajuns la partea cea mai interesantă. Atunci când începi să scrii reguli în CxQL, de multe ori nu ai nevoie atât de mult de documentație, cât de exemple concrete care să ilustreze soluționarea anumitor sarcini și descrierea procesului de funcționare a interogărilor în general.

Voi încerca să le ușurez viața celor care încep să se familiarizeze cu limbajul de interogare și voi prezenta câteva exemple de utilizare a Custom Queries pentru soluționarea anumitor sarcini. Unele dintre ele sunt destul de generale și pot fi aplicate în compania dumneavoastră aproape fără modificări, iar altele sunt mai specifice, dar pot fi folosite la fel, adaptând codul la specificul aplicațiilor dumneavoastră.

Deci, iată cu ce tipuri de sarcini ne-am confruntat cel mai frecvent:

Sarcina: În rezultatele executării regulii, există mai multe Flow și unul dintre ele este un sub-flux al altuia, este necesar să lăsăm doar unul dintre ele.

Soluția: Adevărat, uneori Checkmarx arată mai multe Flow-uri de mișcare a datelor, care se pot suprapune și pot fi versiuni scurtate ale altora. Pentru astfel de cazuri există o metodă specială ReduceFlow. În funcție de parametru, va selecta cel mai scurt sau cel mai lung Flow:

// Оставить только длинные Flow
result = result.ReduceFlow(CxList.ReduceFlowType.ReduceSmallFlow);

// Оставить только короткие Flow
result = result.ReduceFlow(CxList.ReduceFlowType.ReduceBigFlow);

Sarcina: Extinde lista de date sensibile la care răspunde instrumentul

Soluția: În Checkmarx există reguli de bază, rezultatul executării cărora este utilizat de multe alte interogări. Adăugând anumite date specifice aplicației dumneavoastră la unele dintre aceste reguli, puteți îmbunătăți imediat rezultatele scanării. Iată un exemplu de regulă cu care puteți începe:

General_privacy_violation_list

Să adăugăm câteva variabile care sunt utilizate în aplicația noastră pentru stocarea informațiilor sensibile:

// Получаем результат выполнения базового правила
result = base.General_privacy_violation_list();

// Ищем элементы, которые попадают под простые регулярные выражения. Можно дополнить характерными для вас паттернами.
CxList personalList = All.FindByShortNames(new List<string> {
	"*securityToken*", "*sessionId*"}, false);

// Добавляем к конечному результату
result.Add(personalList);

Sarcina: Extinde lista variabilelor cu parole

Soluția: Aș recomanda să acorzi atenție imediată regulii de bază pentru definirea parolelor în cod și să adaugi o listă de nume de variabile care sunt utilizate în compania ta.

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*"));

// Completează lista implicită de variabile
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);
	}
}

Sarcina: Adaugă cadrul utilizat care nu este suportat de Checkmarx

Soluția: Toate cererile în Checkmarx sunt împărțite pe limbaje, așa că regulile trebuie completate pentru fiecare limbaj. Iată câteva exemple de astfel de reguli.

Dacă se folosesc biblioteci care completează sau înlocuiesc funcționalitatea standard — este ușor să le adaugi în regula de bază. Astfel, toți cei care o folosesc vor fi imediat informați despre noile adăugiri. De exemplu, biblioteci pentru logging în Android — Timber și Loggi. În pachetul de bază al regulilor, nu sunt incluse apeluri nesistematice, astfel încât, dacă o parolă sau un identificator de sesiune ajunge în log, nu vom ști despre aceasta. Vom încerca să adăugăm în regulile Checkmarx definiții pentru astfel de metode.

Un exemplu de cod de testare care folosește biblioteca Timber pentru logging:

pachet 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("Mesaj de eroare");
        Timber.d("Mesaj de depanare");

        Timber.tag("O etichetă diferită").e("Și mesaj de eroare");
    }
}

Iată un exemplu de cerere pentru Checkmarx, care va permite adăugarea definiției apelurilor de metode Timber, ca punct de ieșire a datelor din aplicație:

FindAndroidOutputs

// Получаем результат выполнения базового правила
result = base.Find_Android_Outputs();

// Дополняем вызовами, которые приходят из библиотеки Timber
CxList timber = All.FindByExactMemberAccess("Timber.*") +
    All.FindByShortName("Timber").GetMembersOfTarget();

// Добавляем к конечному результату
result.Add(timber);

De asemenea, putem completa regula învecinată, dar de data aceasta referitoare direct la logare în Android:

FindAndroidLog_Outputs

// Получаем результат выполнения базового правила
result = base.Find_Android_Log_Outputs();

// Дополняем вызовами, которые приходят из библиотеки Timber
result.Add(
  All.FindByExactMemberAccess("Timber.*") +
  All.FindByShortName("Timber").GetMembersOfTarget()
);

De asemenea, dacă în aplicațiile Android se folosește WorkManager pentru lucrul asincron, ar fi bine să raportăm acest lucru la Checkmarx, adăugând metoda de obținere a datelor din sarcină getInputData:

FindAndroidRead

// Получаем результат выполнения базового правила
result = base.Find_Android_Read();

// Дополняем вызовом функции getInputData, которая используется в WorkManager
CxList getInputData = All.FindByShortName("getInputData");

// Добавляем к конечному результату
result.Add(getInputData.GetMembersOfTarget());

Sarcina: Căutarea datelor sensibile în plist pentru proiectele iOS

Soluția: Adesea, pentru stocarea diferitelor variabile și valori în iOS se folosesc fișiere speciale cu extensia .plist. Stocarea parolelor, token-urilor, cheilor și altor date sensibile în aceste fișiere nu este recomandată, deoarece acestea pot fi extrase cu ușurință de pe dispozitiv.

Fișierele plist au particularități care nu sunt evidente cu ochiul liber, dar sunt importante pentru Checkmarx. Vom scrie o regulă care va căuta datele necesare și ne va anunța dacă undeva sunt menționate parole sau token-uri.

Un exemplu de astfel de fișier, în care este încorporat un token pentru comunicarea cu serviciul backend:

DeviceDictionary
	
		phone
		iPhone 6s
		
	privatekey
	MIICXAIBAAKBgQCqGKukO1De7zhZj6+

Și o regulă pentru Checkmarx, care are câteva nuanțe ce trebuie luate în considerare la scriere:

// Используем результат выполнения правила по поиску файлов 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);

Sarcina: Căutarea informațiilor în XML

Soluția: În Checkmarx există funcții foarte utile pentru lucrul cu XML și căutarea valorilor, etichetelor, atributelor și altele. Dar în documentație, din păcate, a fost o eroare din cauza căreia niciun exemplu nu funcționează. Deși în ultima versiune a documentației această problemă a fost corectată - fiți atenți dacă folosiți versiuni mai vechi ale documentelor.

Iată un exemplu greșit din documentație:

// Код работать не будет
result = All.FindXmlAttributesByNameAndValue("*.app", 8, “id”, "error- section", false, true);

Ca urmare a încercării de execuție, vom obține o eroare, că Toate nu există o astfel de metodă... Și asta este adevărat, deoarece pentru utilizarea funcțiilor de lucru cu XML există un spațiu de obiecte special, separat — cxXPath. Iată cum arată cererea corectă pentru a găsi setarea din Android care permite utilizarea traficului HTTP:

// Правильный вариант с использованием cxXPath
result = cxXPath.FindXmlAttributesByNameAndValue("*.xml", 8, "cleartextTrafficPermitted", "true", false, true);

Să analizăm puțin mai în detaliu, deoarece sintaxa tuturor funcțiilor este similară, după ce ai înțeles una, trebuie doar să alegi pe cea dorită. Așadar, pas cu pas prin parametrii:

  • "*.xml"— masca fișierelor pe care trebuie să le cauți

  • 8 — id-ul limbii pentru care se aplică regula

  • "cleartextTrafficPermitted"— numele atributului din xml

  • "true" — valoarea acestui atribut

  • false — utilizarea expresiilor regulate în timpul căutării

  • true — înseamnă că căutarea se va efectua ignorând literele mari și mici, adică fără a ține cont de caz

Pentru exemplu, a fost utilizată o regulă care determină setările incorecte, din punct de vedere al securității, ale conexiunii de rețea în Android, care permit comunicarea cu serverul prin protocolul HTTP. Exemplul de setare care conține atributul cleartextTrafficPermitted cu valoarea true:

example.com
        
            
        
        
            secure.example.com

Sarcina: Limitarea rezultatelor după numele/calea fișierului

Soluția: În unul dintre proiectele mari legate de dezvoltarea unei aplicații mobile pentru Android, ne-am confruntat cu false alarme ale regulii care definește setarea de obfuscare. Problema este că regula din cutie caută în fișierul build.gradle setarea care răspunde de aplicarea regulilor de obfuscare pentru versiunea de producție a aplicației.

Dar în proiectele mari, uneori apar fișiere copil build.gradle, care aparțin bibliotecilor incluse în proiect. Particularitatea este că, chiar dacă în aceste fișiere nu este indicată necesitatea obfuscării, la compilare se vor aplica setările fișierului părinte de construire.

Astfel, sarcina constă în a exclude alertele din fișierele copil care se referă la biblioteci. Acestea pot fi determinate prin prezența liniei apply 'com.android.library'.

Un exemplu de cod din fișierul build.gradle, care determină necesitatea obfuscării:

aplica plugin: 'com.android.application'

android {
    compileSdkVersion 24
    buildToolsVersion "24.0.2"
    defaultConfig {
        ...
    }

    buildTypes {
        release {
            minifyEnabled true
            ...
        }
    }
}

dependențe {
  ...
}

Exemplu de fișier build.gradle pentru biblioteca inclusă în proiect care nu are o astfel de configurare:

aplica plugin: 'android-library'

dependențe {
  compile 'com.android.support:support-v4:18.0.+ '
}

android {
  compileSdkVersion 14
  buildToolsVersion '17.0.0'
  ...
}

Și regula pentru 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);
		}
	}
}

Această abordare poate fi destul de universală și poate fi utilă nu doar pentru aplicații Android, ci și pentru alte cazuri când este necesar să se determine apartenența rezultatului la un anumit fișier.

Sarcina: Adăugați suport pentru o bibliotecă terță parte, dacă sintaxa nu este complet acceptată

Soluția: Numărul diverselor cadre folosite în procesul de scriere a codului este pur și simplu copleșitor. Desigur, Checkmarx nu știe întotdeauna despre existența acestora și sarcina noastră este să-l învățăm să înțeleagă că anumite metode aparțin acestei biblioteci. Uneori, acest lucru este complicat de faptul că cadrele folosesc denumiri de funcții care sunt foarte comune și nu putem determina clar legătura unei anumite apeluri cu o bibliotecă anume.

Dificultatea constă în faptul că sintaxa unor astfel de biblioteci nu este întotdeauna recunoscută corect și trebuie să experimentăm pentru a nu obține un număr mare de alarme false. Există câteva variante pentru a îmbunătăți precizia scanării și a rezolva sarcina propusă:

  • Prima variantă, știm cu siguranță că biblioteca este folosită într-un anumit proiect și putem aplica regula la nivel de echipă. Dar în cazul în care echipa decide să folosească o altă abordare sau utilizează mai multe biblioteci cu nume de funcții care se suprapun, putem obține un rezultat mai puțin plăcut din numeroasele alarme false.

  • A doua variantă, aplicarea căutării în fișiere în care se face explicit importul bibliotecii. Prin această abordare, putem fi siguri că în acest fișier se aplică cu adevărat biblioteca de care avem nevoie.

  • Și a treia variantă este utilizarea ambelor abordări anterioare în comun.

Ca exemplu, să analizăm o bibliotecă cunoscută în cercuri restrânse slick pentru limbajul de programare Scala, și anume, funcționalitatea Splicing Literal Values. În general, pentru a transmite parametrii în interogările SQL, este necesar să se utilizeze operatorul $, care înlocuiește datele într-o interogare SQL preformatată. Cu alte cuvinte, acesta este un echivalent direct al Prepared Statement din Java. Însă, în cazul în care este necesară construirea dinamică a interogării SQL, de exemplu, atunci când trebuie transmise numele tabelor, se poate utiliza operatorul #$, care va înlocui direct datele în interogare (practic, ca o concatenare de șiruri).

Exemplu de cod:

// В общем случае - значения, контролируемые пользователем
val table = "coffees"
sql"select * from #$table where name = $name".as[Coffee].headOption

Checkmarx nu poate momentan să identifice utilizarea Splicing Literal Values și să treacă cu vederea operatorii #$, așa că să încercăm să-l învățăm să identifice posibilele SQL injection și să evidențieze locurile necesare în cod:

// Находим все импорты
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));
}

Sarcina: Căutarea funcțiilor vulnerabile utilizate în bibliotecile Open-Source

Soluția: În multe companii se folosesc instrumente pentru controlul Open-Source (practica OSA), care permit detectarea utilizării versiunilor vulnerabile ale bibliotecilor în aplicațiile în dezvoltare. Uneori, actualizarea unei astfel de biblioteci la o versiune sigură nu este posibilă. În unele cazuri există restricții funcționale, în altele pur și simplu nu există o versiune sigură. În acest caz, o combinație de practici SAST și OSA poate ajuta, permițând identificarea faptului că funcțiile care conduc la exploatarea vulnerabilității nu sunt utilizate în cod.

Dar uneori, mai ales dacă ne gândim la JavaScript, poate fi o sarcină nu tocmai trivială. Mai jos este prezentată o soluție, poate nu ideală, dar funcțională, cu exemplul vulnerabilităților într-un component lodash în metodele template și *set.

Exemple de cod potențial vulnerabil de testare într-un fișier 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!'

Și atunci când este conectat direct în html:

<!DOCTYPE html>
<html>
<head>
    <title>Tutorial 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>

Căutăm toate metodele noastre vulnerabile, care sunt enumerate în vulnerabilități:

// Ищем все строки: в которых встречается строка 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));

Sarcina: Căutarea certificatelor hardcodate în aplicație

Soluția: Deseori, aplicațiile, în special cele mobile, utilizează certificate sau chei pentru a avea acces la diverse servere sau pentru a verifica SSL-Pinning. Privind din punct de vedere al securității — a stoca astfel de lucruri în cod nu este cea mai bună practică. Să încercăm să scriem o regulă care va căuta astfel de fișiere în 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;

Sarcina: Căutarea token-urilor compromise în aplicație

Soluția: Adesea trebuie să revocăm token-uri compromise sau alte informații importante care sunt prezente în cod. Bineînțeles, a le păstra în interiorul surselor nu este cea mai bună idee, dar situațiile sunt diferite. Datorită cererilor CxQL, găsirea acestor lucruri este suficient de simplă:

// Получаем все строки, которые содержатся в коде
CxList strings = base.Find_Strings();

// Ищем среди всех строк нужное нам значение. В примере токен в виде строки "qwerty12345"
result = strings.FindByShortName("qwerty12345");

Concluzie

Sper că pentru cei care își încep familiarizarea cu instrumentul Checkmarx, acest articol va fi util. Poate și cei care scriu regulile lor de mult timp vor găsi ceva folositor în acest ghid.

Din păcate, în prezent ne lipsește foarte mult o resursă unde să putem găsi idei noi în procesul de dezvoltare a regulilor pentru Checkmarx. De aceea am creat un depozit pe Github, unde vom publica realizările noastre, astfel încât oricine folosește CxQL să poată găsi ceva util în el, dar și să aibă ocazia să împărtășească cu comunitatea munca sa. Depozitul este în proces de umplere și structurare a conținutului, așa că contribuțiile sunt binevenite!

Vă mulțumesc pentru atenție!

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster