Kako napisati pravila za Checkmarx a da ne poludim

Hej Habr!

U svom radu naša kompanija se vrlo često bavi raznim alatima za statičku analizu koda (SAST). Izvan kutije svi rade prosječno. Naravno, sve zavisi od projekta i tehnologija koje se u njemu koriste, kao i koliko su te tehnologije pokrivene pravilima analize. Po mom mišljenju, jedan od najvažnijih kriterija pri odabiru SAST alata je mogućnost da ga prilagodite specifičnostima vaših aplikacija, odnosno pišete i mijenjate pravila analize ili, kako se češće nazivaju, prilagođeni upiti.

Kako napisati pravila za Checkmarx a da ne poludim

Najčešće koristimo Checkmarx - vrlo zanimljiv i moćan analizator koda. U ovom članku ću govoriti o svom iskustvu pisanja pravila analize za to.

Sadržaj

ulazak

Za početak, želio bih preporučiti jedan od rijetkih članaka na ruskom o značajkama pisanja upita za Checkmarx. Objavljeno je na Habréu krajem 2019. godine pod naslovom: "Zdravo, Čekmarks!" Kako napisati Checkmarx SAST upit i pronaći cool ranjivosti.

U njemu se detaljno ispituje kako napisati prve upite u CxQL-u (Checkmarx Query Language) za neku testnu aplikaciju i pokazuje osnovne principe kako pravila analize funkcionišu.

Neću ponavljati ono što je u njemu opisano, iako će neke raskrsnice i dalje biti prisutne. U svom članku pokušat ću sastaviti svojevrsnu “zbirku recepata”, listu rješenja za specifične probleme s kojima sam se susreo tokom rada sa Checkmarxom. Morao sam da se razbijem nad mnogim od ovih problema. Ponekad nije bilo dovoljno informacija u dokumentaciji, a ponekad je bilo čak i teško razumjeti kako učiniti ono što se traži. Nadam se da moje iskustvo i neprospavane noći neće biti uzaludni, a ova “kolekcija recepata prilagođenih upita” će vam uštedjeti nekoliko sati ili par nervnih ćelija. Dakle, počnimo!

Opće informacije o pravilima

Prvo, pogledajmo nekoliko osnovnih koncepata i proces rada sa pravilima, radi boljeg razumijevanja onoga što će se sljedeće dogoditi. I zato što dokumentacija ne govori ništa o tome ili je vrlo raširena u strukturi, što nije baš zgodno.

  1. Pravila se primjenjuju tokom skeniranja u zavisnosti od unaprijed odabrane postavke na početku (skup aktivnih pravila). Možete kreirati neograničen broj unaprijed postavljenih postavki, a kako ih tačno strukturirati ovisi o specifičnostima vašeg procesa. Možete ih grupirati po jeziku ili odabrati unaprijed postavljene postavke za svaki projekat. Broj aktivnih pravila utiče na brzinu i tačnost skeniranja.

    Kako napisati pravila za Checkmarx a da ne poludimPodešavanje Preseta u Checkmarx interfejsu

  2. Pravila se uređuju u posebnom alatu zvanom CxAuditor. Ovo je desktop aplikacija koja se povezuje na server na kojem radi Checkmarx. Ovaj alat ima dva načina rada: uređivanje pravila i analizu rezultata već obavljenog skeniranja.

    Kako napisati pravila za Checkmarx a da ne poludimCxAudit interfejs

  3. Pravila u Checkmarxu su podijeljena po jeziku, odnosno svaki jezik ima svoj skup upita. Postoje i neka opća pravila koja vrijede bez obzira na jezik, to su takozvani osnovni upiti. Uglavnom, osnovni upiti uključuju traženje informacija koje koriste druga pravila.

    Kako napisati pravila za Checkmarx a da ne poludimPodjela pravila po jeziku

  4. Pravila su “izvršna” i “neizvršna” (izvršena i neizvršena). Nije baš tačan naziv, po mom mišljenju, ali to je ono što je. Suština je da će rezultat izvršavanja „Izvršnih“ pravila biti prikazan u rezultatima skeniranja u korisničkom sučelju, a „Non-Executable“ pravila su potrebna samo da bi se njihovi rezultati koristili u drugim zahtjevima (u suštini, samo funkcija).

    Kako napisati pravila za Checkmarx a da ne poludimOdređivanje tipa pravila prilikom kreiranja

  5. Možete kreirati nova pravila ili dopuniti/prepisati postojeća. Da biste ponovo napisali pravilo, morate ga pronaći u stablu, kliknuti desnim tasterom miša i odabrati „Override“ iz padajućeg menija. Ovdje je važno zapamtiti da nova pravila nisu inicijalno uključena u unaprijed postavljene postavke i nisu aktivna. Da biste počeli da ih koristite potrebno je da ih aktivirate u meniju „Preset Manager“ u instrumentu. Prepisana pravila zadržavaju svoja podešavanja, odnosno ako je pravilo bilo aktivno, ostat će tako i odmah će se primijeniti.

    Kako napisati pravila za Checkmarx a da ne poludimPrimjer novog pravila u interfejsu Preset Manager

  6. Tokom izvršavanja gradi se „stablo“ zahtjeva, što zavisi od čega. Prvo se izvršavaju pravila koja prikupljaju informacije, a drugi oni koji ih koriste. Rezultat izvršenja je keširan, pa ako je moguće koristiti rezultate postojećeg pravila, onda je bolje to učiniti, to će smanjiti vrijeme skeniranja.

  7. Pravila se mogu primijeniti na različitim nivoima:

  • Za cijeli sistem - koristit će se za bilo koje skeniranje bilo kojeg projekta

  • Na nivou tima (Tim) - koristit će se samo za skeniranje projekata u odabranom timu.

  • Na nivou projekta - Biće primijenjen u određenom projektu

    Kako napisati pravila za Checkmarx a da ne poludimOdređivanje nivoa na kojem će se pravilo primjenjivati

