Kako napisati pravila za Checkmarx in ne zmešati se

Hello, Habr!

V svojem delu naša podjetja pogosto naletita na različna orodja za statično analizo kode (SAST). Iz škatle delujejo povprečno. Seveda je vse odvisno od projekta in tehnologij, ki jih uporablja, ter od tega, koliko dobro so te tehnologije zajete s pravili analize. Po mojem mnenju je ena najpomembnejših meril pri izbiri orodja SAST možnost prilagajanja le-tega posebnim značilnostim vaših aplikacij, natančneje pisanje in spreminjanje pravil analize ali, kot jih pogosto imenujemo, Custom Queries.

Kako napisati pravila za Checkmarx in ne zmešati se

Najpogosteje uporabljamo Checkmarx — zelo zanimiv in zmogljiv analizator kode. V tem članku bom delil svoje izkušnje s pisanjem pravil analize zanj.

Taula de continguts

Uvod

Za začetek bi želel priporočiti enega redkih člankov v slovenščini o posebnostih pisanja poizvedb za Checkmarx. Objavljen je bil na Habré konec leta 2019 pod naslovom: „Hello, Checkmarx!“. Kako napisati poizvedbo za Checkmarx SAST in najti odlične ranljivosti.

V njem je podrobno obravnavano, kako napisati prve poizvedbe v jeziku CxQL (Checkmarx Query Language) za nek testni program in prikazani so osnovni principi delovanja pravil analize.

Ne bom ponavljal tistega, kar je napisano tam, čeprav bodo nekatera prekrivanja vseeno prisotna. V svojem članku bom poskušal sestaviti nekakšen 'zbirnik receptov', seznam rešitev specifičnih nalog, s katerimi sem se srečeval med delom s Checkmarxom. Ob mnogih iz teh nalog sem se moral kar precej namučiti. Občasno mi je primanjkovalo podatkov v dokumentaciji, občasno pa je bilo sploh težko razumeti, kako izvesti to, kar je bilo zahtevano. Upam, da moja izkušnja in neprespane noči ne bodo šle v nič, ter da bo ta 'zbirnik receptov Custom Queries' prihranil nekaj ur ali pa morda kakšno živčno celico. Torej, začnimo!

Splošne informacije o pravilih

