Cum să scrii reguli pentru Checkmarx fără a înnebuni

Hei Habr!

În activitatea noastră, compania noastră se ocupă foarte des cu diverse instrumente de analiză statică a codului (SAST). Din cutie, toate funcționează în medie. Desigur, totul depinde de proiect și de tehnologiile utilizate în acesta, precum și de cât de bine sunt acoperite aceste tehnologii de regulile de analiză. După părerea mea, unul dintre cele mai importante criterii atunci când alegeți un instrument SAST este capacitatea de a-l personaliza în funcție de specificul aplicațiilor dvs. și anume, scrieți și modificați regulile de analiză sau, așa cum sunt numite mai des, Interogări Personalizate.

Cum să scrii reguli pentru Checkmarx fără a înnebuni

Cel mai adesea folosim Checkmarx - un analizor de cod foarte interesant și puternic. În acest articol voi vorbi despre experiența mea de a scrie reguli de analiză pentru el.

Cuprins

Intrare

Pentru început, aș dori să recomand unul dintre puținele articole în limba rusă despre caracteristicile scrierii de interogări pentru Checkmarx. A fost publicat pe Habré la sfârșitul anului 2019 sub titlul: — Bună, Checkmarx! Cum să scrieți o interogare Checkmarx SAST și să găsiți vulnerabilități interesante.

Acesta examinează în detaliu cum să scrieți primele interogări în CxQL (Checkmarx Query Language) pentru o aplicație de testare și arată principiile de bază ale modului în care funcționează regulile de analiză.

Nu voi repeta ceea ce este descris în el, deși unele intersecții vor fi în continuare prezente. În articolul meu voi încerca să alcătuiesc un fel de „colecție de rețete”, o listă de soluții la probleme specifice pe care le-am întâlnit în timpul lucrului meu cu Checkmarx. A trebuit să-mi trezesc mințile peste multe dintre aceste probleme. Uneori nu existau suficiente informații în documentație, iar uneori chiar era greu de înțeles cum să faci ceea ce era necesar. Sper că experiența mea și nopțile nedormite nu vor fi în zadar, iar această „colecție de rețete Custom Queries” vă va economisi câteva ore sau câteva celule nervoase. Deci, să începem!

Informații generale despre reguli

Mai întâi, să ne uităm la câteva concepte de bază și la procesul de lucru cu regulile, pentru o mai bună înțelegere a ceea ce se va întâmpla în continuare. Și, de asemenea, pentru că documentația nu spune nimic despre asta sau este foarte răspândită în structură, ceea ce nu este foarte convenabil.

  1. Regulile sunt aplicate în timpul scanării în funcție de presetarea selectată la pornire (un set de reguli active). Puteți crea un număr nelimitat de presetări, iar modul exact de structurare a acestora depinde de specificul procesului dvs. Le puteți grupa după limbă sau puteți selecta presetări pentru fiecare proiect. Numărul de reguli active afectează viteza și acuratețea scanării.

    Cum să scrii reguli pentru Checkmarx fără a înnebuniConfigurarea presetărilor în interfața Checkmarx

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

    Cum să scrii reguli pentru Checkmarx fără a înnebuniInterfața CxAudit

  3. Regulile din Checkmarx sunt împărțite în funcție de limbă, adică fiecare limbă are propriul set de interogări. Există și câteva reguli generale care se aplică indiferent de limbă, acestea sunt așa-numitele interogări de bază. În cea mai mare parte, interogările de bază implică căutarea de informații pe care le folosesc alte reguli.

    Cum să scrii reguli pentru Checkmarx fără a înnebuniÎmpărțirea regulilor în funcție de limbă

  4. Regulile sunt „Executable” și „Non-Executable” (Executate și Neexecutate). Numele nu este chiar corect, după părerea mea, dar asta este. Concluzia 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 le folosi rezultatele în alte solicitări (în esență, doar o funcție).

    Cum să scrii reguli pentru Checkmarx fără a înnebuniDeterminarea tipului de regulă la creare

  5. Puteți crea reguli noi sau le puteți completa/rescrie pe cele existente. Pentru a rescrie o regulă, trebuie să o găsiți în arbore, faceți clic dreapta și selectați „Înlocuire” din meniul derulant. Este important să ne amintim aici că noile reguli nu sunt incluse inițial în presetări și nu sunt active. Pentru a începe să le utilizați, trebuie să le activați în meniul „Preset Manager” din instrument. Regulile rescrise își păstrează setările, adică dacă regula a fost activă, aceasta va rămâne așa și va fi aplicată imediat.

    Cum să scrii reguli pentru Checkmarx fără a înnebuniExemplu de regulă nouă în interfața Manager preset

  6. În timpul execuției, se construiește un „arboresc” de cereri, care depinde de ce. Regulile care colectează informații sunt executate mai întâi, iar cei care le folosesc pe al doilea. Rezultatul execuției este stocat în cache, deci dacă este posibil să utilizați rezultatele unei reguli existente, atunci este mai bine să faceți acest lucru, acest lucru va reduce timpul de scanare.

  7. Regulile pot fi aplicate la diferite niveluri:

  • Pentru întregul sistem - va fi folosit pentru orice scanare a oricărui proiect

  • La nivel de echipă (Echipă) - va fi folosit doar pentru scanarea proiectelor din echipa selectată.

  • La nivel de proiect - Se va aplica într-un anumit proiect

    Cum să scrii reguli pentru Checkmarx fără a înnebuniDeterminarea nivelului la care se va aplica regula