“Rječnik” za početnike

I počeću s nekoliko stvari koje su mi izazvale pitanja, a pokazaću i niz tehnika koje će značajno pojednostaviti život.

Operacije sa listama

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

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

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

Svi pronađeni artikli

Unutar skeniranog jezika možete dobiti listu apsolutno svih elemenata koje je Checkmarx identificirao (nizove, funkcije, klase, metode itd.). Ovo je neki prostor objekata kroz koji se može pristupiti All. To jest, za traženje objekta sa određenim imenom searchMe, možete pretraživati, na primjer, po imenu po svim pronađenim objektima:

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

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

Ali, ako trebate pretraživati ​​na drugom jeziku koji iz nekog razloga nije uključen u skeniranje (na primjer, groovy u Android projektu), možete proširiti naš objektni prostor kroz varijablu:

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

Funkcije za analizu protoka

Ove funkcije se koriste u mnogim pravilima, a evo male varalice o tome što one znače:

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

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

Dobivanje imena/putanja datoteke

Postoji nekoliko atributa koji se mogu dobiti iz rezultata upita (ime datoteke u kojoj je unos pronađen, string, itd.), ali dokumentacija ne kaže kako ih dobiti i koristiti. Dakle, da biste to učinili, morate pristupiti svojstvu LinePragma i unutar njega će se nalaziti objekti koji su nam potrebni:

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

Vrijedi to imati na umu FileName sadrži zapravo putanju do datoteke, pošto smo koristili metodu GetFirstGraph.

Rezultat izvršenja

Postoji posebna varijabla unutar CxQL-a result, koji vraća rezultat izvršavanja vašeg pisanog pravila. Odmah se inicijalizira i u njega možete pisati međurezultate, mijenjajući ih i usavršavajući ih dok radite. Ali, ako ne postoji dodjela ovoj varijabli ili funkciji unutar pravila return— rezultat izvršenja će uvijek biti nula.

Sljedeći upit nam neće ništa vratiti kao rezultat izvršenja i uvijek će biti prazan:

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

Ali, nakon što smo rezultat izvršenja dodijelili magičnoj varijabli rezultat, vidjet ćemo šta nam ovaj poziv vraća:

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

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

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

Koristeći rezultate drugih pravila

Pravila u Checkmarxu se mogu nazvati analogno funkcijama u redovnom programskom jeziku. Kada pišete pravilo, možete koristiti rezultate drugih upita. Na primjer, nema potrebe svaki put tražiti sve pozive metoda u kodu, samo pozovite željeno pravilo:

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

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

Ovaj pristup vam omogućava da skratite kod i značajno smanjite vrijeme izvršenja pravila.

Rješavanje problema

Logging

Kada radite s alatom, ponekad nije moguće odmah napisati željeni upit i morate eksperimentirati, isprobavajući različite opcije. Za takav slučaj, alat omogućava evidentiranje, koje se zove kako slijedi:

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

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