Za začetek si oglejmo nekaj osnovnih pojmov in postopek dela s pravili, da bolje razumemo, kaj se bo dogajalo naprej. In tudi zato, ker to v dokumentaciji ni omenjeno ali je močno razpršeno po strukturi, kar ni zelo priročno.

  1. Pravila se uporabljajo pri skeniranju glede na izbran prednastavitev (skupina aktivnih pravil). Prednastavitve lahko ustvarite v neskončnih količinah, način njihove strukture pa je odvisen od posebnosti vašega procesa. Lahko jih razvrstite po jezikih ali določite prednastavitve za vsak projekt. Število aktivnih pravil vpliva na hitrost in natančnost skeniranja.

    Kako napisati pravila za Checkmarx in ne zmešati seNastavitev prednastavitve v vmesniku Checkmarx

  2. Pravila se urejajo s posebnim orodjem, imenovanim CxAuditor. To je namizna aplikacija, ki se povezuje na strežnik s Checkmarx. To orodje deluje v dveh načinih: urejanje pravil in analiza rezultatov že izvedenega skeniranja.

    Kako napisati pravila za Checkmarx in ne zmešati seVmesnik CxAudit

  3. Pravila v Checkmarx so razdeljena po jezikih, to pomeni, da za vsak jezik obstaja svoj nabor poizvedb. Obstajajo tudi nekatera splošna pravila, ki se uporabljajo ne glede na jezik, to so tako imenovane osnovne poizvedbe. V večini primerov osnovne poizvedbe vsebujejo iskanje informacij, ki jih uporabljajo druga pravila.

    Kako napisati pravila za Checkmarx in ne zmešati seRazdelitev pravil po jezikih

  4. Pravila so lahko 'Executable' in 'Non-Executable' (Izvedljiva in Neizvedljiva). Ni sicer povsem pravilno ime, po mojem mnenju, a tako je. Osnovna ideja je, da se rezultati izvajanja 'Executable' pravil prikažejo v rezultatih skeniranja v uporabniškem vmesniku, medtem ko so pravila 'Non-Executable' potrebna samo za uporabo njihovih rezultatov v drugih poizvedbah (v bistvu — zgolj funkcija).

    Kako napisati pravila za Checkmarx in ne zmešati seOpredelitev vrste pravila pri ustvarjanju

  5. Nova pravila lahko ustvarite ali dopolnite/perskrbite obstoječa. Da bi prepisali pravilo, morate najti to pravilo v drevesu, desno klikniti in v pojavnem meniju izbrati možnost 'Override'. Tu je pomembno vedeti, da nova pravila izvorno niso vključena v prednastavitve in niso aktivna. Da bi jih lahko uporabili, jih morate aktivirati v meniju 'Preset Manager' v orodju. Prepisana pravila ohranijo svoje nastavitve, to pomeni, da če je bilo pravilo aktivno, bo tako ostalo in se bo uporabljalo takoj.

    Kako napisati pravila za Checkmarx in ne zmešati sePrimer novega pravila v vmesniku za upravljanje prednastavitvami

  6. Med izvajanjem se gradi 'drevo' poizvedb, kar je odvisno od česa. Najprej se izvajajo pravila, ki zbirajo informacije, nato tista, ki jih uporabljajo. Rezultat izvajanja se predpomni, tako da, če je mogoče uporabiti rezultate obstoječega pravila, je najbolje to storiti, kar bo zmanjšalo čas skeniranja.

  7. Pravila je mogoče uporabljati na različnih ravneh:

  • Za celoten sistem — uporabljen bo pri katerem koli skeniranju katerega koli projekta

  • Na ravni ekipe (Team) — uporabljal se bo samo pri skeniranju projektov v izbrani ekipi.

  • Na ravni projekta — uporabljal se bo v konkretnem projektu

    Kako napisati pravila za Checkmarx in ne zmešati seDoločitev ravni, na kateri bo pravilo uporabljeno

„Slovar“ za začetnike

In začel bom z več stvarmi, ki so mi povzročale vprašanja, kot tudi pokazal nekaj trikov, ki bodo bistveno poenostavili življenje.

Operacije s seznami

- odštevanje enega od drugega (list2 - list1)
* presečišče seznamov (list1 * list2)
+ seštevanje seznamov (list1 + list2)

& (logični IN) - združuje sezname po ujemanju (list1 & list2), podobno presečišču (list1 * list2)
| (logični ALI) - združuje sezname po širšem iskanju (list1 | list2)

S seznami ne deluje:  ^  &&  ||  %  \/ 

Vsi najdeni elementi

V okviru skeniranega jezika lahko pridobite seznam vseh elementov, ki jih je določil Checkmarx (vrstice, funkcije, razredi, metode itd.). To je določeno območje objektov, do katerega lahko dostopate preko Vse. To pomeni, da za iskanje objekta s specifičnim imenom searchMe, lahko izvedete iskanje, na primer, po imenu med vsemi najdenimi objekti:

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

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

Če pa potrebujete iskanje v drugem jeziku, ki iz kakršnega koli razloga ni bil vključen v skeniranje (na primer groovy v Android projektu), lahko razširite naše območje objektov preko spremenljivke:

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

Funkcije za analizo Flow

Te funkcije se uporabljajo v mnogih pravilih in tukaj je mala pripomoček, kar pomenijo:

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

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

Pridobivanje imena/poti datoteke

Obstaja nekaj atributov, ki jih lahko pridobite iz rezultatov izvajanja poizvedbe (ime datoteke, v kateri je bilo najdeno ujemanje, vrstica itd.), vendar kako jih pridobiti in uporabiti v dokumentaciji ni navedeno. No, da bi to storili, je treba dostopati do lastnosti LinePragma, in znotraj nje bodo objekti, ki jih potrebujemo:

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

