Ako napísať pravidlá pre Checkmarx a nezblázniť sa

Čau Habr!

Pri našej práci sa naša spoločnosť veľmi často zaoberá rôznymi nástrojmi na analýzu statického kódu (SAST). Po vybalení všetky fungujú priemerne. Samozrejme, všetko závisí od projektu a technológií v ňom použitých, ako aj od toho, ako dobre tieto technológie pokrývajú pravidlá analýzy. Podľa môjho názoru je jedným z najdôležitejších kritérií pri výbere nástroja SAST schopnosť prispôsobiť ho špecifikám vašich aplikácií, konkrétne písať a meniť pravidlá analýzy alebo, ako sa častejšie nazývajú, vlastné dotazy.

Ako napísať pravidlá pre Checkmarx a nezblázniť sa

Najčastejšie používame Checkmarx - veľmi zaujímavý a výkonný analyzátor kódu. V tomto článku budem hovoriť o svojich skúsenostiach s písaním pravidiel analýzy.

obsah

Vstup

Na začiatok by som vám rád odporučil jeden z mála článkov v ruštine o funkciách písania dopytov pre Checkmarx. Vyšlo na Habré koncom roka 2019 pod názvom: "Ahoj, Checkmarx!" Ako napísať dotaz Checkmarx SAST a nájsť skvelé zraniteľnosti.

Podrobne skúma, ako napísať prvé dotazy v jazyku CxQL (Checkmarx Query Language) pre niektoré testovacie aplikácie a ukazuje základné princípy fungovania pravidiel analýzy.

Nebudem opakovať to, čo je v ňom popísané, aj keď niektoré križovatky budú stále prítomné. Vo svojom článku sa pokúsim zostaviť akúsi „zbierku receptov“, zoznam riešení konkrétnych problémov, s ktorými som sa počas práce s Checkmarxom stretol. Nad mnohými z týchto problémov som si musel polámať hlavu. Niekedy v dokumentácii nebolo dostatok informácií a niekedy bolo dokonca ťažké pochopiť, ako urobiť to, čo bolo potrebné. Dúfam, že moje skúsenosti a bezsenné noci nebudú márne a táto „zbierka receptov Custom Queries“ vám ušetrí pár hodín alebo pár nervových buniek. Takže, začnime!

Všeobecné informácie o pravidlách