Ali vrijedi zapamtiti da ova metoda prihvaća samo kao ulaz string, tako da neće biti moguće prikazati kompletnu listu pronađenih elemenata kao rezultat prve operacije. Druga opcija, koja se koristi za otklanjanje grešaka, je da se s vremena na vreme dodeli magičnoj varijabli result rezultat upita i vidi šta se dešava. Ovaj pristup nije baš zgodan; morate biti sigurni da nema zaobilaženja ili operacija s ovim u kodu nakon result ili jednostavno komentirajte kod ispod. Ili možete, poput mene, zaboraviti ukloniti nekoliko takvih poziva iz gotovog pravila i pitati se zašto ništa ne radi.

Pogodniji način je pozivanje metode return sa traženim parametrom. U ovom slučaju, izvršenje pravila će se završiti i moći ćemo da vidimo šta se dogodilo kao rezultat onoga što smo napisali:

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

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

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

Problem sa prijavom

Postoje situacije kada ne možete pristupiti alatu CxAudit (koji se koristi za pisanje pravila). Razloga za to može biti mnogo, uključujući padove, iznenadna ažuriranja Windowsa, BSOD i druge nepredviđene situacije koje su izvan naše kontrole. U ovom slučaju, ponekad postoji nedovršena sesija u bazi podataka, što vas sprečava da se ponovo prijavite. Da biste to popravili, morate pokrenuti nekoliko upita:

Za Checkmarx prije 8.6:

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

Za Checkmarx nakon 8.6:

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

Pravila pisanja

Sada dolazimo do najzanimljivijeg dijela. Kada počnete pisati pravila u CxQL-u, ono što vam često nedostaje nije toliko dokumentacija koliko neki živi primjeri rješavanja određenih problema i opisivanje procesa kako upiti funkcioniraju općenito.

Pokušaću da malo olakšam život onima koji počinju da se upuštaju u jezik upita i daću nekoliko primera korišćenja prilagođenih upita za rešavanje određenih problema. Neki od njih su prilično opći i mogu se koristiti u vašoj kompaniji praktično bez promjena, drugi su specifičniji, ali se mogu koristiti i promjenom koda kako bi odgovarali specifičnostima vaših aplikacija.

Dakle, evo problema sa kojima smo se najčešće susreli:

Zadatak: Postoji nekoliko tokova u rezultatima izvršavanja pravila i jedan od njih je ugniježđenje drugog, morate napustiti jedan od njih.

rješenje: Zaista, ponekad Checkmarx pokazuje nekoliko tokova podataka koji se mogu preklapati i biti skraćena verzija drugih. Za takve slučajeve postoji posebna metoda ReduceFlow. Ovisno o parametru, odabire najkraći ili najduži protok:

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

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

Zadatak: Proširite listu osjetljivih podataka na koje alat reagira

rješenje: Checkmarx ima osnovna pravila, čije rezultate koriste mnogi drugi upiti. Dopunom nekih od ovih pravila podacima specifičnim za vašu aplikaciju, možete odmah poboljšati rezultate skeniranja. Ispod je primjer pravila za početak:

Općenita_lista_povređivanja_privatnosti

Dodajmo nekoliko varijabli koje se koriste u našoj aplikaciji za pohranjivanje osjetljivih informacija:

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

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

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

Zadatak: Proširite listu varijabli sa lozinkama

rješenje: Preporučio bih da odmah obratite pažnju na osnovno pravilo za definisanje lozinki u kodu i da mu dodate listu imena varijabli koje se najčešće koriste u vašoj kompaniji.

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

Zadatak: Dodajte korištene okvire koje Checkmarx ne podržava

rješenje: Svi upiti u Checkmarx-u su podijeljeni po jeziku, tako da morate dodati pravila za svaki jezik. U nastavku su neki primjeri takvih pravila.

Ako se koriste biblioteke koje dopunjuju ili zamjenjuju standardnu ​​funkcionalnost, one se lako mogu dodati osnovnom pravilu. Tada će svi koji ga koriste odmah saznati za nove uvode. Kao primjer, biblioteke za prijavu na Android su Timber i Loggi. U osnovnom paketu ne postoje pravila za identifikaciju nesistemskih poziva, tako da ako lozinka ili identifikator sesije uđu u dnevnik, nećemo znati za to. Pokušajmo dodati definicije takvih metoda u Checkmarx pravila.

Testni primjer koda koji koristi biblioteku Timber za evidentiranje:

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