„Dicționar” pentru începători

Și voi începe cu câteva lucruri care mi-au pus întrebări și voi arăta, de asemenea, o serie de tehnici care vor simplifica semnificativ viața.

Operații cu liste

- вычитание одного из другого (list2 - list1)
* пересечение списков (list1 * list2)
+ сложение списков (list1 + list2)

& (логическое И) - объединяет списки по совпадению (list1 & list2), аналогично пересечению (list1 * list2)
| (логическое ИЛИ) - объединяет списки по широкому поиску (list1 | list2)

Со списками не работает:  ^  &&  ||  %  / 

Toate articolele găsite

În limbajul scanat, puteți obține o listă cu absolut toate elementele pe care Checkmarx le-a identificat (șiruri, funcții, clase, metode etc.). Acesta este un spațiu de obiecte prin care se poate accesa All. Adică pentru a căuta un obiect cu un anumit nume searchMe, puteți căuta, de exemplu, după nume în toate obiectele găsite:

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

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

Dar, dacă trebuie să căutați într-o altă limbă care, dintr-un anumit motiv, nu a fost inclusă în scanare (de exemplu, groovy într-un proiect Android), puteți extinde spațiul nostru obiect printr-o variabilă:

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

Funcții pentru analiza fluxului

Aceste funcții sunt folosite în multe reguli și iată o mică foaie de cheat cu ceea ce înseamnă:

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

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

Se obține numele/calea fișierului

Există mai multe atribute care pot fi obținute din rezultatele unei interogări (numele fișierului în care a fost găsită intrarea, șir etc.), dar documentația nu spune cum să le obții și să le folosești. Deci, pentru a face acest lucru, trebuie să accesați proprietatea LinePragma și obiectele de care avem nevoie vor fi localizate în interiorul acesteia:

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

Merită să rețineți că FileName conține de fapt calea către fișier, deoarece am folosit metoda GetFirstGraph.

Rezultatul executiei

Există o variabilă specială în interiorul CxQL result, care returnează rezultatul executării regulii dvs. scrise. Este inițializat imediat și puteți scrie rezultate intermediare în el, schimbându-le și rafinându-le pe măsură ce lucrați. Dar, dacă nu există nicio atribuire acestei variabile sau funcție în interiorul regulii return— rezultatul execuției va fi întotdeauna zero.

Următoarea interogare nu ne va returna nimic ca urmare a execuției și va fi întotdeauna goală:

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

Dar, după ce am atribuit rezultatul execuției rezultatului variabilei magice, vom vedea ce ne întoarce acest apel:

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

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

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