Najprv sa pozrime na niekoľko základných pojmov a proces práce s pravidlami, aby sme lepšie pochopili, čo sa bude diať ďalej. A tiež preto, že dokumentácia o tom nič nehovorí alebo je veľmi rozložená v štruktúre, čo nie je príliš pohodlné.

  1. Pravidlá sa použijú počas kontroly v závislosti od prednastavenia vybratého pri spustení (súbor aktívnych pravidiel). Môžete vytvoriť neobmedzený počet predvolieb a presne to, ako ich štruktúrovať, závisí od špecifík vášho procesu. Môžete ich zoskupiť podľa jazyka alebo vybrať predvoľby pre každý projekt. Počet aktívnych pravidiel ovplyvňuje rýchlosť a presnosť skenovania.

    Ako napísať pravidlá pre Checkmarx a nezblázniť saNastavenie predvoľby v rozhraní Checkmarx

  2. Pravidlá sa upravujú v špeciálnom nástroji CxAuditor. Toto je desktopová aplikácia, ktorá sa pripája k serveru so systémom Checkmarx. Tento nástroj má dva režimy činnosti: úprava pravidiel a analýza výsledkov už vykonanej kontroly.

    Ako napísať pravidlá pre Checkmarx a nezblázniť saRozhranie CxAudit

  3. Pravidlá v Checkmarx sú rozdelené podľa jazyka, to znamená, že každý jazyk má svoju vlastnú sadu dotazov. Existujú aj niektoré všeobecné pravidlá, ktoré platia bez ohľadu na jazyk, ide o takzvané základné dotazy. Základné dotazy väčšinou zahŕňajú vyhľadávanie informácií, ktoré používajú iné pravidlá.

    Ako napísať pravidlá pre Checkmarx a nezblázniť saRozdelenie pravidiel podľa jazyka

  4. Pravidlá sú „spustiteľné“ a „nespustiteľné“ (vykonané a nespustené). Podľa mňa to nie je úplne správny názov, ale je to tak. Pointa je, že výsledok spustenia „Spustiteľných“ pravidiel sa zobrazí vo výsledkoch kontroly v používateľskom rozhraní a „Nespustiteľné“ pravidlá sú potrebné iba na použitie ich výsledkov v iných požiadavkách (v skutočnosti sú to len funkcie ).

    Ako napísať pravidlá pre Checkmarx a nezblázniť saUrčenie typu pravidla pri vytváraní

  5. Môžete vytvoriť nové pravidlá alebo doplniť/prepísať existujúce. Ak chcete prepísať pravidlo, musíte ho nájsť v strome, kliknúť pravým tlačidlom myši a z rozbaľovacej ponuky vybrať možnosť „Prepísať“. Tu je dôležité pamätať na to, že nové pravidlá nie sú pôvodne zahrnuté v predvoľbách a nie sú aktívne. Aby ste ich mohli začať používať, musíte ich aktivovať v menu „Preset Manager“ v prístroji. Prepísané pravidlá si zachovajú svoje nastavenia, to znamená, že ak bolo pravidlo aktívne, zostane tak a okamžite sa použije.

    Ako napísať pravidlá pre Checkmarx a nezblázniť saPríklad nového pravidla v rozhraní Správca predvolieb

  6. Počas vykonávania sa vytvorí „strom“ požiadaviek, ktorý závisí od toho, čo. Pravidlá, ktoré zhromažďujú informácie, sa vykonávajú ako prvé a potom tie, ktoré ich používajú. Výsledok vykonania sa ukladá do vyrovnávacej pamäte, takže ak je možné použiť výsledky existujúceho pravidla, je lepšie to urobiť, skráti sa tým čas skenovania.

  7. Pravidlá je možné aplikovať na rôznych úrovniach:

  • Pre celý systém - použije sa na akékoľvek skenovanie akéhokoľvek projektu

  • Na úrovni tímu – použije sa len na skenovanie projektov vo vybranom tíme.

  • Na úrovni projektu - Bude aplikovaný v konkrétnom projekte

    Ako napísať pravidlá pre Checkmarx a nezblázniť saUrčenie úrovne, na ktorej sa bude pravidlo uplatňovať

"Slovník" pre začiatočníkov

A začnem niekoľkými vecami, ktoré mi spôsobili otázky, a tiež ukážem niekoľko techník, ktoré výrazne zjednodušia život.

Operácie so zoznamami

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

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

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

Všetky nájdené položky

V naskenovanom jazyku môžete získať zoznam úplne všetkých prvkov, ktoré Checkmarx identifikoval (reťazce, funkcie, triedy, metódy atď.). Toto je priestor objektov, cez ktorý je možné pristupovať All. Teda hľadať objekt s konkrétnym názvom searchMe, môžete vyhľadávať napríklad podľa názvu vo všetkých nájdených objektoch:

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

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

Ak však potrebujete hľadať v inom jazyku, ktorý z nejakého dôvodu nebol zahrnutý do skenovania (napríklad groovy v projekte Android), môžete rozšíriť náš priestor objektov prostredníctvom premennej:

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

Funkcie pre analýzu toku

Tieto funkcie sa používajú v mnohých pravidlách a tu je malý cheat, čo znamenajú:

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

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

Získava sa názov súboru/cesta

Existuje niekoľko atribútov, ktoré možno získať z výsledkov dotazu (názov súboru, v ktorom bol záznam nájdený, reťazec atď.), ale dokumentácia neuvádza, ako ich získať a použiť. Aby ste to mohli urobiť, musíte získať prístup k vlastnosti LinePragma a objekty, ktoré potrebujeme, budú umiestnené v nej:

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

Stojí za to mať to na pamäti FileName obsahuje vlastne cestu k súboru, keďže sme použili metódu GetFirstGraph.

Výsledok popravy

V CxQL je špeciálna premenná result, ktorý vráti výsledok vykonania vášho napísaného pravidla. Okamžite sa inicializuje a môžete do nej zapisovať medzivýsledky, meniť a dolaďovať ich počas práce. Ak však v pravidle neexistuje žiadne priradenie k tejto premennej alebo funkcii return— výsledok vykonania bude vždy nula.