Evo primjera zahtjeva za Checkmarx, koji će vam omogućiti da dodate definiciju pozivanja Timber metoda kao izlazne točke za podatke iz aplikacije:

FindAndroidOutputs

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

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

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

Možete dodati i susjedno pravilo, ali ovo se odnosi direktno na prijavu na Android:

FindAndroidLog_Outputs

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

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

Također, ako koriste Android aplikacije WorkManager za asinhroni rad, dobra je ideja dodatno obavijestiti Checkmarx o tome dodavanjem metode za dobivanje podataka iz zadatka getInputData:

FindAndroidRead

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

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

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

Zadatak: Traženje osjetljivih podataka u plist za iOS projekte

rješenje: iOS često koristi posebne datoteke sa ekstenzijom .plist za pohranu različitih varijabli i vrijednosti. Ne preporučuje se pohranjivanje lozinki, tokena, ključeva i drugih osjetljivih podataka u ove datoteke, jer se bez problema mogu izvući iz uređaja.

Plist datoteke imaju karakteristike koje nisu očigledne golim okom, ali su važne za Checkmarx. Napišimo pravilo koje će tražiti podatke koji su nam potrebni i reći nam da li se negdje spominju lozinke ili tokeni.

Primjer takve datoteke, koja sadrži token za komunikaciju sa pozadinskom uslugom:

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

I pravilo za Checkmarx, koje ima nekoliko nijansi koje treba uzeti u obzir prilikom pisanja:

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

Zadatak: Pronalaženje informacija u XML-u

rješenje: Checkmarx ima vrlo zgodne funkcije za rad sa XML-om i traženje vrijednosti, oznaka, atributa i još mnogo toga. Ali, nažalost, došlo je do greške u dokumentaciji zbog koje ni jedan primjer ne radi. Unatoč činjenici da je ovaj nedostatak otklonjen u najnovijoj verziji dokumentacije, budite oprezni ako koristite starije verzije dokumenata.

Evo netačnog primjera iz dokumentacije:

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

Kao rezultat pokušaja izvršenja, dobićemo grešku koja All ne postoji takav metod... I to je tačno, jer postoji poseban, odvojen objektni prostor za korišćenje funkcija za rad sa XML-om - cxXPath. Ovako izgleda ispravan upit za pronalaženje postavke u Androidu koja dozvoljava korištenje HTTP prometa:

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

Pogledajmo to malo detaljnije, pošto je sintaksa za sve funkcije slična, nakon što shvatite jednu, trebate samo odabrati onu koja vam je potrebna. Dakle, uzastopno prema parametrima:

  • "*.xml"— maska ​​datoteka koje treba pretraživati

  • 8 — id jezika za koji se pravilo primjenjuje

  • "cleartextTrafficPermitted"— ime atributa u xml

  • "true" — vrijednost ovog atributa

  • false — upotreba regularnog izraza prilikom pretraživanja

  • true — znači da će pretraga biti obavljena zanemarujući velika i mala slova, odnosno neosjetljivo na velika i mala slova

Kao primjer, koristili smo pravilo koje identifikuje neispravne, sa sigurnosne tačke gledišta, postavke mrežne veze u Androidu koje omogućavaju komunikaciju sa serverom putem HTTP protokola. Primjer postavke koja sadrži atribut cleartextTrafficPermitted sa značenjem 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>

Zadatak: Ograničite rezultate prema imenu/putanju datoteke

rješenje: U jednom od velikih projekata vezanih za razvoj mobilne aplikacije za Android, naišli smo na lažne pozitivne rezultate pravila koje određuje postavku zamagljivanja. Činjenica je da pravilo izvan okvira traži u datoteci build.gradle postavka odgovorna za primjenu pravila zamagljivanja za izdanu verziju aplikacije.

Ali u velikim projektima ponekad postoje podređeni fajlovi build.gradle, koji se odnose na biblioteke uključene u projekat. Posebnost je u tome što čak i ako ove datoteke ne ukazuju na potrebu za zamagljivanjem, postavke roditeljske datoteke sklopa će biti primijenjene tokom kompilacije.

Dakle, zadatak je odsjeći okidače u podređenim datotekama koje pripadaju bibliotekama. Mogu se prepoznati po prisutnosti linije apply 'com.android.library'.

Primjer koda iz datoteke build.gradle, što određuje potrebu za zamagljivanjem:

apply plugin: 'com.android.application'

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

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

