Ahoj, Habr!
Vo svojej práci sa naša spoločnosť veľmi často zaoberá rôznymi nástrojmi statickej analýzy kódu (SAST). Z krabice fungujú všetky priemerne. Samozrejme, všetko závisí od projektu a technológií, ktoré sa v ňom používajú, ako aj od toho, ako dobre sú tieto technológie pokryté analýzami. Podľa môjho názoru je jedným z najdôležitejších kritérií pri výbere nástroja SAST možnosť prispôsobiť ho špecifikám svojich aplikácií, t.j. písať a meniť pravidlá analýzy, alebo ako ich častejšie nazývajú, vlastné dotazy.

Najčastejšie používame Checkmarx — veľmi zaujímavý a výkonný analyzátor kódu. V tomto článku sa podelím o svoju skúsenosť so zápisom analýznych pravidiel pre neho.
Obsah
Úvod
Na začiatok by som chcel odporučiť jeden z mála článkov v slovenčine o špecifikách písania dotazov pre Checkmarx. Bol publikovaný na Habr v konci roka 2019 pod názvom: .
V ňom sa podrobne skúma, ako napísať prvé dotazy v jazyku CxQL (Checkmarx Query Language) pre niektorú testovaciu aplikáciu a sú predstavené základné princípy práce analýznych pravidiel.
Nebudem opakovať to, čo je v ňom opísané, aj keď niektoré prekrytia predsa len budú prítomné. Vo svojom článku sa pokúsim zostaviť akýsi „zborník receptov“, zoznam riešení konkrétnych úloh, s ktorými som sa stretol počas svojej práce s Checkmarx. Nad mnohými z týchto úloh som si musel dobre lámať hlavu. Niekedy chýbali údaje v dokumentácii a inokedy bolo ťažké pochopiť, ako urobiť to, čo sa požaduje. Dúfam, že moja skúsenosť a prebdené noci nebudú zbytočné a tento „zborník receptov vlastných dotazov“ vám ušetrí niekoľko hodín alebo pár nervových buniek. Tak poďme na to!
Všeobecné informácie o pravidlách
Na začiatok si vysvetlíme 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 v dokumentácii to nie je uvedené alebo je to veľmi neprehľadné, čo nie je veľmi pohodlné.
Pravidlá sa uplatňujú pri skenovaní v závislosti od zvoleného predvoľby (súbor aktívnych pravidiel). Predvolieb môžete vytvoriť neobmedzené množstvo, pričom štruktúra závisí od charakteristík vášho procesu. Môžete ich rozdeliť podľa jazykov alebo vytvoriť predvoľby pre každý projekt. Počet aktívnych pravidiel ovplyvňuje rýchlosť a presnosť skenovania.
Nastavenie predvoľby v rozhraní CheckmarxPravidlá sa upravujú v špeciálnom nástroji s názvom CxAuditor. Ide o desktopovú aplikáciu, ktorá sa pripája k serveru s Checkmarx. Tento nástroj má dva režimy: úprava pravidiel a analýza výsledkov už vykonaného skenovania.
Rozhranie CxAuditPravidlá v Checkmarx sú rozdelené podľa jazykov, to znamená, že pre každý jazyk existuje svoj súbor dopytov. Existujú aj niektoré spoločné pravidlá, ktoré sa používajú bez ohľadu na jazyk, to sú tzv. základné dopyty. Väčšinou základné dopyty obsahujú hľadanie informácií, ktoré používajú iné pravidlá.
Rozdelenie pravidiel podľa jazykovPravidlá sú rozdelené na "Executable" a "Non-Executable" (Vykonateľné a Nevykonateľné). Nazval by som to názvom, ktorý nie je úplne presný, ale tak je to. Podstatou je, že výsledok vykonania "Executable" pravidiel sa zobrazí vo výsledkoch skenovania v užívateľskom rozhraní, zatiaľ čo "Non-Executable" pravidlá sú potrebné iba na využívanie ich výsledkov v iných dopytoch (v podstate – len funkcia).
Určenie typu pravidla pri vytváraníMôžete vytvárať nové pravidlá alebo doplniť/prepisovať existujúce. Aby ste prepisovali pravidlo, musíte ho nájsť v strome, kliknúť pravým tlačidlom a z kontextového menu vybrať položku "Override". Tu je dôležité pamätať, že nové pravidlá nie sú z pôvodného štartu aktívne v predvoľbách. Aby ste ich mohli začať používať, musíte ich aktivovať v menu "Preset Manager" v nástroji. Prepísané pravidlá si uchovávajú svoje nastavenia, to znamená, že ak bolo pravidlo aktívne, takým aj zostane a bude aplikované okamžite.
Príklad nového pravidla v rozhraní správcu predvoľiebPočas vykonávania sa vytvára "strom" dotazov, ktorý ukazuje, čo od čoho závisí. Najskôr sa vykonávajú pravidlá, ktoré zbierajú informácie, a potom tie, ktoré ich používajú. Výsledok vykonania sa cachuje, takže ak je možné využiť výsledky existujúceho pravidla, je lepšie to takto urobiť, čo skráti čas skenovania.
Pravidlá je možné uplatniť na rôznych úrovniach:
Pre celý systém — bude použitý na akékoľvek skenovanie akéhokoľvek projektu
Na úrovni tímu (Team) — bude sa uplatňovať iba na skenovanie projektov vo vybranom tíme.
Na úrovni projektu — bude aplikované v konkrétnom projekte
Určenie úrovne, na ktorej sa bude pravidlo aplikovať
„Slovník“ pre začiatočníka
A začnem s niekoľkými vecami, ktoré mi robili otázky, a tiež ukážem niekoľko trikov, ktoré výrazne uľahčia život.
Operácie so zoznamami
- odčítanie jedného od druhého (list2 - list1)
* prienik zoznamov (list1 * list2)
+ sčítanie zoznamov (list1 + list2)
& (logické A) - spája zoznamy podľa zhody (list1 & list2), podobne ako prienik (list1 * list2)
| (logické ALEBO) - spája zoznamy podľa širokého vyhľadávania (list1 | list2)
So zoznamami nefunguje: ^ && || % \/ Všetky nájdené prvky
V rámci skenovaného jazyka je možné získať zoznam všetkých elementov, ktoré určil Checkmarx (riadky, funkcie, triedy, metódy atď.). Toto je určitý priestor objektov, ku ktorému sa dá pristúpiť cez All. Teda pre vyhľadávanie objektu s konkrétnym názvom searchMe, je možné vykonať vyhľadávanie, napríklad podľa mena vo všetkých nájdených objektoch:
// Такой запрос выдаст все элементы
result = All;
// Такой запрос выдаст все элементы, в имени которых присутствует “searchMe“
result = All.FindByName("searchMe");Ak však potrebujete vykonať vyhľadávanie v inom jazyku, ktorý z nejakého dôvodu nebol zahrnutý do skenovania (napríklad groovy v projekte pre Android), môžete rozšíriť náš priestor objektov cez premennú:
result = AllMembers.All.FindByName("searchMe");Funkcie na analýzu Flow
Tieto funkcie sa používajú v mnohých pravidlách a tu je malá pomôcka, čo znamenajú:
// Какие данные second влияют на first.
// Другими словами - ТО (second) что влияет на МЕНЯ (first).
result = first.DataInfluencedBy(second);
// Какие данные first влияют на second.
// Другими словами - Я (first) влияю на ТО (second).
result = first.DataInfluencingOn(second);Získanie názvu/cesty k súboru
Existuje niekoľko atribútov, ktoré je možné získať z výsledkov vykonania dotazu (názov súboru, v ktorom bolo nájdené vyskytovanie, riadok atď.), ale ako ich získať a použiť v dokumentácii sa nepovedalo. Takže na to, aby ste to dosiahli, je potrebné pristúpiť k vlastnosti LinePragma a už vo vnútri nej budú potrebné objekty:
// Для примера найдем все методы
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);Je dobré mať na pamäti, že FileName skutočne obsahuje cestu k súboru, pretože sme použili metódu GetFirstGraph.
Výsledok vykonania
Vnútri CxQL je zabudovaná špeciálna premenná result, ktorá vracia výsledok vykonania vášho napísaného pravidla. Je inicializovaná hneď na začiatku a môžete do nej zapisovať medzi výsledky, meniť a upresňovať ich počas práce. Ale ak v pravidle nebude priradenie tejto premennej alebo funkcie return— výsledok vykonania bude vždy nulový.
Nasledujúci dotaz nám nič nevráti počas vykonania a vždy bude prázdny:
// Находим элементы foo
CxList libraries = All.FindByName("foo");Ale ak priradíme výsledok vykonania k magickej premennej result — uvidíme, čo nám tento volanie vracia:
// Находим элементы foo
CxList libraries = All.FindByName("foo");
// Выводим, как результат выполнения правила
result = libraries
// Или еще короче
result = All.FindByName("foo");Používanie výsledkov vykonania iných pravidiel
Pravidlá v Checkmarx môžeme nazvať analógom funkcií v bežnom programovacom jazyku. Pri písaní pravidla môžete použiť výsledky iných dotazov. Ako príklad, nie je potrebné zakaždým hľadať všetky výzvy metód v kóde, stačí zavolať potrebné pravidlo:
// Получаем результат выполнения другого правила
CxList methods = Find_Methods();
// Ищем внутри метод foo.
// Второй параметр false означает, что ищем без чувствительности к регистру
result = methods.FindByShortName("foo", false);Takýto prístup umožňuje skrátiť kód a výrazne skrátiť čas vykonania pravidla.
Riešenie problémov
Logovanie
Pri práci s nástrojom sa niekedy nedarí hneď napísať potrebný dotaz a musíme experimentovať, skúšať rôzne varianty. Pre takýto prípad je v nástroji predpokladané logovanie, ktoré sa volá nasledovne:
// Находим что-то
CxList toLog = All.FindByShortName("log");
// Формируем строку и отправляем в лог
cxLog.WriteDebugMessage (“number of DOM elements =” + All.Count);Ale treba si pamätať, že tento metóda prijíma iba reťazec, takže sa nedá získať úplný zoznam nájdených prvkov ako výsledok prvej operácie. Druhá varianta, ktorá sa používa na ladenie — je občas priradiť magickej premennej result výsledok vykonania dotazu a pozerať sa, čo vznikne. Tento prístup nie je veľmi pohodlný, treba si byť istý, že v kóde potom nie sú predefinície alebo operácie s týmto result alebo jednoducho komentovať nasledujúci kód. Alebo ako ja, zabudnúť odstrániť z hotového pravidla niekoľko takýchto volaní a diviť sa, prečo nič nefunguje.
Bolo by pohodlnejšie — zavolať metódu return s potrebným parametrom. V takom prípade sa vykonanie pravidla skončí a my uvidíme, čo sa nám podarilo v výsledku toho, čo sme napísali:
// Находим что-то
CxList toLog = All.FindByShortName("log");
// Выводим результат выполнения
return toLog
//Все, что написано дальше не будет выполнено
result = All.DataInfluencedBy(toLog)Problém s prihlásením
Niekedy nastanú situácie, keď sa nedá pripojiť k nástroju CxAudit (ktorý sa používa na písanie pravidiel). Môže to mať množstvo príčin, ako je havarijné ukončenie práce, náhle aktualizovanie Windows, BSOD a iné nepredvídateľné situácie, na ktoré nemáme vplyv. V takom prípade môže zostať nedokončená relácia v databáze, ktorá znemožňuje opätovný prístup. Na opravu je potrebné vykonať niekoľko dotazov:
Pre Checkmarx do 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');Písanie pravidiel
Dostali sme sa k najzaujímavejšej časti. Keď začínate písať pravidlá v CxQL, často chýba nielen dokumentácia, ale skôr živé príklady na riešenie určitých úloh a popis procesu fungovania dotazov ako celku.
Pokúsim sa trochu uľahčiť život tým, ktorí začínajú skúmať jazyk dotazov, a uvediem niekoľko príkladov použitia Custom Queries na riešenie konkrétnych úloh. Niektoré z nich sú dosť všeobecné a dajú sa použiť vo vašej spoločnosti prakticky bez zmien, iné sú špecifickejšie, ale dajú sa tiež použiť, ak zmeníte kód podľa špecifikácie vašich aplikácií.
Takže, tu sú úlohy, s ktorými sme sa najčastejšie stretávali:
Úloha: Vo výsledkoch vykonania pravidla je niekoľko Flow a jeden z nich je vložený do iného, potrebujeme si ponechať jeden z nich.
Riešenie: Naozaj, niekedy Checkmarx zobrazuje niekoľko Flow tokov údajov, ktoré môžu prekryť 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ší Flow:
// Оставить только длинные Flow
result = result.ReduceFlow(CxList.ReduceFlowType.ReduceSmallFlow);
// Оставить только короткие Flow
result = result.ReduceFlow(CxList.ReduceFlowType.ReduceBigFlow);Úloha: Rozšíriť zoznam citlivých údajov, na ktoré reaguje nástroj
Riešenie: V Checkmarx existujú základné pravidlá, ktorých výsledky vykonávania využíva mnoho ďalších dotazov. Dopĺňaním niektorých z takýchto pravidiel údajmi špecifickými pre vašu aplikáciu môžete okamžite zlepšiť výsledky skenovania. Nižšie je príklad pravidla, od ktorého môžete začať:
General_privacy_violation_list
Pridáme niekoľko premenných, ktoré sa používajú v našej aplikácii na uchovávanie citlivých informácií:
// Получаем результат выполнения базового правила
result = base.General_privacy_violation_list();
// Ищем элементы, которые попадают под простые регулярные выражения. Можно дополнить характерными для вас паттернами.
CxList personalList = All.FindByShortNames(new List<string> {
"*securityToken*", "*sessionId*"}, false);
// Добавляем к конечному результату
result.Add(personalList);Úloha: Rozšíriť zoznam premenných s heslami
Riešenie: Odporúčam venovať pozornosť základnému pravidlu pre definovanie hesiel v kóde a pridať k nemu zoznam názvov premenných, ktoré sú v vašej spoločnosti bežné.
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*"));
// Dopĺňame predvolený zoznam premenných
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);
}
}Úloha: Pridať používané rámce, ktoré nie sú podporované Checkmarx
Riešenie: Všetky dotazy v Checkmarx sú rozdelené podľa jazykov, preto je potrebné doplniť pravidlá pre každý jazyk. Nižšie sú uvedené niektoré príklady takýchto pravidiel.
Ak používate knižnice, ktoré dopĺňajú alebo nahrádzajú štandardnú funkcionalitu — je ich ľahké pridať do základného pravidla. Takto sa všetci, ktorí ich používajú — ihneď dozvedia o nových vstupe. Ako príklad, knižnice na logovanie v Androide — Timber a Loggi. V základnej dodávke pravidiel definovania nie sú systémové volania, takže ak sa heslo alebo identifikátor relácie dostane do logu, nebudeme o tom vedieť. Pokúsme sa pridať do pravidiel Checkmarx definície takýchto metód.
Testovací príklad kódu, ktorý používa knižnicu Timber na logovanie:
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("Chybová správa");
Timber.d("Správa na ladenie");
Timber.tag("Iný tag").e("A chybová správa");
}
}Tu je príklad požiadavky pre Checkmarx, ktorá umožní pridať definíciu volania metód Timber ako bod výstupu údajov z aplikácie:
FindAndroidOutputs
// Получаем результат выполнения базового правила
result = base.Find_Android_Outputs();
// Дополняем вызовами, которые приходят из библиотеки Timber
CxList timber = All.FindByExactMemberAccess("Timber.*") +
All.FindByShortName("Timber").GetMembersOfTarget();
// Добавляем к конечному результату
result.Add(timber);A tiež možno doplniť susediace pravidlo, ale už priamo vzťahujúce sa na logovanie v Android:
FindAndroidLog_Outputs
// Получаем результат выполнения базового правила
result = base.Find_Android_Log_Outputs();
// Дополняем вызовами, которые приходят из библиотеки Timber
result.Add(
All.FindByExactMemberAccess("Timber.*") +
All.FindByShortName("Timber").GetMembersOfTarget()
);Ak sa v aplikáciách Android používa na vykonávanie asynchrónnych úloh, je dobré dodatočne informovať Checkmarx pridaním metódy získania údajov z úlohy getInputData:
FindAndroidRead
// Получаем результат выполнения базового правила
result = base.Find_Android_Read();
// Дополняем вызовом функции getInputData, которая используется в WorkManager
CxList getInputData = All.FindByShortName("getInputData");
// Добавляем к конечному результату
result.Add(getInputData.GetMembersOfTarget());Úloha: Hľadanie citlivých údajov v plist pre projekty iOS
Riešenie: Často sa pre ukladanie rôznych premenných a hodnôt v iOS používajú špeciálne súbory s príponou .plist. Ukladanie hesiel, tokenov, kľúčov a iných citlivých údajov v týchto súboroch sa neodporúča, pretože môžu byť bez problémov extrahované z zariadenia.
Súbory plist majú vlastnosti, ktoré nie sú na prvý pohľad zrejmé, ale sú dôležité pre Checkmarx. Napíšeme pravidlo, ktoré bude hľadať potrebné údaje a informovať nás, ak sa niekde spomínajú heslá alebo tokeny.
Príklad takého súboru, v ktorom je zakódovaný token pre komunikáciu so službou backend:
DeviceDictionary
phone
iPhone 6s
privatekey
MIICXAIBAAKBgQCqGKukO1De7zhZj6+A pravidlo pre Checkmarx, v ktorom je niekoľko aspektov, ktoré treba zohľadniť 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);Úloha: Hľadanie informácií v XML
Riešenie: V Checkmarx sú veľmi užitočné funkcie na prácu s XML a hľadanie hodnôt, tagov, atribútov a iného. V dokumentácii však bohužiaľ došlo k chybe, kvôli ktorej žiadny príklad nefunguje. Napriek tomu, že v najnovšej verzii dokumentácie je tento nedostatok 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, že u All neexistuje takáto metóda... A to je správne, pretože na používanie funkcií súvisiacich s prácou s XML existuje špeciálny, oddelený objektový priestor — cxXPath. Toto je správny dotaz na vyhľadanie nastavenia v Androide, ktoré povoluje používanie HTTP prenosu:
// Правильный вариант с использованием cxXPath
result = cxXPath.FindXmlAttributesByNameAndValue("*.xml", 8, "cleartextTrafficPermitted", "true", false, true);Rozoberme to podrobnejšie, pretože syntax všetkých funkcií je podobná, po tom, ako som sa oboznámil s jednou, ďalšie už len potrebujú vybrať. Takže po parametroch postupne:
"*.xml"— maska súborov, podľa ktorých sa musí vykonať vyhľadávanie8— id jazyka, pre ktorý sa aplikuje pravidlo"cleartextTrafficPermitted"— názov atribútu v xml"true"— hodnota tohto atribútunepravda— použitie regulárneho výrazu pri vyhľadávanítrue— znamená, že vyhľadávanie sa vykoná s ignorovaním veľkosti písmen, to znamená case-insensitive
Pre príklad bolo použité pravidlo, ktoré určuje nesprávne, z hľadiska bezpečnosti, nastavenia sieťového pripojenia v Androide, ktoré povolujú komunikáciu so serverom prostredníctvom protokolu HTTP. Príklad nastavenia, ktoré obsahuje atribút cleartextTrafficPermitted s hodnotou true:
example.com
secure.example.comÚloha: Obmedziť výsledky podľa názvu/cesty súboru
Riešenie: V jednom z veľkých projektov, ktoré súvisia s vývojom mobilnej aplikácie pre Android, sme sa stretli s falošnými poplachmi pravidla, ktoré určuje nastavenie obfuskácie. V skutočnosti pravidlo z predvoleného nastavenia hľadá v súbore build.gradle nastavenie, ktoré zodpovedá za uplatnenie pravidiel obfuskácie pre verziu aplikácie.
Avšak v veľkých projektoch sa niekedy objavujú podriadené súbory build.gradle, ktoré sa týkajú knižníc zahrnutých v projekte. Zvláštnosťou je, že aj keď v týchto súboroch nie je uvedená potreba obfuskácie, pri kompilácii sa použijú nastavenia nadriadeného súboru pre zostavenie.
Týmto je úloha obmedziť vyhlásenia vo vedľajších súboroch, ktoré sa týkajú knižníc. Je možné ich určiť na základe prítomnosti riadku apply 'com.android.library'.
Príklad kódu zo súboru build.gradle, ktorý určuje potrebu obfuskácie:
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, ktorá je pripojená k projektu a nemá takúto konfiguráciu:
apply plugin: 'android-library'
dependencies {
compile 'com.android.support:support-v4:18.0.+'
}
android {
compileSdkVersion 14
buildToolsVersion '17.0.0'
...
}A pravidlo pre 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);
}
}
}Takýto prístup môže byť dosť univerzálny a môže sa hodiť nielen pre Android aplikácie, ale aj pre iné prípady, kde je potrebné určovať, či výsledok patrí k určitému súboru.
Úloha: Pridať podporu pre externú knižnicu, ak je syntax čiastočne podporovaná
Riešenie: Počet rôznych rámcov, ktoré sa používajú v procese písania kódu, jednoducho presahuje únosnú mez. Samozrejme, Checkmarx nie vždy vie o ich existencii a našou úlohou je naučiť ho rozumieť, že určité metódy patria práve k tomuto rámcu. Niekedy je to zložité, pretože rámce používajú názvy funkcií, ktoré sú veľmi bežné a nie je možné jednoznačne určiť, k akému konkrétnemu volaniu patrí daný rámec.
Zložitost spočíva v tom, že syntax takýchto knižníc nie vždy správne rozpoznáva a musíme experimentovať, aby sme sa vyhli veľkému počtu falošných poplachov. Existuje niekoľko možností, ako zlepšiť presnosť skenovania a vyriešiť danú úlohu:
Prvá možnosť, presne vieme, že knižnica sa používa v konkrétnom projekte a môžeme aplikovať pravidlo na úrovni tímu. Ale ak sa tím rozhodne použiť iný prístup alebo používa niekoľko knižníc, v ktorých sa prekrývajú názvy funkcií, môžeme získať nepríjemný obraz z množstva falošných poplachov.
Druhá možnosť, aplikovať vyhľadávanie v súboroch, v ktorých sa jasne importuje knižnica. Pri takomto prístupe si môžeme byť istí, že v danom súbore sa určite používa potrebná knižnica.
A tretia možnosť, je použitie oboch vyššie uvedených prístupov spoločne.
Ako príklad si rozoberieme známy v úzkych kruhoch knižnicu pre programovací jazyk Scala, a to konkrétne, funkcionalitu . Vo všeobecnosti na prenos parametrov do SQL dotazu je potrebné použiť operátor $, ktorý vloží dáta do predpripraveného SQL dotazu. Teda je to v skutočnosti priamy ekvivalent Prepared Statement v Jave. Ale ak je potrebné dynamicky konštruovať SQL dotaz, napríklad ak je potrebné poslať názvy tabuliek, je možné použiť operátor #$, ktorý priamo vloží dáta do dotazu (prakticky, ako konkatenácia reťazcov).
Príklad kódu:
// В общем случае - значения, контролируемые пользователем
val table = "coffees"
sql"select * from #$table where name = $name".as[Coffee].headOptionCheckmarx zatiaľ nevie zistiť používanie Splicing Literal Values a prehliada operátory #$, takže sa pokúsime naučiť ho identifikovať potenciálne SQL injekcie a zvýrazniť potrebné 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));
}Úloha: Hľadanie používaných zraniteľných funkcií v open-source knihovnách
Riešenie: Mnohé spoločnosti používajú nástroje na kontrolu open-source (praktika OSA), ktoré umožňujú odhaliť používanie 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 bezpečnú verziu. V niektorých prípadoch sú funkčné obmedzenia, v iných bezpečná verzia jednoducho neexistuje. V takýchto prípadoch pomôže kombinácia praktík SAST a OSA, ktorá umožňuje určiť, že funkcie, ktoré vedú k zneužitiu zraniteľnosti, nie sú použité v kóde.
Ale niekedy, najmä ak zoberieme do úvahy JavaScript, to nemusí byť úplne triviálna úloha. Nižšie je uvedené riešenie, možno nie ideálne, ale predsa fungujúce, na príklade zraniteľností v komponente lodash v metódach template a *set.
Príklady testovacieho potenciálne zraniteľného kódu v JS súbore:
/**
* 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 priamom pripojení do HTML:
<!DOCTYPE html>
<html>
<head>
<title>Lodash tutoriál</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));Úloha: Hľadanie zabudovaných certifikátov v aplikácii
Riešenie: Aplikácie, najmä mobilné, často používajú certifikáty alebo kľúče na prístup k rôznym serverom alebo na overovanie SSL-Pinning. Z hľadiska bezpečnosti - uchovávanie takýchto vecí v kóde nie je najlepšou praxou. Pokúsime sa napísať pravidlo, ktoré bude hľadať takéto súbory v repozitári:
// Найдем все сертификаты по маске файла
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;Úloha: Hľadanie skompromitovaných tokenov v aplikácii
Riešenie: Niekedy je potrebné stiahnuť skompromitované tokeny alebo iné dôležité informácie prítomné v kóde. Určite nie je najlepším nápadom uchovávať ich v zdrojových kódoch, ale situácie sú rôzne. Vďaka dopytom CxQL je takéto vyhľadávanie relatívne jednoduché:
// Получаем все строки, которые содержатся в коде
CxList strings = base.Find_Strings();
// Ищем среди всех строк нужное нам значение. В примере токен в виде строки "qwerty12345"
result = strings.FindByShortName("qwerty12345");Záver
Dúfam, že táto článok bude užitočný pre tých, ktorí začínajú svoju interakciu s nástrojom Checkmarx. Možno aj tí, ktorí už dlhší čas píšu svoje pravidlá, nájdu v tomto sprievodcovi niečo užitočné.
Bohužiaľ, momentálne chýba zdroj, kde by sa dalo čerpať nové inšpirácie pri vývoji pravidiel pre Checkmarx. Preto sme vytvorili , kde budeme zverejňovať svoje výstupy, aby každý, kto používa CxQL, mohol nájsť niečo užitočné a mal možnosť podeliť sa so svojou prácou s komunitou. Repo je v procese naplnenia a štruktúrovania obsahu, takže príspevky sú vítané!
Ďakujem za pozornosť!
Zdroj: habr.com

Nastavenie predvoľby v rozhraní Checkmarx
Rozhranie CxAudit
Rozdelenie pravidiel podľa jazykov
Určenie typu pravidla pri vytváraní
Príklad nového pravidla v rozhraní správcu predvoľieb
Určenie úrovne, na ktorej sa bude pravidlo aplikovať