Nasledujúci dotaz nám v dôsledku vykonania nič nevráti a vždy bude prázdny:

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

Ale po priradení výsledku vykonania k magickej premennej výsledok uvidíme, čo nám toto volanie vráti:

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

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

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

Použitie výsledkov iných pravidiel

Pravidlá v Checkmarxe možno nazvať analogicky k funkciám v bežnom programovacom jazyku. Pri písaní pravidla môžete použiť výsledky iných dopytov. Napríklad nie je potrebné zakaždým hľadať všetky volania metód v kóde, stačí zavolať požadované pravidlo:

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

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

Tento prístup vám umožňuje skrátiť kód a výrazne skrátiť čas vykonania pravidla.

riešenie problémov

Ťažba dreva

Pri práci s nástrojom niekedy nie je možné okamžite napísať požadovaný dotaz a musíte experimentovať a skúšať rôzne možnosti. V takom prípade nástroj poskytuje protokolovanie, ktoré sa nazýva takto:

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

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

Je však potrebné pripomenúť, že táto metóda prijíma iba ako vstup reťazec, takže v dôsledku prvej operácie nebude možné zobraziť úplný zoznam nájdených prvkov. Druhou možnosťou, ktorá sa používa na ladenie, je čas od času priradiť k magickej premennej result výsledok dotazu a uvidíte, čo sa stane. Tento prístup nie je príliš pohodlný; musíte si byť istí, že v nasledujúcom kóde nie sú žiadne prepísania alebo operácie result alebo jednoducho okomentujte kód nižšie. Alebo môžete, ako ja, zabudnúť odstrániť niekoľko takýchto hovorov z hotového pravidla a čudovať sa, prečo nič nefunguje.

Pohodlnejším spôsobom je zavolať metódu return s požadovaným parametrom. V tomto prípade sa vykonávanie pravidla skončí a my budeme môcť vidieť, čo sa stalo v dôsledku toho, čo sme napísali:

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

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

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

Problém s prihlásením

Sú situácie, keď nemáte prístup k nástroju CxAudit (ktorý sa používa na písanie pravidiel). Môže to mať mnoho príčin, vrátane zlyhaní, náhlych aktualizácií systému Windows, BSOD a iných nepredvídaných situácií, ktoré sú mimo našej kontroly. V tomto prípade sa niekedy v databáze vyskytne nedokončená relácia, ktorá vám bráni v opätovnom prihlásení. Ak to chcete opraviť, musíte spustiť niekoľko dotazov:

Pre Checkmarx pred 8.6:

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

Pre Checkmarx po 8.6:

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

Pravidlá písania

Teraz sa dostávame k najzaujímavejšej časti. Keď začnete písať pravidlá v CxQL, to, čo vám často chýba, nie je ani tak dokumentácia, ako skôr živé príklady riešenia určitých problémov a popis procesu fungovania dopytov vo všeobecnosti.

Pokúsim sa trochu uľahčiť život tým, ktorí sa začínajú ponárať do dopytovacieho jazyka a uvediem niekoľko príkladov použitia Custom Queries na riešenie určitých problémov. Niektoré z nich sú dosť všeobecné a dajú sa vo vašej firme použiť prakticky bez zmien, iné sú konkrétnejšie, ale dajú sa využiť aj zmenou kódu tak, aby vyhovovali špecifikám vašich aplikácií.

Takže, tu sú problémy, s ktorými sme sa najčastejšie stretávali:

cieľ: Vo výsledkoch vykonania pravidla je niekoľko tokov a jeden z nich je vnorením iného, ​​jeden z nich musíte opustiť.

riešenie: V skutočnosti niekedy Checkmarx zobrazuje niekoľko dátových tokov, ktoré sa môžu prekrývať a byť skrátenou verziou iných. Pre takéto prípady existuje špeciálna metóda ReduceFlow. V závislosti od parametra vyberie najkratší alebo najdlhší prietok:

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

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

cieľ: Rozšírte zoznam citlivých údajov, na ktoré nástroj reaguje