Folosind rezultatele altor reguli

Regulile din Checkmarx pot fi numite similare cu funcțiile dintr-un limbaj de programare obișnuit. Când scrieți o regulă, puteți utiliza rezultatele altor interogări. De exemplu, nu este nevoie să căutați toate apelurile de metodă în cod de fiecare dată, doar apelați regula dorită:

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

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

Această abordare vă permite să scurtați codul și să reduceți semnificativ timpul de execuție a regulii.

Rezolvarea problemelor

Logare

Când lucrați cu instrumentul, uneori nu este posibil să scrieți imediat interogarea dorită și trebuie să experimentați, încercând diferite opțiuni. Pentru un astfel de caz, instrumentul oferă înregistrare, care se numește după cum urmează:

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

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

Dar merită să ne amintim că această metodă acceptă doar ca intrare şir, deci nu va fi posibilă afișarea unei liste complete a elementelor găsite ca urmare a primei operațiuni. A doua opțiune, care este folosită pentru depanare, este de a atribui din când în când unei variabile magice result rezultatul interogării și vedeți ce se întâmplă. Această abordare nu este foarte convenabilă; trebuie să vă asigurați că nu există modificări sau operații cu aceasta în cod după result sau pur și simplu comentați codul de mai jos. Sau puteți, ca și mine, să uitați să eliminați mai multe astfel de apeluri dintr-o regulă gata făcută și să vă întrebați de ce nimic nu funcționează.

O modalitate mai convenabilă este să apelați metoda return cu parametrul necesar. În acest caz, executarea regulii se va încheia și vom putea vedea ce s-a întâmplat în urma a ceea ce am scris:

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

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

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

Problemă de conectare

Există situații în care nu puteți accesa instrumentul CxAudit (care este folosit pentru a scrie reguli). Pot exista multe motive pentru aceasta, inclusiv blocări, actualizări bruște ale Windows, BSOD și alte situații neprevăzute care sunt în afara controlului nostru. În acest caz, uneori există o sesiune neterminată în baza de date, ceea ce vă împiedică să vă conectați din nou. Pentru a o remedia, trebuie să rulați mai multe interogări:

Pentru Checkmarx înainte de 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');

Reguli de scriere

Acum ajungem la partea cea mai interesantă. Când începeți să scrieți reguli în CxQL, ceea ce vă lipsește adesea nu este atât documentația, cât câteva exemple vii de rezolvare a anumitor probleme și descrierea procesului de funcționare a interogărilor în general.

Voi încerca să fac viața puțin mai ușoară celor care încep să se scufunde în limbajul de interogare și să dau mai multe exemple de utilizare a Interogărilor personalizate pentru a rezolva anumite probleme. Unele dintre ele sunt destul de generale și pot fi folosite în compania ta practic fără modificări, altele sunt mai specifice, dar pot fi folosite și prin modificarea codului pentru a se potrivi specificului aplicațiilor tale.

Așadar, iată care sunt problemele pe care le-am întâlnit cel mai des:

obiectiv: Există mai multe fluxuri în rezultatele executării regulii și unul dintre ele este un cuib al altuia, trebuie să lăsați unul dintre ele.

soluţie: Într-adevăr, uneori Checkmarx arată mai multe fluxuri de date care se pot suprapune și pot fi o versiune scurtată a altora. Există o metodă specială pentru astfel de cazuri ReduceFlow. În funcție de parametru, acesta va selecta cel mai scurt sau cel mai lung debit:

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

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

obiectiv: Extindeți lista de date sensibile la care reacționează instrumentul

soluţie: Checkmarx are reguli de bază, ale căror rezultate sunt folosite de multe alte interogări. Suplimentând unele dintre aceste reguli cu date specifice aplicației dvs., puteți îmbunătăți imediat rezultatele scanării. Mai jos este un exemplu de regulă pentru a începe:

Lista_generală_de_încălcări_privacy

Să adăugăm mai multe variabile care sunt folosite în aplicația noastră pentru a stoca informații sensibile:

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

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

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