Treba je imeti v mislih, da ImeDatoteke dejansko vsebuje pot do datoteke, saj smo uporabili metodo GetFirstGraph.

Rezultat izvedbe

Znotraj CxQL je predvidena posebna spremenljivka result, ki vrne rezultat izvajanja vašega napisanega pravila. Inicializirana je takoj in vanjo lahko shranjujete vmesne rezultate, jih spreminjate in natančite v postopku dela. Vendar, če v pravilu ne bo dodelitve te spremenljivke ali funkcije return— bo rezultat izvajanja vedno ničelni.

Naslednji poizvedba nam ne bo vrnila ničesar in bo vedno prazna:

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

Toda, po tem ko dodelimo rezultat izvajanja čarovniški spremenljivki result — bomo videli, kaj nam vrne ta klic:

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

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

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

Uporaba rezultatov izvedbe drugih pravil

Pravila v Checkmarx lahko imenujemo ekvivalenti funkcij v običajnem programskem jeziku. Pri pisanju pravila lahko povsem uporabite rezultate drugih poizvedb. Na primer, ni nujno, da vsakič iščete vse klice metod v kodu, dovolj je, da pokličete potrebno pravilo:

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

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

Tak pristop omogoča zmanjšanje kode in znatno skrajša čas izvajanja pravila.

Reševanje težav

Zapisovanje

Pri delu z orodjem včasih ni mogoče takoj napisati potrebne poizvedbe in je treba eksperimentirati ter preizkusiti različne možnosti. V takem primeru je v orodju predvideno beleženje, ki ga pokličemo na naslednji način:

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

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

Vendar pa je treba upoštevati, da ta metoda sprejema le niz, zato ne bo mogoče izdati celotnega seznama najdenih elementov kot rezultat prve operacije. Druga možnost, ki se uporablja za odpravljanje napak — je občasno dodeliti čarovniški spremenljivki result rezultat izvajanja poizvedbe in gledati, kaj se zgodi. Tak pristop ni najbolj priročen, potrebno je biti prepričan, da v kodi kasneje ni prepisov ali operacij s tem result ali pa preprosto komentirati spodnjo kodo. Lahko pa, tako kot jaz, pozabite odstraniti več takih klicev iz pripravljenega pravila in se sprašujete, zakaj nič ne deluje.

Bolj priročen način — je poklicati metodo return s potrebnim parametrom. V takem primeru se bo izvajanje pravila zaključilo in lahko bomo videli, kaj smo dobili kot rezultat našega dela:

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

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

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

Težava z prijavo

Obstajajo situacije, ko ni mogoče dostopati do orodja CxAudit (ki se uporablja za pisanje pravil). Razlogov za to je lahko veliko: nenadno zavrsitev dela, nepričakovana posodobitev Windows, BSOD in druge nepredvidene situacije, ki nam niso pod nadzorom. V takem primeru včasih ostane nedokončana seja v bazi podatkov, ki preprečuje ponoven dostop. Za odpravo težave je potrebno izvesti več poizvedb:

Za Checkmarx do 8.6:

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

Za Checkmarx po 8.6:

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

Pisanje pravil

In končno smo prišli do najbolj zanimivega. Ko začnete pisati pravila v CxQL, pogosto primanjkuje ne toliko dokumentacije, kot bolj žive primere rešitev določenih nalog in opis procesa dela poizvedb kot celote.

Poskušal bom nekoliko olajšati življenje tistim, ki se začnejo poglabljati v jezik poizvedb, in predložil bom nekaj primerov uporabe Custom Queries za reševanje določenih nalog. Nekateri od njih so dovolj splošni in se lahko uporabljajo v vašem podjetju skoraj brez sprememb, drugi so bolj specifični, a jih je tudi mogoče uporabiti, če prilagodite kodo svoji aplikaciji.