riešenie: Checkmarx má základné pravidlá, ktorých výsledky využívajú mnohé ďalšie dotazy. Doplnením niektorých z týchto pravidiel o údaje špecifické pre vašu aplikáciu môžete okamžite zlepšiť výsledky kontroly. Nižšie je uvedený príklad pravidla, ako začať:

Všeobecný_zoznam_porušení_ochrany súkromia

Pridajme niekoľko premenných, ktoré sa v našej aplikácii používajú na ukladanie citlivých informácií:

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

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

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

cieľ: Rozšírte zoznam premenných pomocou hesiel

riešenie: Odporúčal by som okamžite venovať pozornosť základnému pravidlu pre definovanie hesiel v kóde a doplniť ho o zoznam názvov premenných, ktoré sa bežne používajú vo vašej spoločnosti.

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

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

cieľ: Pridajte použité rámce, ktoré Checkmarx nepodporuje

riešenie: Všetky dotazy v Checkmarx sú rozdelené podľa jazyka, takže musíte pridať pravidlá pre každý jazyk. Nižšie uvádzame niekoľko príkladov takýchto pravidiel.

Ak sa používajú knižnice, ktoré dopĺňajú alebo nahrádzajú štandardnú funkcionalitu, možno ich jednoducho pridať k základnému pravidlu. Potom sa každý, kto ho používa, okamžite dozvie o nových úvodoch. Napríklad knižnice na prihlasovanie v systéme Android sú Timber a Loggi. V základnom balíku neexistujú žiadne pravidlá na identifikáciu nesystémových volaní, takže ak sa do protokolu dostane heslo alebo identifikátor relácie, nebudeme o tom vedieť. Pokúsme sa pridať definície takýchto metód do pravidiel Checkmarx.

Príklad testovacieho kódu, ktorý používa knižnicu Timber na protokolovanie:

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

A tu je príklad požiadavky na Checkmarx, ktorá vám umožní pridať definíciu volania metód dreva ako výstupného bodu pre dáta z aplikácie:

Nájsť AndroidOutputs

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

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

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

A môžete tiež pridať k susednému pravidlu, ale toto sa týka priamo prihlásenia v systéme Android:

Nájdite AndroidLog_Outputs

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

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

Tiež, ak používajú aplikácie pre Android WorkManager pre asynchrónnu prácu je dobré o tom dodatočne informovať Checkmarxa pridaním metódy na získanie údajov z úlohy getInputData:

Nájsť AndroidRead

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

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

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

cieľ: Vyhľadávanie citlivých údajov v plist pre projekty iOS

riešenie: iOS často používa špeciálne súbory s príponou .plist na ukladanie rôznych premenných a hodnôt. Ukladanie hesiel, tokenov, kľúčov a iných citlivých údajov v týchto súboroch sa neodporúča, pretože ich možno zo zariadenia bez problémov extrahovať.

Súbory Plist majú funkcie, ktoré nie sú viditeľné voľným okom, ale sú dôležité pre Checkmarx. Napíšme si pravidlo, ktoré vyhľadá údaje, ktoré potrebujeme, a povie nám, či sú niekde uvedené heslá alebo tokeny.

Príklad takéhoto súboru, ktorý obsahuje token na komunikáciu s backendovou službou:

<?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>

A pravidlo pre Checkmarx, ktoré má niekoľko nuancií, ktoré by sa mali brať do úvahy pri písaní:

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

cieľ: Hľadanie informácií v XML

riešenie: Checkmarx má veľmi pohodlné funkcie na prácu s XML a vyhľadávanie hodnôt, značiek, atribútov a pod. Bohužiaľ, v dokumentácii sa vyskytla chyba, kvôli ktorej nefunguje ani jeden príklad. Napriek tomu, že táto chyba bola v najnovšej verzii dokumentácie odstránená, buďte opatrní, ak používate staršie verzie dokumentov.

Tu je nesprávny príklad z dokumentácie:

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

V dôsledku pokusu o vykonanie dostaneme chybu, ktorá All taká metóda neexistuje... A to je pravda, keďže existuje špeciálny, oddelený priestor objektov na použitie funkcií na prácu s XML - cxXPath. Takto vyzerá správny dotaz na nájdenie nastavenia v systéme Android, ktoré umožňuje používanie prenosu HTTP:

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