obiectiv: Extindeți lista de variabile cu parole

soluţie: Aș recomanda să fiți imediat atenți la regula de bază pentru definirea parolelor în cod și să adăugați la aceasta o listă de nume de variabile care sunt utilizate în mod obișnuit în compania dumneavoastră.

Listă_încălcări_confidențialitate_parole

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

// Дополняем дефолтный список переменных
List < string > pswdIncludeList = new List<string>{"*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 < string > pswdExcludeList = new List<string>{"*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);
	}
}

obiectiv: Adăugați cadre utilizate care nu sunt acceptate de Checkmarx

soluţie: Toate interogările din Checkmarx sunt împărțite în funcție de limbă, așa că trebuie să adăugați reguli pentru fiecare limbă. Mai jos sunt câteva exemple de astfel de reguli.

Dacă sunt folosite biblioteci care completează sau înlocuiesc funcționalitatea standard, acestea pot fi adăugate cu ușurință la regula de bază. Apoi, toți cei care îl folosesc vor afla imediat despre noile introduceri. De exemplu, bibliotecile pentru autentificare în Android sunt Timber și Loggi. În pachetul de bază, nu există reguli pentru identificarea apelurilor non-sistem, așa că dacă o parolă sau un identificator de sesiune intră în jurnal, nu vom ști despre asta. Să încercăm să adăugăm definiții ale unor astfel de metode la regulile Checkmarx.

Exemplu de cod de testare care utilizează biblioteca Timber pentru înregistrare:

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

Și iată un exemplu de solicitare pentru Checkmarx, care vă va permite să adăugați o definiție a apelării metodelor Timber ca punct de ieșire pentru datele din aplicație:

GăsițiAndroidOutputs

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

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

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

Și puteți adăuga, de asemenea, la regula vecină, dar aceasta se referă direct la conectarea în Android:

GăsițiAndroidLog_Outputs

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

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

De asemenea, dacă folosesc aplicații Android WorkManager pentru lucrul asincron, este o idee bună să informați suplimentar Checkmarx despre acest lucru, adăugând o metodă pentru obținerea datelor din sarcină getInputData:

GăsițiAndroidRead

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

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

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

obiectiv: Căutarea datelor sensibile în plist pentru proiecte iOS

soluţie: iOS folosește adesea fișiere speciale cu extensia .plist pentru a stoca diverse variabile și valori. Stocarea parolelor, jetoanelor, cheilor și altor date sensibile în aceste fișiere nu este recomandată, deoarece acestea pot fi extrase de pe dispozitiv fără probleme.

Fișierele Plist au caracteristici care nu sunt evidente cu ochiul liber, dar sunt importante pentru Checkmarx. Să scriem o regulă care să caute datele de care avem nevoie și să ne spună dacă parolele sau jetoanele sunt menționate undeva.

Un exemplu de astfel de fișier, care conține un token pentru comunicarea cu serviciul backend:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>DeviceDictionary</key>
	<dict>
		<key>phone</key>
		<string>iPhone 6s</string>
	</dict>
	<key>privatekey</key>
	<string>MIICXAIBAAKBgQCqGKukO1De7zhZj6+</string>
</dict>
</plist>

Și o regulă pentru Checkmarx, care are mai multe nuanțe care ar trebui să fie luate în considerare atunci când scrieți:

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

obiectiv: Găsirea informațiilor în XML

soluţie: Checkmarx are funcții foarte convenabile pentru a lucra cu XML și pentru a căuta valori, etichete, atribute și multe altele. Dar, din păcate, a existat o eroare în documentație din cauza căreia nu funcționează niciun exemplu. În ciuda faptului că acest defect a fost eliminat în cea mai recentă versiune a documentației, aveți grijă dacă utilizați versiuni anterioare ale documentelor.

Iată un exemplu incorect din documentație:

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