dependencies {
  ...
}

Primjer datoteke build.gradle za biblioteku uključenu u projekat koja nema ovu postavku:

apply plugin: 'android-library'

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

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

I pravilo za Checkmarx:

ProGuardObfuscationNotInUse

// Поиск метода release среди всех методов в Gradle файлах
CxList releaseMethod = Find_Gradle_Method("release");

// Все объекты из файлов build.gradle
CxList gradleBuildObjects = Find_Gradle_Build_Objects();

// Поиск того, что находится внутри метода "release" среди всех объектов из файлов build.gradle
CxList methodInvokesUnderRelease = gradleBuildObjects.FindByType(typeof(MethodInvokeExpr)).GetByAncs(releaseMethod);

// Ищем внутри gradle-файлов строку "com.android.library" - это значит, что данный файл относится к библиотеке и его необходимо исключить из правила
CxList android_library = gradleBuildObjects.FindByName("com.android.library");

// Инициализация пустого массива
List<string> libraries_path = new List<string> {};

// Проходим через все найденные "дочерние" файлы
foreach(CxList library in android_library)
{
    // Получаем путь к каждому файлу
	string file_name_library = library.GetFirstGraph().LinePragma.FileName;
    
    // Добавляем его в наш массив
	libraries_path.Add(file_name_library);
}

// Ищем все вызовы включения обфускации в релизных настройках
CxList minifyEnabled = methodInvokesUnderRelease.FindByShortName("minifyEnabled");

// Получаем параметры этих вызовов
CxList minifyValue = gradleBuildObjects.GetParameters(minifyEnabled, 0);

// Ищем среди них включенные
CxList minifyValueTrue = minifyValue.FindByShortName("true");

// Немного магии, если не нашли стандартным способом :D
if (minifyValueTrue.Count == 0) {
	minifyValue = minifyValue.FindByAbstractValue(abstractValue => abstractValue is TrueAbstractValue);
} else {
    // А если всё-таки нашли, то предыдущий результат и оставляем
	minifyValue = minifyValueTrue;	
}

// Если не нашлось таких методов
if (minifyValue.Count == 0)
{
    // Для более корректного отображения места срабатывания в файле ищем или buildTypes или android
	CxList tempResult = All.NewCxList();
	CxList buildTypes = Find_Gradle_Method("buildTypes");
	if (buildTypes.Count > 0) {
		tempResult = buildTypes;
	} else {
		tempResult = Find_Gradle_Method("android");
	}
	
	// Для каждого из найденных мест срабатывания проходим и определяем, дочерний или основной файлы сборки
	foreach(CxList res in tempResult)
	{
        // Определяем, в каком файле был найден buildType или android методы
		string file_name_result = res.GetFirstGraph().LinePragma.FileName;
        
        // Если такого файла нет в нашем списке "дочерних" файлов - значит это основной файл и его можно добавить в результат
		if (libraries_path.Contains(file_name_result) == false){
			result.Add(res);
		}
	}
}

Ovaj pristup može biti prilično univerzalan i koristan ne samo za Android aplikacije, već i za druge slučajeve kada trebate utvrditi pripada li rezultat određenoj datoteci.

Zadatak: Dodajte podršku za biblioteku treće strane ako sintaksa nije u potpunosti podržana

rješenje: Broj različitih okvira koji se koriste u procesu pisanja koda jednostavno je van granica. Naravno, Checkmarx ne zna uvijek za njihovo postojanje, a naš zadatak je da ga naučimo da shvati da određene metode pripadaju upravo ovom okviru. Ponekad je ovo komplikovano činjenicom da okviri koriste imena funkcija koja su vrlo česta i nemoguće je nedvosmisleno odrediti odnos određenog poziva prema određenoj biblioteci.

Poteškoća je u tome što sintaksa takvih biblioteka nije uvijek ispravno prepoznata i morate eksperimentirati kako biste izbjegli veliki broj lažnih pozitivnih rezultata. Postoji nekoliko opcija za poboljšanje preciznosti skeniranja i rješavanje problema:

  • Prva opcija, pouzdano znamo da se biblioteka koristi u određenom projektu i možemo primijeniti pravilo na nivou tima. Ali ako tim odluči zauzeti drugačiji pristup ili koristi nekoliko biblioteka u kojima se imena funkcija preklapaju, možemo dobiti ne baš ugodnu sliku brojnih lažnih pozitivnih rezultata.

  • Druga opcija je traženje datoteka u kojima je biblioteka jasno uvezena. Ovim pristupom možemo biti sigurni da se biblioteka koja nam je potrebna tačno koristi u ovoj datoteci.

  • I treća opcija je korištenje dva gornja pristupa zajedno.