Pozrime sa na to trochu podrobnejšie, keďže syntax pre všetky funkcie je podobná, potom, čo ste na jednu prišli, stačí si vybrať tú, ktorú potrebujete. Takže postupne podľa parametrov:

  • "*.xml"— maska ​​súborov, ktoré sa majú prehľadávať

  • 8 — ID jazyka, pre ktorý sa pravidlo uplatňuje

  • "cleartextTrafficPermitted"— názov atribútu v xml

  • "true" — hodnotu tohto atribútu

  • false — použitie regulárneho výrazu pri vyhľadávaní

  • true — znamená, že vyhľadávanie sa vykoná bez ohľadu na malé a veľké písmená, to znamená, že sa nerozlišujú malé a veľké písmená

Ako príklad sme použili pravidlo, ktoré identifikuje z bezpečnostného hľadiska nesprávne nastavenia sieťového pripojenia v systéme Android, ktoré umožňujú komunikáciu so serverom prostredníctvom protokolu HTTP. Príklad nastavenia obsahujúceho atribút cleartextTrafficPermitted so zmyslom 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>

cieľ: Obmedzte výsledky podľa názvu súboru/cesty

riešenie: V jednom z veľkých projektov súvisiacich s vývojom mobilnej aplikácie pre Android sme narazili na falošné pozitíva pravidla, ktoré určuje nastavenie zahmlievania. Faktom je, že pravidlo mimo krabice vyhľadáva v súbore build.gradle nastavenie zodpovedné za uplatňovanie pravidiel zahmlievania pre verziu aplikácie.

Ale vo veľkých projektoch niekedy existujú podradené súbory build.gradle, ktoré odkazujú na knižnice zahrnuté v projekte. Zvláštnosťou je, že aj keď tieto súbory neindikujú potrebu zahmlievania, pri kompilácii sa aplikujú nastavenia súboru rodičovskej zostavy.

Úlohou je teda odrezať spúšťače v podriadených súboroch, ktoré patria do knižníc. Možno ich identifikovať podľa prítomnosti čiary apply 'com.android.library'.

Príklad kódu zo súboru build.gradle, ktorý určuje potrebu zahmlievania:

apply plugin: 'com.android.application'

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

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

dependencies {
  ...
}

Príklad súboru build.gradle pre knižnicu zahrnutú v projekte, ktorá nemá toto nastavenie:

apply plugin: 'android-library'

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

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

A pravidlo pre Checkmarxa:

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

Tento prístup môže byť celkom univerzálny a užitočný nielen pre aplikácie pre Android, ale aj pre iné prípady, keď potrebujete určiť, či výsledok patrí konkrétnemu súboru.

cieľ: Pridajte podporu pre knižnicu tretej strany, ak syntax nie je plne podporovaná

riešenie: Počet rôznych rámcov, ktoré sa používajú v procese písania kódu, je jednoducho mimo tabuľky. Samozrejme, Checkmarx nie vždy vie o ich existencii a našou úlohou je naučiť ho pochopiť, že určité metódy patria špecificky do tohto rámca. Niekedy to komplikuje fakt, že frameworky používajú názvy funkcií, ktoré sú veľmi bežné a nie je možné jednoznačne určiť vzťah konkrétneho volania ku konkrétnej knižnici.

Problém je v tom, že syntax takýchto knižníc nie je vždy správne rozpoznaná a musíte experimentovať, aby ste sa vyhli veľkému počtu falošných poplachov. Existuje niekoľko možností, ako zlepšiť presnosť skenovania a vyriešiť problém:

  • Prvá možnosť, s istotou vieme, že knižnica sa používa v konkrétnom projekte a môže pravidlo aplikovať na tímovej úrovni. Ak sa však tím rozhodne pre iný prístup alebo použije niekoľko knižníc, v ktorých sa názvy funkcií prekrývajú, môžeme získať nie veľmi príjemný obraz o početných falošných pozitívach.

  • Druhou možnosťou je vyhľadávanie súborov, v ktorých je knižnica prehľadne naimportovaná. S týmto prístupom si môžeme byť istí, že knižnica, ktorú potrebujeme, je presne použitá v tomto súbore.

  • A tretia možnosť je použiť dva vyššie uvedené prístupy spolu.

