Kako napisati pravila za Checkmarx, ne da bi se zmešalo

Pozdravljeni, Habr!

Naše podjetje se pri svojem delu zelo pogosto ukvarja z različnimi orodji za analizo statične kode (SAST). Izven škatle vsi delujejo povprečno. Seveda pa je vse odvisno od projekta in tehnologij, ki se v njem uporabljajo, pa tudi od tega, kako dobro te tehnologije pokrivajo pravila analize. Po mojem mnenju je eden najpomembnejših kriterijev pri izbiri orodja SAST možnost, da ga prilagodite specifikam vaše aplikacije, in sicer pisanje in spreminjanje analiznih pravil ali, kot jih pogosteje imenujemo, Custom Queries.

Kako napisati pravila za Checkmarx, ne da bi se zmešalo

Najpogosteje uporabljamo Checkmarx - zelo zanimiv in zmogljiv analizator kode. V tem članku bom govoril o svojih izkušnjah s pisanjem pravil za analizo.

Kazalo

Začetek

Za začetek bi rad priporočil enega redkih člankov v ruščini o značilnostih pisanja poizvedb za Checkmarx. Na Habréju je bil objavljen konec leta 2019 pod naslovom: "Pozdravljen, Checkmarx!" Kako napisati poizvedbo Checkmarx SAST in najti zanimive ranljivosti.

Podrobno preučuje, kako napisati prve poizvedbe v CxQL (Checkmarx Query Language) za nekatere testne aplikacije in prikazuje osnovna načela delovanja pravil analize.

Ne bom ponavljal opisanega v njem, čeprav bodo nekatera križišča še vedno prisotna. V svojem članku bom poskušal sestaviti nekakšno "zbirko receptov", seznam rešitev za določene težave, s katerimi sem se srečal med delom s Checkmarxom. Pri mnogih od teh težav sem moral nabijati glavo. Včasih v dokumentaciji ni bilo dovolj podatkov, včasih pa je bilo celo težko razumeti, kako narediti zahtevano. Upam, da moje izkušnje in neprespane noči ne bodo zaman in vam bo ta »zbirka receptov Custom Queries« prihranila nekaj ur ali nekaj živčnih celic. Torej, začnimo!

Splošne informacije o pravilih