Torej, s katerimi nalogami smo se največkrat srečevali:

Naloga: V rezultatih izvajanja pravila je več Flow-ov, eden od njih pa je vključen v drugega; treba je obdržati enega od njiju.

Rešenje: Res je, včasih Checkmarx prikaže več Flow-ov gibanja podatkov, ki se lahko prekrivajo in so skrajšana različica drugih. Za takšne primere obstaja posebna metoda ReduceFlow. Odvisno od parametra, bo izbral najkrajši ali najdaljši Flow:

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

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

Naloga: Razširiti seznam občutljivih podatkov, na katere orodje reagira

Rešenje: V Checkmarxu obstajajo osnovna pravila, katerih rezultati se uporabljajo v mnogih drugih poizvedbah. Z dopolnitev nekaterih od teh pravil z podatki, specifičnimi za vašo aplikacijo, lahko takoj izboljšate rezultate skeniranja. Spodaj je primer pravila, s katerega lahko začnete:

General_privacy_violation_list

Dodajmo nekaj spremenljivk, ki se v naši aplikaciji uporabljajo za shranjevanje občutljivih informacij:

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

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

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

Naloga: Razširiti seznam spremenljivk s gesli

Rešenje: Priporočam, da takoj upoštevate osnovno pravilo določanja gesel v kodi in dodate seznam imen spremenljivk, ki jih vaša podjetja običajno uporabljajo.

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

// Dopolnimo privzeti seznam spremenljivk
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);
	}
}

Naloga: Dodajte uporabljene ogrodja, ki jih Checkmarx ne podpira.

Rešenje: Vse poizvedbe v Checkmarxu so razdeljene po jezikih, zato je potrebno dopolniti pravila za vsak jezik. Spodaj je nekaj primerov takih pravil.

Če uporabljate knjižnice, ki dopolnjujejo ali nadomeščajo standardne funkcionalnosti — jih je enostavno dodati v osnovno pravilo. Takrat bodo vsi, ki jih uporabljajo, takoj obveščeni o novih določilih. Na primer, knjižnice za zapisovanje v Androidu — Timber in Loggi. V osnovni zbirki pravil za določanje nesistemskih klicev ni, zato, če geslo ali identifikator seje pride v zapis, o tem ne bomo izvedeli. Poskusimo dodati v pravila Checkmarx določanje takih metod.

Testni primer kode, ki uporablja knjižnico Timber za zapisovanje:

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

Aici se află un exemplu de cerere pentru Checkmarx, care va permite adăugarea definiției apelurilor metodei 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, se poate completa regula învecinată, dar deja referitoare la jurnalizarea î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 muncă asincronă, ar fi bine să comunicăm acest lucru 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());

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

Rešenje: Adesea, pentru stocarea diferitelor variabile și valori în iOS se folosesc fișiere speciale cu extensia .plist. Stocarea parolelor, tokenilor, cheilor și altor date sensibile în aceste fișiere nu este recomandată, deoarece acestea pot fi extrase fără probleme din dispozitiv.

Fișierele plist au caracteristici care nu sunt evidente la o privire neavizată, dar care sunt importante pentru Checkmarx. Vom scrie o regulă care va căuta datele dorite și ne va informa dacă apar undeva parole sau tokeni.

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

DeviceDictionary
	
		phone
		iPhone 6s
		
	privatekey
	MIICXAIBAAKBgQCqGKukO1De7zhZj6+

Și regula pentru Checkmarx, în care sunt câteva nuanțe care 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);

Naloga: Căutarea informațiilor în XML

Rešenje: În Checkmarx există funcții foarte convenabile pentru lucrul cu XML și căutarea valorilor, tag-urilor, atributelor și altor elemente. Dar în documentație, din păcate, a fost o eroare care face ca niciun exemplu să nu funcționeze. Chiar dacă acest neajuns a fost corectat în ultima versiune a documentației — fiți atenți dacă folosiți versiuni anterioare ale documentelor.

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

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