În urma încercării de execuție, vom primi o eroare care All nu există o astfel de metodă... Și acest lucru este adevărat, deoarece există un spațiu obiect special, separat pentru utilizarea funcțiilor pentru lucrul cu XML - cxXPath. Iată cum arată interogarea corectă pentru a găsi o setare în Android care să permită utilizarea traficului HTTP:

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

Să ne uităm la asta mai în detaliu, deoarece sintaxa pentru toate funcțiile este similară, după ce ți-ai dat seama una, atunci trebuie doar să o selectezi pe cea de care ai nevoie. Deci, secvenţial în funcţie de parametri:

  • "*.xml"— masca fișierelor de căutat

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

  • "cleartextTrafficPermitted"— numele atributului în xml

  • "true" — valoarea acestui atribut

  • false — utilizarea expresiilor regulate la căutare

  • true — înseamnă că căutarea va fi efectuată ignorând majuscule și minuscule, adică fără a ține seama de majuscule

De exemplu, am folosit o regulă care identifică incorecte, din punct de vedere al securității, setările de conexiune la rețea în Android care permit comunicarea cu serverul prin protocolul HTTP. Exemplu de setare care conține un atribut cleartextTrafficPermitted cu sens true:

<network-security-config>
    <domain-config>
        <domain includeSubdomains="true">example.com</domain>
        <trust-anchors>
            <certificates src="@raw/my_ca"/>
        </trust-anchors>
        <domain-config cleartextTrafficPermitted="true">
            <domain includeSubdomains="true">secure.example.com</domain>
        </domain-config>
    </domain-config>
</network-security-config>

obiectiv: Limitați rezultatele după numele/calea fișierului

soluţie: Într-unul dintre marile proiecte legate de dezvoltarea unei aplicații mobile pentru Android, am întâlnit false pozitive ale regulii care determină setarea de ofuscare. Faptul este că regula out of the box caută în fișier build.gradle o setare responsabilă pentru aplicarea regulilor de ofuscare pentru versiunea de lansare a aplicației.

Dar în proiectele mari uneori există fișiere copii build.gradle, care se referă la bibliotecile incluse în proiect. Particularitatea este că, chiar dacă aceste fișiere nu indică nevoia de ofuscare, setările fișierului de asamblare părinte vor fi aplicate în timpul compilării.

Astfel, sarcina este de a tăia declanșatoarele din fișierele copil care aparțin bibliotecilor. Ele pot fi identificate prin prezența liniei apply 'com.android.library'.

Exemplu de cod din fișier build.gradle, care determină nevoia de ofuscare:

apply plugin: 'com.android.application'

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

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

dependencies {
  ...
}

Fișier exemplu build.gradle pentru o bibliotecă inclusă în proiect care nu are această setare:

apply plugin: 'android-library'

dependencies {
  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 utilă nu numai pentru aplicațiile Android, ci și pentru alte cazuri în care trebuie să determinați dacă un rezultat aparține unui anumit fișier.

obiectiv: Adăugați suport pentru o bibliotecă terță parte dacă sintaxa nu este pe deplin acceptată

soluţie: Numărul de cadre diferite care sunt utilizate în procesul de scriere a codului este pur și simplu în afara diagramelor. Desigur, Checkmarx nu știe întotdeauna despre existența lor, iar sarcina noastră este să-l învățăm să înțeleagă că anumite metode aparțin în mod specific acestui cadru. Uneori, acest lucru este complicat de faptul că cadrele folosesc nume de funcții care sunt foarte comune și este imposibil să se determine fără ambiguitate relația unui anumit apel cu o anumită bibliotecă.

Dificultatea este că sintaxa unor astfel de biblioteci nu este întotdeauna recunoscută corect și trebuie să experimentați pentru a evita obținerea unui număr mare de rezultate false pozitive. Există mai multe opțiuni pentru a îmbunătăți acuratețea scanării și pentru a rezolva problema:

  • Prima varianta, stim sigur ca biblioteca este folosita intr-un proiect anume si poate aplica regula la nivel de echipa. Dar dacă echipa decide să adopte o abordare diferită sau folosește mai multe biblioteci în care numele de funcții se suprapun, putem obține o imagine nu foarte plăcută a numeroaselor fals pozitive

  • A doua opțiune este să căutați fișiere în care biblioteca este clar importată. Cu această abordare, putem fi siguri că biblioteca de care avem nevoie este folosită exact în acest fișier.

  • Și a treia opțiune este să folosiți împreună cele două abordări de mai sus.

De exemplu, să ne uităm la o bibliotecă bine-cunoscută în cercuri înguste şmecher pentru limbajul de programare Scala, și anume, funcționalitatea Îmbinarea valorilor literale. În general, pentru a transmite parametri unei interogări SQL, trebuie să utilizați operatorul $, care înlocuiește datele într-o interogare SQL preformată. Adică, de fapt, este un analog direct al Prepared Statement în Java. Dar, dacă trebuie să construiți dinamic o interogare SQL, de exemplu, dacă trebuie să transmiteți nume de tabel, puteți utiliza operatorul #$, care va înlocui direct datele în interogare (aproape ca concatenarea șirurilor).

Cod simplu:

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

Checkmarx nu știe încă cum să detecteze utilizarea Splicing Literal Values ​​și să ignore operatorii #$, deci să încercăm să-l învățăm să identifice potențialele injecții SQL și să evidențiem locurile potrivite din 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));
}