Najprej si oglejmo nekaj osnovnih konceptov in postopek dela s pravili, da bomo bolje razumeli, kaj se bo zgodilo naslednje. In tudi zato, ker dokumentacija o tem ne pove ničesar ali pa je zelo razpršena po strukturi, kar ni zelo priročno.

  1. Pravila se uporabljajo med skeniranjem glede na prednastavitev, izbrano ob začetku (niz aktivnih pravil). Ustvarite lahko neomejeno število prednastavitev in natančno, kako jih strukturirati, je odvisno od posebnosti vašega procesa. Lahko jih združite po jeziku ali izberete prednastavitve za vsak projekt. Število aktivnih pravil vpliva na hitrost in natančnost skeniranja.

    Kako napisati pravila za Checkmarx, ne da bi se zmešaloNastavitev prednastavitve v vmesniku Checkmarx

  2. Pravila se urejajo v posebnem orodju imenovanem CxAuditor. To je namizna aplikacija, ki se poveže s strežnikom, v katerem se izvaja Checkmarx. To orodje ima dva načina delovanja: urejanje pravil in analiziranje rezultatov že izvedenega skeniranja.

    Kako napisati pravila za Checkmarx, ne da bi se zmešaloVmesnik CxAudit

  3. Pravila v Checkmarxu so razdeljena po jeziku, kar pomeni, da ima vsak jezik svoj niz poizvedb. Obstaja tudi nekaj splošnih pravil, ki veljajo ne glede na jezik, to so tako imenovane osnovne poizvedbe. Osnovne poizvedbe večinoma vključujejo iskanje informacij, ki jih uporabljajo druga pravila.

    Kako napisati pravila za Checkmarx, ne da bi se zmešaloRazdelitev pravil po jeziku

  4. Pravila so »izvršljiva« in »neizvršljiva« (izvedena in neizvedena). Po mojem mnenju ni ravno pravo ime, a tako je. Bistvo je, da bo rezultat izvajanja »izvršljivih« pravil prikazan v rezultatih skeniranja v uporabniškem vmesniku, »neizvršljiva« pravila pa so potrebna samo za uporabo njihovih rezultatov v drugih zahtevah (v bistvu le funkcija).

    Kako napisati pravila za Checkmarx, ne da bi se zmešaloDoločanje vrste pravila pri ustvarjanju

  5. Ustvarite lahko nova pravila ali dopolnite/prepišete obstoječa. Če želite prepisati pravilo, ga morate poiskati v drevesu, z desno miškino tipko kliknite in v spustnem meniju izberite »Preglasi«. Pomembno si je zapomniti, da nova pravila na začetku niso vključena v prednastavitve in niso aktivna. Če jih želite začeti uporabljati, jih morate aktivirati v meniju »Preset Manager« v instrumentu. Prepisana pravila ohranijo svoje nastavitve, to pomeni, da če je bilo pravilo aktivno, bo tako tudi ostalo in bo uporabljeno takoj.

    Kako napisati pravila za Checkmarx, ne da bi se zmešaloPrimer novega pravila v vmesniku Preset Manager

  6. Med izvajanjem se zgradi »drevo« zahtev, odvisno od česa. Najprej se izvršijo pravila, ki zbirajo informacije, nato pa tista, ki jih uporabljajo. Rezultat izvajanja je predpomnjen, zato je bolje, če je mogoče uporabiti rezultate obstoječega pravila, saj bo to skrajšalo čas skeniranja.

  7. Pravila se lahko uporabljajo na različnih ravneh:

  • Za celoten sistem - bo uporabljen za morebitno skeniranje katerega koli projekta

  • Na ravni ekipe (Ekipa) – uporablja se samo za skeniranje projektov v izbrani ekipi.

  • Na ravni projekta – Uporabljeno bo v določenem projektu

    Kako napisati pravila za Checkmarx, ne da bi se zmešaloDoločitev ravni, na kateri bo uporabljeno pravilo

"Slovar" za začetnike

Začel bom z nekaj stvarmi, ki so mi povzročale vprašanja, pokazal pa bom tudi številne tehnike, ki bodo bistveno poenostavile življenje.

Operacije s seznami

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

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

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

Vsi najdeni predmeti

Znotraj skeniranega jezika lahko dobite seznam absolutno vseh elementov, ki jih je identificiral Checkmarx (nizi, funkcije, razredi, metode itd.). To je nekaj prostora predmetov, skozi katere je mogoče dostopati All. To pomeni, da iščete predmet z določenim imenom searchMe, lahko na primer iščete po imenu po vseh najdenih predmetih:

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

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

Če pa morate iskati v drugem jeziku, ki iz nekega razloga ni bil vključen v skeniranje (na primer groovy v projektu Android), lahko razširite naš predmetni prostor s spremenljivko:

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

Funkcije za analizo toka

Te funkcije se uporabljajo v številnih pravilih in tukaj je majhen goljuf, kaj pomenijo:

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

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

Pridobivanje imena/poti datoteke

Obstaja več atributov, ki jih je mogoče pridobiti iz rezultatov poizvedbe (ime datoteke, v kateri je bil najden vnos, niz itd.), vendar v dokumentaciji ni navedeno, kako jih pridobiti in uporabiti. Če želite to narediti, morate dostopati do lastnosti LinePragma in predmeti, ki jih potrebujemo, se bodo nahajali znotraj nje:

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

Vredno je upoštevati, da FileName dejansko vsebuje pot do datoteke, saj smo uporabili metodo GetFirstGraph.