Ca rezultat al încercării de execuție, vom obține o eroare, că Vse ne obstaja taka metoda... In to je res, saj za uporabo funkcij za delo z XML obstaja posebno, ločeno objektno prostor - cxXPath. Tako izgleda pravilen poizvedbo za iskanje nastavitve v Androidu, ki dovoljuje uporabo HTTP prometa:

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

Podrobneje si poglejmo, saj je sintaksa pri vseh funkcijah podobna; ko se enkrat spoznaš z eno, je naprej potrebno le izbrati pravo. Torej, zaporedoma po parametrih:

  • "*.xml"— maska datotek, po katerih je potrebno izvesti iskanje

  • 8 — id jezika, za katerega velja pravilo

  • "cleartextTrafficPermitted"— ime atributa v xml

  • "true" — vrednost tega atributa

  • false — uporaba regularnih izrazov pri iskanju

  • true — pomeni, da bo iskanje opravljeno ob ignoriranju velike in male črke, torej brez razlikovanja med velikimi in malimi črkami

Kot primer je uporabljeno pravilo, ki določa nepravilne nastavitve omrežnega povezovanja v Androidu, ki dovoljujejo komunikacijo s strežnikom prek protokola HTTP. Primer nastavitve, ki vsebuje atribut cleartextTrafficPermitted z vrednostjo true:

example.com
        
            
        
        
            secure.example.com

Naloga: Omejite rezultate po imenu/poti do datoteke

Rešenje: V enem od večjih projektov, povezanih z razvojem mobilne aplikacije za Android, smo se srečali z lažnimi sprožitvami pravila, ki določa nastavitev obfuskacije. Gre za to, da pravilo iz škatle išče v datoteki build.gradle nastavitev, ki je odgovorna za uporabo pravil obfuskacije za končno različico aplikacije.

Vendar se v večjih projektih včasih pojavijo poddatoteke, build.gradle, ki so povezane z knjižnicami, vključenimi v projekt. Značilnost je, da tudi če v teh datotekah ni navedeno, da je obfuskacija potrebna, bodo ob compiliranju veljala pravila osnovne datoteke.

Tako je naloga omejiti sprožitve v poddatotekah, ki se nanašajo na knjižnice. Določiti jih je mogoče po prisotnosti vrstice apply 'com.android.library'.

Primer kode iz datoteke build.gradle, ki določa potrebo po obfuskaciji:

apply plugin: 'com.android.application'

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

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

dependencies {
  ...
}

Primer datoteke build.gradle za biblioteku uklučenu u projekat i koja nema tu postavku:

apply plugin: 'android-library'

dependencies {
  compile 'com.android.support:support-v4:18.0.+'
}

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

I pravilo za 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);
		}
	}
}

Ovaj pristup može biti dovoljno univerzalan i može se koristiti ne samo za Android aplikacije, već i za druge slučajeve kada je potrebno odrediti pripadnost rezultata određenoj datoteci.

Naloga: Dodati podršku za spoljnu biblioteku, ako sintaksa nije potpuno podržana

Rešenje: Količina raznih frejmova koji se koriste u procesu pisanja koda je jednostavno ogromna. Naravno, Checkmarx ne zna uvek za njihovo postojanje i naš zadatak je da ga naučimo da razume da se određene metode odnose upravo na taj frejm. Ponekad to otežava činjenica da frejmovi koriste nazive funkcija koje su veoma rasprostranjene i ne može se jednoznačno odrediti pripadnost određenog poziva konkretnoj biblioteci.