Kao primjer, pogledajmo biblioteku koja je dobro poznata u uskim krugovima sličan za programski jezik Scala, odnosno funkcionalnost Spajanje doslovnih vrijednosti. Općenito, za prosljeđivanje parametara u SQL upit, morate koristiti operator $, koji zamjenjuje podatke u unaprijed oblikovani SQL upit. To je, u stvari, direktan analog Prepared Statement-a u Javi. Ali, ako trebate dinamički konstruirati SQL upit, na primjer, ako trebate proslijediti nazive tablica, možete koristiti operator #$, koji će direktno zamijeniti podatke u upitu (skoro kao konkatenacija nizova).

Primjer koda:

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

Checkmarx još ne zna kako otkriti upotrebu literalnih vrijednosti spajanja i preskače operatore #$, pa pokušajmo ga naučiti da identificira potencijalne SQL injekcije i istakne prava mjesta u kodu:

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

Zadatak: Potražite korištene ranjive funkcije u bibliotekama otvorenog koda

rješenje: Mnoge kompanije koriste alate za praćenje otvorenog koda (OSA praksa) da otkriju upotrebu ranjivih verzija biblioteka u razvijenim aplikacijama. Ponekad nije moguće ažurirati takvu biblioteku na sigurnu verziju. U nekim slučajevima postoje funkcionalna ograničenja, u drugima uopće ne postoji sigurna verzija. U ovom slučaju, kombinacija SAST i OSA praksi pomoći će da se utvrdi da se funkcije koje dovode do iskorištavanja ranjivosti ne koriste u kodu.

Ali ponekad, posebno kada se uzme u obzir JavaScript, ovo možda nije sasvim trivijalan zadatak. Ispod je rješenje, možda nije idealno, ali ipak funkcionira, koristeći primjer ranjivosti u komponenti lodash u metodama template и *set.

Primjeri testiranja potencijalno ranjivog koda u JS datoteci:

/**
 * Template example
 */

'use strict';
var _ = require("./node_modules/lodash.js");


// Use the "interpolate" delimiter to create a compiled template.
var compiled = _.template('hello <%= js %>!');
console.log(compiled({ 'js': 'lodash' }));
// => 'hello lodash!'

// Use the internal `print` function in "evaluate" delimiters.

var compiled = _.template('<% print("hello " + js); %>!');
console.log(compiled({ 'js': 'lodash' }));
// => 'hello lodash!'

I kada se povezujete direktno u 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>

Tražimo sve naše ranjive metode, koje su navedene u ranjivosti:

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

Zadatak: Traženje sertifikata ugrađenih u aplikaciju

rješenje: Nije neuobičajeno da aplikacije, posebno mobilne, koriste certifikate ili ključeve za pristup različitim serverima ili provjeru SSL-pininga. Iz sigurnosne perspektive, pohranjivanje takvih stvari u kodu nije najbolja praksa. Pokušajmo napisati pravilo koje će tražiti slične datoteke u spremištu:

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

Zadatak: Pronalaženje kompromitovanih tokena u aplikaciji

rješenje: Često je potrebno opozvati kompromitovane tokene ili druge važne informacije koje su prisutne u kodu. Naravno, njihovo pohranjivanje unutar izvornog koda nije dobra ideja, ali situacije se razlikuju. Zahvaljujući CxQL upitima, pronalaženje ovakvih stvari je prilično lako:

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

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

zaključak

Nadam se da će ovaj članak biti koristan onima koji počinju svoje upoznavanje s alatom Checkmarx. Možda će u ovom vodiču pronaći nešto korisno i oni koji već dugo pišu svoja pravila.

Nažalost, trenutno postoji nedostatak resursa na kojem bi se mogle izvući nove ideje tokom razvoja pravila za Checkmarx. Zato smo i kreirali spremište na Githubu, gdje ćemo objaviti naše radove kako bi svi koji koriste CxQL mogli pronaći nešto korisno u njemu, a također i imali priliku podijeliti svoj rad sa zajednicom. Repozitorijum je u procesu popunjavanja i strukturiranja sadržaja, tako da su saradnici dobrodošli!

Spasibo za vnimanie!

izvor: www.habr.com

Dodajte komentar