Rezultat izvedbe

V CxQL je posebna spremenljivka result, ki vrne rezultat izvajanja vašega zapisanega pravila. Inicializira se takoj in vanj lahko zapišete vmesne rezultate ter jih med delom spreminjate in izpopolnjujete. Ampak, če tej spremenljivki ali funkciji znotraj pravila ni dodelitve return— rezultat izvedbe bo vedno enak nič.

Naslednja poizvedba nam kot rezultat izvedbe ne bo vrnila ničesar in bo vedno prazna:

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

Toda, ko rezultat izvajanja dodelimo čarobni spremenljivki result, bomo videli, kaj nam vrne ta klic:

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

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

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

Uporaba rezultatov drugih pravil

Pravila v Checkmarxu lahko imenujemo analogno funkcijam v običajnem programskem jeziku. Ko pišete pravilo, lahko uporabite rezultate drugih poizvedb. Na primer, ni treba vsakič iskati vseh klicev metod v kodi, samo pokličite želeno pravilo:

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

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

Ta pristop vam omogoča, da skrajšate kodo in znatno zmanjšate čas izvajanja pravila.

Reševanje težav

Sečnja

Pri delu z orodjem včasih ni mogoče takoj napisati želene poizvedbe in morate eksperimentirati, preizkušati različne možnosti. Za tak primer orodje zagotavlja beleženje, ki se imenuje na naslednji način:

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

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

Vendar je vredno zapomniti, da ta metoda sprejema le kot vnos vrvica, zato ne bo mogoče prikazati celotnega seznama najdenih elementov kot rezultat prve operacije. Druga možnost, ki se uporablja za odpravljanje napak, je občasno dodelitev čarobni spremenljivki result rezultat poizvedbe in poglejte, kaj se zgodi. Ta pristop ni zelo priročen; zagotoviti morate, da v kodi po tem ni nobenih preglasitev ali operacij s tem result ali preprosto komentirajte spodnjo kodo. Lahko pa, tako kot jaz, pozabite odstraniti več takih klicev iz že pripravljenega pravila in se sprašujete, zakaj nič ne deluje.

Priročnejši način je klic metode return z zahtevanim parametrom. V tem primeru se bo izvedba pravila končala in videli bomo, kaj se je zgodilo kot rezultat zapisanega:

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

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

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

Težava s prijavo

Obstajajo situacije, ko ne morete dostopati do orodja CxAudit (ki se uporablja za pisanje pravil). Za to je lahko veliko razlogov, vključno z zrušitvami, nenadnimi posodobitvami sistema Windows, BSOD in drugimi nepredvidenimi situacijami, na katere nimamo vpliva. V tem primeru je včasih v bazi nedokončana seja, zaradi katere se ne morete ponovno prijaviti. Če želite to popraviti, morate izvesti več poizvedb:

Za Checkmarx pred 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');

Pravila pisanja

Zdaj smo pri najbolj zanimivem delu. Ko začnete pisati pravila v CxQL, vam pogosto manjka ne toliko dokumentacije kot nekaj živih primerov reševanja določenih problemov in opisovanja procesa delovanja poizvedb na splošno.

Poskušal bom nekoliko olajšati življenje tistim, ki se začenjajo poglabljati v jezik poizvedb, in podal nekaj primerov uporabe poizvedb po meri za reševanje določenih težav. Nekatere med njimi so precej splošne in jih lahko praktično brez sprememb uporabljate v svojem podjetju, druge so bolj specifične, lahko pa jih uporabite tudi tako, da spremenite kodo tako, da ustreza specifikam vaših aplikacij.

Torej, tukaj so težave, s katerimi se najpogosteje srečujemo:

Naloga: V rezultatih izvajanja pravila je več tokov in eden od njih je gnezdenje drugega, enega od njih morate zapustiti.

raztopina: Včasih Checkmarx dejansko prikaže več tokov 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 pretok:

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

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