Težina leži u tome što sintaksa takvih biblioteka ne uvek pravilno prepoznaje i često je potrebno eksperimentisati da se ne dobije velika količina lažnih alarma. Postoji nekoliko opcija za poboljšanje tačnosti skeniranja i rešavanje postavljenog zadatka:

  • Prva opcija je da znamo da se biblioteka koristi u određenom projektu i možemo primeniti pravilo na nivou tima. Međutim, u slučaju da tim odluči da koristi drugi pristup ili koristi više biblioteka u kojima se prelamaju nazivi funkcija, možemo dobiti neprijatnu sliku zbog brojnih lažnih alarma.

  • Druga opcija je primeniti pretragu po datotekama u kojima se jasno vrši uvoz biblioteke. Ovim pristupom možemo biti sigurni da se u toj datoteci koristi potrebna nam biblioteka.

  • A treća opcija je korišćenje oba prethodna pristupa zajedno.

Kao primer razmotrimo poznatu biblioteku u uskim krugovima slick za programski jezik Scala, a naime, funkcionalnost Splicing Literal ValuesВ общем случае, для передачи параметров в SQL-запрос необходимо использовать оператор $, который подставляет данные в предварительно сформированный SQL-запрос. То есть, по факту является прямым аналогом Prepared Statement в Java. Но, в случае необходимости динамически конструировать SQL-запрос, например, если нужно передавать имена таблиц, возможно использовать оператор #$, который напрямую подставит данные в запрос (практически, как конкатенация строк).

Пример кода:

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

Checkmarx пока не умеет определять использование Splicing Literal Values и пропускает операторы #$, так что попробуем научить его определять потенциальные SQL-инъекции и подсвечивать нужные места в коде:

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

Naloga: Поиск используемых уязвимых функций в Open-Source библиотеках

Rešenje: Во многих компаниях используются инструменты для контроля Open-Source (практика OSA), позволяющие обнаружить использование уязвимых версий библиотек в разрабатываемых приложениях. Иногда обновить такую библиотеку до безопасной версии не представляется возможным. В каких-то случаях есть функциональные ограничения, в других безопасной версии и вовсе нет. В таком случае поможет комбинация практик SAST и OSA, позволяющая определить, что функции, которые приводят к эксплуатации уязвимости, не используются в коде.

Но иногда, особенно, если рассматривать JavaScript, это может быть не совсем тривиальной задачей. Ниже представлено решение, возможно не идеальное, но тем не менее работающее, на примере уязвимостей в компоненте lodash в методах template in *set.

Примеры тестового потенциально уязвимого кода в 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!'

И при подключении напрямую в 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>

Ищем все наши уязвимые методы, которые перечислены в уязвимостях:

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

Naloga: Поиск зашитых в приложение сертификатов

Rešenje: Нередко приложения, особенно мобильные, используют сертификаты или ключи для доступа к различным серверам или проверки SSL-Pinning. Если смотреть с точки зрения безопасности — хранить такие вещи в коде не самая лучшая практика. Попробуем написать правило, которое будет искать подобные файлы в репозитории:

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

Naloga: Поиск скомпрометированных токенов в приложении

Rešenje: Pogosto je potrebno umakniti kompromitirane žetone ali druge pomembne informacije, ki so prisotne v kodi. Seveda ni najboljša ideja, da jih hranimo znotraj izvorne kode, a situacije so različne. Zahvaljujoč CxQL poizvedbam je iskanje takšnih stvari dovolj preprosto:

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

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

Zaključek

Upam, da bo ta članek koristen tistim, ki se srečujejo z orodjem Checkmarx. Morda tudi tisti, ki že dolgo pišejo svoje pravila, najdejo kaj uporabnega v tem vodniku.

Na žalost trenutno zelo primanjkuje virov, kjer bi bilo mogoče črpati nove ideje pri razvoju pravil za Checkmarx. Zato smo ustvarili repozitorij na Githubu, kjer bomo objavljali svoje prispevke, da bi vsakdo, ki uporablja CxQL, lahko našel kaj koristnega, prav tako pa bi imel priložnost, da deli svoje delo s skupnostjo. Repozitorij je v procesu izpolnjevanja in strukturiranja vsebine, zato so prispevki dobrodošli!

Hvala za pozornost!

Izvor: habr.com

Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki 🔥 Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki | ProHoster