Ako príklad sa pozrime na knižnicu, ktorá je v úzkych kruhoch známa úhľadný pre programovací jazyk Scala, konkrétne funkčnosť Spájanie doslovných hodnôt. Vo všeobecnosti, ak chcete zadať parametre do SQL dotazu, musíte použiť operátor $, ktorý nahrádza údaje do vopred vytvoreného SQL dotazu. V skutočnosti ide o priamu analógiu pripraveného vyhlásenia v jazyku Java. Ak však potrebujete dynamicky zostaviť dotaz SQL, napríklad ak potrebujete zadať názvy tabuliek, môžete použiť operátor #$, ktorý priamo nahradí údaje do dotazu (takmer ako reťazenie reťazcov).

Priklad kód:

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

Checkmarx ešte nevie, ako rozpoznať použitie zostrihových doslovných hodnôt a preskakuje operátorov #$, tak sa ho skúsme naučiť identifikovať potenciálne SQL injekcie a zvýrazniť tie správne miesta v kóde:

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

cieľ: Vyhľadajte použité zraniteľné funkcie v knižniciach Open-Source

riešenie: Mnoho spoločností používa nástroje na monitorovanie Open-Source (prax OSA) na detekciu používania zraniteľných verzií knižníc vo vyvíjaných aplikáciách. Niekedy nie je možné aktualizovať takúto knižnicu na zabezpečenú verziu. V niektorých prípadoch existujú funkčné obmedzenia, v iných neexistuje žiadna bezpečná verzia. V tomto prípade kombinácia postupov SAST a OSA pomôže určiť, že funkcie, ktoré vedú k zneužitiu zraniteľnosti, nie sú v kóde použité.

Ale niekedy, najmä pri zvažovaní JavaScriptu, to nemusí byť úplne triviálna úloha. Nižšie je uvedené riešenie, možno nie ideálne, ale napriek tomu fungujúce, na príklade zraniteľností v komponente lodash v metódach template и *set.

Príklady testovania potenciálne zraniteľného kódu v súbore 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!'

A pri pripájaní priamo v 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>

Hľadáme všetky naše zraniteľné metódy, ktoré sú uvedené v zraniteľnostiach:

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

cieľ: Hľadá sa certifikáty vložené do aplikácie

riešenie: Nie je nezvyčajné, že aplikácie, najmä mobilné, používajú certifikáty alebo kľúče na prístup k rôznym serverom alebo na overenie SSL-Pinning. Z hľadiska bezpečnosti nie je ukladanie takýchto vecí v kóde najlepším postupom. Skúsme napísať pravidlo, ktoré bude hľadať podobné súbory v úložisku:

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

cieľ: Nájdenie kompromitovaných tokenov v aplikácii

riešenie: Často je potrebné odvolať napadnuté tokeny alebo iné dôležité informácie, ktoré sú prítomné v kóde. Samozrejme, ich ukladanie do zdrojového kódu nie je dobrý nápad, ale situácie sa líšia. Vďaka dopytom CxQL je hľadanie takýchto vecí celkom jednoduché:

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

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

Záver

Dúfam, že tento článok bude užitočný pre tých, ktorí sa začínajú zoznamovať s nástrojom Checkmarx. Snáď v tomto návode nájdu niečo užitočné aj tí, ktorí si už dlhšie píšu vlastné pravidlá.

V súčasnosti bohužiaľ chýba zdroj, kde by sa dali získať nové nápady počas vývoja pravidiel pre Checkmarx. Preto sme vytvorili úložisko na Github, kde zverejníme našu prácu, aby si v nej každý, kto používa CxQL, našiel niečo užitočné a zároveň mal možnosť podeliť sa o svoju prácu s komunitou. Úložisko je v procese napĺňania a štruktúrovania obsahu, takže prispievatelia sú vítaní!

Ďakujem vám za pozornosť!

Zdroj: hab.com

Pridať komentár