Naloga: Razširite seznam občutljivih podatkov, na katere se orodje odzove

raztopina: Checkmarx ima osnovna pravila, katerih rezultate uporabljajo številne druge poizvedbe. Z dopolnitvijo nekaterih od teh pravil s podatki, specifičnimi za vašo aplikacijo, lahko takoj izboljšate rezultate skeniranja. Spodaj je primer pravila za lažji začetek:

Splošni_seznam_kršitev_zasebnosti

Dodajmo več 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širite seznam spremenljivk z gesli

raztopina: Priporočam, da takoj posvetite pozornost osnovnemu pravilu za definiranje gesel v kodi in mu dodate seznam imen spremenljivk, ki se običajno uporabljajo v vašem podjetju.

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

Naloga: Dodajte uporabljena ogrodja, ki jih Checkmarx ne podpira

raztopina: Vse poizvedbe v Checkmarxu so razdeljene po jeziku, zato morate dodati pravila za vsak jezik. Spodaj je nekaj primerov takih pravil.

Če se uporabljajo knjižnice, ki dopolnjujejo ali nadomeščajo standardno funkcionalnost, jih je mogoče enostavno dodati osnovnemu pravilu. Potem bodo vsi, ki ga uporabljajo, takoj izvedeli za nove uvedbe. Na primer, knjižnici za prijavo v Android sta Timber in Loggi. V osnovnem paketu ni pravil za prepoznavanje nesistemskih klicev, tako da če geslo ali identifikator seje pride v dnevnik, za to ne bomo vedeli. Poskusimo dodati definicije takih metod pravilom Checkmarx.

Primer preizkusne kode, ki za beleženje uporablja knjižnico Timber:

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

In tukaj je primer zahteve za Checkmarx, ki vam bo omogočil dodajanje definicije klicanja metod Timber kot izhodne točke za podatke iz aplikacije:

Poišči AndroidOutputs

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

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

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

Prav tako lahko dodate k sosednjemu pravilu, vendar se to neposredno nanaša na prijavo v Android:

Poišči AndroidLog_Outputs

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

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

Tudi, če uporabljajo aplikacije za Android WorkManager za asinhrono delo je dobro, da o tem dodatno obvestite Checkmarx z dodajanjem metode za pridobivanje podatkov iz opravila getInputData:

FindAndroidRead

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

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

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

Naloga: Iskanje občutljivih podatkov v plist za projekte iOS

raztopina: iOS pogosto uporablja posebne datoteke s pripono .plist za shranjevanje različnih spremenljivk in vrednosti. Shranjevanje gesel, žetonov, ključev in drugih občutljivih podatkov v teh datotekah ni priporočljivo, saj jih lahko brez težav ekstrahiramo iz naprave.

Datoteke Plist imajo funkcije, ki niso očitne s prostim očesom, vendar so pomembne za Checkmarx. Napišimo pravilo, ki bo iskalo podatke, ki jih potrebujemo, in nam povedalo, če so kje omenjena gesla ali žetoni.

Primer takšne datoteke, ki vsebuje žeton za komunikacijo z zaledno storitvijo:

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

In pravilo za Checkmarx, ki ima več odtenkov, ki jih je treba upoštevati pri pisanju:

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

raztopina: Checkmarx ima zelo priročne funkcije za delo z XML in iskanje vrednosti, oznak, atributov in drugega. A na žalost je prišlo do napake v dokumentaciji, zaradi katere ne deluje niti en primer. Kljub temu, da je bila ta pomanjkljivost odpravljena v zadnji različici dokumentacije, bodite previdni, če uporabljate starejše različice dokumentov.

Tukaj je napačen primer iz dokumentacije:

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

Kot rezultat poskusa izvedbe bomo prejeli napako, ki All take metode ni ... In to je res, saj obstaja poseben, ločen predmetni prostor za uporabo funkcij za delo z XML - cxXPath. Tako je videti pravilna poizvedba za iskanje nastavitve v sistemu Android, ki dovoljuje uporabo prometa HTTP:

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