obiectiv: Căutați funcții vulnerabile utilizate în bibliotecile Open-Source

soluţie: Multe companii folosesc instrumente de monitorizare Open-Source (practica OSA) pentru a detecta utilizarea versiunilor vulnerabile ale bibliotecilor în aplicațiile dezvoltate. Uneori nu este posibil să actualizați o astfel de bibliotecă la o versiune securizată. În unele cazuri există limitări funcționale, în altele nu există deloc o versiune sigură. În acest caz, o combinație de practici SAST și OSA va ajuta la determinarea faptului că funcțiile care conduc la exploatarea vulnerabilității nu sunt utilizate în cod.

Dar uneori, mai ales când luați în considerare JavaScript, aceasta poate să nu fie o sarcină complet trivială. Mai jos este o soluție, poate nu ideală, dar totuși funcțională, folosind exemplul vulnerabilităților din componentă lodash în metode template и *set.

Exemple de testare a codului potențial vulnerabil î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 când vă conectați direct în html:

<!DOCTYPE html>
<html>
<head>
    <title>Lodash Tutorial</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));

obiectiv: Căutarea certificatelor încorporate în aplicație

soluţie: Nu este neobișnuit ca aplicațiile, în special cele mobile, să folosească certificate sau chei pentru a accesa diverse servere sau pentru a verifica SSL-Pinning. Din punct de vedere al securității, stocarea unor astfel de lucruri în cod nu este cea mai bună practică. Să încercăm să scriem o regulă care va căuta fișiere similare în depozit:

// Найдем все сертификаты по маске файла
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;

obiectiv: Găsirea jetoanelor compromise în aplicație

soluţie: De multe ori este necesar să revocați token-urile compromise sau alte informații importante care sunt prezente în cod. Desigur, stocarea lor în codul sursă nu este o idee bună, dar situațiile variază. Datorită interogărilor CxQL, găsirea unor astfel de lucruri este destul de ușoară:

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

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

Concluzie

Sper că acest articol va fi util celor care încep să se familiarizeze cu instrumentul Checkmarx. Poate că cei care își scriu propriile reguli de mult timp vor găsi și ei ceva util în acest ghid.

Din păcate, în prezent lipsește o resursă din care să poată fi adunate idei noi în timpul dezvoltării regulilor pentru Checkmarx. De aceea am creat depozit pe Github, unde ne vom posta munca, astfel încât toți cei care folosesc CxQL să găsească ceva util în el și să aibă, de asemenea, posibilitatea de a împărtăși munca lor cu comunitatea. Depozitul este în proces de completare și structurare a conținutului, așa că contribuitorii sunt bineveniți!

Vă mulțumim pentru atenție!

Sursa: www.habr.com

Adauga un comentariu