Oglejmo si ga nekoliko podrobneje, saj je sintaksa za vse funkcije podobna, potem ko ugotovite eno, morate samo izbrati tisto, ki jo potrebujete. Torej, zaporedno glede na parametre:

  • "*.xml"— maska ​​datotek za iskanje

  • 8 — ID jezika, za katerega velja pravilo

  • "cleartextTrafficPermitted"— ime atributa v xml

  • "true" — vrednost tega atributa

  • false — uporaba regularnega izraza pri iskanju

  • true — pomeni, da bo iskanje izvedeno brez upoštevanja velikih in malih črk, to je brez razlikovanja med velikimi in malimi črkami

Kot primer smo uporabili pravilo, ki identificira nepravilne, z varnostnega vidika, nastavitve omrežne povezave v sistemu Android, ki omogočajo komunikacijo s strežnikom prek protokola HTTP. Primer nastavitve, ki vsebuje atribut cleartextTrafficPermitted s pomenom 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>

Naloga: Omejite rezultate glede na ime/pot datoteke

raztopina: Pri enem od velikih projektov, povezanih z razvojem mobilne aplikacije za Android, smo naleteli na lažne pozitivne rezultate pravila, ki določa nastavitev zakrivanja. Dejstvo je, da pravilo izven okvirja išče v datoteki build.gradle nastavitev, ki je odgovorna za uporabo pravil zakrivanja za izdajo različice aplikacije.

Toda v velikih projektih včasih obstajajo podrejene datoteke build.gradle, ki se nanašajo na knjižnice, vključene v projekt. Posebnost je, da tudi če te datoteke ne nakazujejo potrebe po zamegljevanju, bodo med prevajanjem uporabljene nastavitve datoteke nadrejenega sestava.

Tako je naloga odrezati sprožilce v podrejenih datotekah, ki pripadajo knjižnicam. Prepoznamo jih lahko po prisotnosti črte apply 'com.android.library'.

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

apply plugin: 'com.android.application'

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

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

dependencies {
  ...
}

Primer datoteke build.gradle za knjižnico, vključeno v projekt, ki nima te nastavitve:

apply plugin: 'android-library'

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

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

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

Ta pristop je lahko precej univerzalen in uporaben ne samo za aplikacije za Android, ampak tudi za druge primere, ko morate ugotoviti, ali rezultat pripada določeni datoteki.

Naloga: Dodajte podporo za knjižnico tretje osebe, če sintaksa ni v celoti podprta

raztopina: Število različnih ogrodij, ki se uporabljajo v procesu pisanja kode, je enostavno preseženo. Seveda Checkmarx ne ve vedno za njihov obstoj in naša naloga je, da ga naučimo razumeti, da določene metode pripadajo posebej temu okviru. Včasih je to zapleteno zaradi dejstva, da ogrodja uporabljajo imena funkcij, ki so zelo pogosta in je nemogoče nedvoumno določiti odnos določenega klica do določene knjižnice.

Težava je v tem, da sintaksa takih knjižnic ni vedno pravilno prepoznana in morate eksperimentirati, da se izognete velikemu številu lažno pozitivnih rezultatov. Obstaja več možnosti za izboljšanje natančnosti skeniranja in rešitev težave:

  • Prva možnost, zagotovo vemo, da se knjižnica uporablja v določenem projektu in lahko uporabi pravilo na ravni skupine. Če pa se ekipa odloči za drugačen pristop ali uporabi več knjižnic, v katerih se imena funkcij prekrivajo, lahko dobimo ne preveč prijetno sliko številnih lažno pozitivnih rezultatov.

  • Druga možnost je iskanje datotek, v katerih je knjižnica jasno uvožena. S tem pristopom smo lahko prepričani, da je v tej datoteki uporabljena točno knjižnica, ki jo potrebujemo.

  • In tretja možnost je uporaba obeh zgornjih pristopov skupaj.

Kot primer poglejmo knjižnico, ki je znana v ozkih krogih gladek za programski jezik Scala, in sicer funkcionalnost Spajanje dobesednih vrednosti. Na splošno morate za posredovanje parametrov v poizvedbo SQL uporabiti operator $, ki nadomesti podatke v predhodno oblikovano poizvedbo SQL. To je pravzaprav neposreden analog pripravljene izjave v Javi. Če pa morate dinamično sestaviti poizvedbo SQL, na primer, če morate posredovati imena tabel, lahko uporabite operator #$, ki bo podatke neposredno nadomestil v poizvedbo (skoraj kot združevanje nizov).

Vzorčna koda:

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

Checkmarx še ne zna zaznati uporabe spajanja dobesednih vrednosti in preskoči operatorje #$, zato ga poskušajmo naučiti prepoznati morebitne injekcije SQL in označiti prava mesta v kodi:

// Находим все импорты
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: Iskanje uporabljenih ranljivih funkcij v odprtokodnih knjižnicah

raztopina: Številna podjetja uporabljajo orodja za nadzor odprte kode (praksa OSA), da zaznajo uporabo ranljivih različic knjižnic v razvitih aplikacijah. Včasih takšne knjižnice ni mogoče posodobiti na varno različico. V nekaterih primerih obstajajo funkcionalne omejitve, v drugih varne različice sploh ni. V tem primeru bo kombinacija praks SAST in OSA pomagala ugotoviti, da funkcije, ki vodijo do izkoriščanja ranljivosti, niso uporabljene v kodi.

Toda včasih, zlasti ko razmišljamo o JavaScriptu, to morda ni povsem trivialna naloga. Spodaj je rešitev, ki morda ni idealna, a kljub temu deluje, na primeru ranljivosti v komponenti lodash v metodah template и *set.

Primeri preskusne potencialno ranljive kode v datoteki 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!'

In pri neposrednem povezovanju 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>

Iščemo vse naše ranljive metode, ki so navedene v ranljivostih:

// Ищем все строки: в которых встречается строка 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: Iskanje certifikatov, vgrajenih v aplikacijo

raztopina: Ni nenavadno, da aplikacije, zlasti mobilne, uporabljajo potrdila ali ključe za dostop do različnih strežnikov ali preverjanje pripenjanja SSL. Z varnostnega vidika shranjevanje takih stvari v kodo ni najboljša praksa. Poskusimo napisati pravilo, ki bo iskalo podobne datoteke v repozitoriju:

// Найдем все сертификаты по маске файла
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: Iskanje ogroženih žetonov v aplikaciji

raztopina: Pogosto je treba preklicati ogrožene žetone ali druge pomembne informacije, ki so prisotne v kodi. Seveda njihovo shranjevanje znotraj izvorne kode ni dobra ideja, vendar so situacije različne. Zahvaljujoč poizvedbam CxQL je iskanje takšnih stvari precej enostavno:

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

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

Zaključek

Upam, da bo ta članek koristen za tiste, ki se začenjajo seznanjati z orodjem Checkmarx. Morda bodo v tem priročniku kaj koristnega našli tudi tisti, ki že dolgo pišejo svoja pravila.

Na žalost trenutno primanjkuje vira, kjer bi lahko pridobili nove ideje med razvojem pravil za Checkmarx. Zato smo ustvarili repozitorij na Githubu, kjer bomo objavljali svoje delo, tako da lahko vsi, ki uporabljajo CxQL, v njem najdejo kaj uporabnega in imajo tudi možnost deliti svoje delo s skupnostjo. Repozitorij je v procesu polnjenja in strukturiranja vsebine, zato vabljeni sodelujoči!

Спасибо за внимание!

Vir: www.habr.com

Dodaj komentar