Kako napisati pravila za Checkmarx i ne poludeti

Bok, Habr!

U svom radu, naša tvrtka često se susreće s raznim alatima za statičku analizu koda (SAST). Iz kutije, svi oni rade prosječno. Naravno, sve ovisi o projektu i tehnologijama koje se koriste, kao i koliko su te tehnologije dobro pokrivene pravilima analize. Po mom mišljenju, jedan od najvažnijih kriterija prilikom odabira SAST alata je mogućnost prilagodbe alata specifičnostima vlastitih aplikacija, točnije pisanje i izmjena pravila analize ili, kako ih češće nazivaju, Custom Queries.

Kako napisati pravila za Checkmarx i ne poludeti

Najčešće koristimo Checkmarx — vrlo zanimljiv i moćan analizator koda. U ovom članku ću podijeliti svoje iskustvo u pisanju pravila analize za njega.

Sadržaj

Uvod

Za početak, preporučio bih jedan od rijetkih članaka na hrvatskom jeziku o osobitostima pisanja upita za Checkmarx. Objavljen je na Habr-u krajem 2019. godine pod naslovom: „Bok, Checkmarx!“. Kako napisati upit za Checkmarx SAST i pronaći super ranjivosti.

U njemu se detaljno razmatra kako napisati prve upite na jeziku CxQL (Checkmarx Query Language) za određenu testnu aplikaciju i prikazani su osnovni principi rada pravila analize.

Neću ponavljati ono što je opisano u njemu, iako će neka preklapanja ipak postojati. U svom članku ću pokušati sastaviti neki „priručnik recepata“, popis rješenja konkretnih zadataka s kojima sam se susretao tijekom svog rada s Checkmarxom. Ponekad sam se morao dobro namučiti s mnogim od tih zadataka. Ponekad nije bilo dovoljno informacija u dokumentaciji, a ponekad je bilo teško shvatiti kako napraviti ono što se traži. Nadam se da moje iskustvo i neprospavane noći neće biti uzaludni, te da će ovaj „priručnik Custom Queries recepata“ uštedjeti nekoliko sati ili par-troje živaca. Pa, krenimo!

Opće informacije o pravilima

Za početak, razmotrit ćemo nekoliko osnovnih pojmova i proces rada s pravilima, kako bismo bolje razumjeli što će se događati dalje. Također, ovo se spominje zbog toga što u dokumentaciji o tome nije rečeno ni približno jasno ili je jako raspršeno po strukturi, što nije baš praktično.

  1. Pravila se primjenjuju prilikom skeniranja, ovisno o odabranom početnom preset-u (skup aktivnih pravila). Možete stvoriti neograničen broj preset-a i način na koji ih strukturirate ovisi o specifičnostima vašeg procesa. Mogu se grupirati prema jezicima ili odvojiti preset-i za svaki projekt. Broj aktivnih pravila utječe na brzinu i točnost skeniranja.

    Kako napisati pravila za Checkmarx i ne poludetiPostavljanje Preset-a u sučelju Checkmarx

  2. Pravila se uređuju u posebnom alatu pod nazivom CxAuditor. To je desktop aplikacija koja se povezuje s poslužiteljem Checkmarx-a. Ovaj alat ima dva načina rada: uređivanje pravila i analiza rezultata već provedenog skeniranja.

    Kako napisati pravila za Checkmarx i ne poludetiSučelje CxAudit

  3. Pravila u Checkmarx-u su podijeljena po jezicima, što znači da za svaki jezik postoji vlastiti skup upita. Također postoje neka opća pravila koja se primjenjuju neovisno o jeziku, to su takozvani osnovni upiti. U većini slučajeva, osnovni upiti sadrže pretraživanje informacija koje koriste druga pravila.

    Kako napisati pravila za Checkmarx i ne poludetiPodjela pravila prema jezicima

  4. Pravila mogu biti 'Executable' i 'Non-Executable' (Izvršna i Neizvršna). Naziv možda nije potpuno točan, ali to je ono što imamo. Bit je u tome da će rezultat izvršavanja 'Executable' pravila biti prikazan u rezultatima skeniranja u UI-u, dok 'Non-Executable' pravila služe samo za korištenje njihovih rezultata u drugim upitima (u suštini — samo funkcija).

    Kako napisati pravila za Checkmarx i ne poludetiOdređivanje tipa pravila pri kreiranju

  5. Možete kreirati nova pravila ili dopuniti/prepisati postojeća. Da biste prepisali pravilo, potrebno je pronaći ga u stablu, desnom tipkom miša kliknuti i u padajućem izborniku odabrati opciju 'Override'. Ovdje je važno napomenuti da nova pravila isprva nisu uključena u preset-e i nisu aktivna. Da biste ih počeli koristiti, potrebno ih je aktivirati u izborniku 'Preset Manager' u alatu. Prepisana pravila zadržavaju svoje postavke, što znači da, ako je pravilo bilo aktivno, takvo će i ostati i primjenjivati se odmah.

    Kako napisati pravila za Checkmarx i ne poludetiPrimjer nove pravila u sučelju Preset Manager

  6. Tijekom izvršenja gradi se „stablo“ upita, ovisno o tome što od čega ovisi. Prvo se izvršavaju pravila koja prikupljaju informacije, a zatim ona koja ih koriste. Rezultat izvršenja se kešira, tako da, ako postoji mogućnost upotrebe rezultata postojećeg pravila, bolje je to učiniti, što će smanjiti vrijeme skeniranja.

  7. Pravila se mogu primjenjivati na različitim razinama:

  • Za cijeli sustav — bit će korišteno za svako skeniranje bilo kojeg projekta

  • Na razini tima (Team) — bit će primjenjivano samo za skeniranje projekata u odabranom timu.

  • Na razini projekta — bit će primjenjivano u konkretnom projektu

    Kako napisati pravila za Checkmarx i ne poludetiOdređivanje razine na kojoj će se pravilo primjenjivati

„Rječnik“ za početnike

I počet ću s nekoliko stvari koje su mi postavile pitanja, kao i pokazati niz trikova koji će značajno olakšati život.

Operacije sa popisima

- oduzimanje jednog od drugog (list2 - list1)
* presjek lista (list1 * list2)
+ zbrajanje listi (list1 + list2)

& (logičko I) - spaja liste prema podudaranju (list1 & list2), slično presjeku (list1 * list2)
| (logičko ILI) - spaja liste prema širokoj pretrazi (list1 | list2)

Ne radi s listama:  ^  &&  ||  %  \/ 

Svi pronađeni elementi

Unutar skeniranog jezika može se dobiti popis apsolutno svih elemenata koje je odredio Checkmarx (nizovi, funkcije, klase, metode itd.). To je određeni prostor objekata kojima se može pristupiti putem Sve. Dakle, za pretraživanje objekta s određenim nazivom searchMe, može se napraviti pretraživanje, na primjer, po imenu kroz sve pronađene objekte:

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

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

Ali, ako je potrebno izvršiti pretraživanje na drugom jeziku, koji iz nekog razloga nije bio uključen u skeniranje (na primjer groovy u projektu za Android), možemo proširiti naš prostor objekata preko varijable:

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

Funkcije za analizu Flow

Ove funkcije se koriste u mnogim pravilima i evo male bilješke o tome što znače:

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

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

Dobivanje imena/putanje datoteke

Postoji nekoliko atributa koje možemo dobiti iz rezultata izvršenja upita (ime datoteke u kojoj je pronađeno pojavljivanje, redak itd.), ali kako ih dobiti i koristiti u dokumentaciji nije rečeno. Dakle, kako bismo to učinili, potrebno je obratiti se svojstvu LinePragma i unutar njega će se nalaziti potrebni objekti:

// Для примера найдем все методы
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 imati na umu da FileName zapravo sadrži putanju do datoteke, budući da smo koristili metodu GetFirstGraph.

Rezultat izvršenja

Unutra CxQL-a postoji posebna varijabla result, koja vraća rezultat izvršavanja vašeg napisanog pravila. Ona se inicijalizira odmah i možete zapisivati privremene rezultate, mijenjajući i precizirajući ih u procesu rada. Međutim, ako unutar pravila ne bude dodeljivanja ovoj varijabli ili funkciji return— rezultat izvršavanja uvijek će biti nulti.

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

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

Ali, ako dodelimo rezultat izvršavanja magičnoj varijabli result — vidjet ćemo što nam vraća ovaj poziv:

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

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

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

Korištenje rezultata izvršenja drugih pravila

Pravila u Checkmarx-u mogu se nazvati analognim funkcijama u običnom programskom jeziku. Prilikom pisanja pravila, sasvim možete koristiti rezultate drugih upita. Na primjer, nema potrebe svaki put pretraživati sve pozive metoda u kodu, dovoljno je pozvati odgovarajuće pravilo:

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

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

Ovakav pristup omogućuje skraćivanje koda i značajno smanjenje vremena izvršavanja pravila.

Rješavanje problema

Logiranje

Prilikom rada s alatom, ponekad ne uspijevate odmah napisati potreban upit i morate eksperimentirati, isprobavajući različite varijante. Za takve slučajeve u alatu je predviđeno evidentiranje, koje se poziva na sljedeći način:

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

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

Ali treba imati na umu da ovaj metod prihvaća samo string, tako da se ne može dobiti potpuni popis pronađenih elemenata kao rezultat prve operacije. Druga opcija koja se koristi za otklanjanje grešaka — povremeno dodeljivati magičnoj varijabli result rezultat izvršavanja upita i vidjeti što će ispasti. Ovakav pristup nije previše praktičan, potrebno je biti siguran da nakon toga u kodu nema ponovnog definisanja ili operacija s tim result ili jednostavno komentirati kod koji se nalazi ispod. Ili, kao ja, zaboraviti ukloniti nekoliko takvih poziva iz gotovog pravila i pitati se zašto ništa ne funkcioniše.

Prikladniji način — jest pozvati metodu return s potrebnim parametrima. U tom slučaju, izvršavanje pravila će završiti i moći ćemo vidjeti što smo dobili kao rezultat onoga što smo napisali:

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

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

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

Problem s prijavom

Ponekad se dogodi da ne možete pristupiti alatu CxAudit (koji se koristi za pisanje pravila). Razloga za to može biti mnogo, poput nenadanog zatvaranja, iznenadne nadogradnje Windows-a, BSOD-a i drugih nepredviđenih situacija na koje nemamo utjecaj. U takvim slučajevima ponekad ostane nezavršena sesija u bazi podataka, koja onemogućava ponovni pristup. Da bi se to ispravilo, potrebno je izvršiti nekoliko upita:

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

Pisanje pravila

Evo stigli smo do najzanimljivijeg. Kada počnete pisati pravila u CxQL, često nedostaje više ne toliko dokumentacije koliko živih primjera rješavanja određenih zadataka i opisa procesa rada upita u cjelini.

Pokušat ću malo olakšati život onima koji počinju uranjati u jezik upita i navesti nekoliko primjera korištenja Custom Queries za rješavanje određenih zadatka. Neki od njih su dovoljno opći i mogu se primijeniti u vašoj kompaniji praktički bez izmjena, dok su drugi specifičniji, ali se također mogu koristiti uz prilagođavanje koda prema specifičnostima vaših aplikacija.

Dakle, s kojim se zadacima najčešće susrećemo:

Zadatak: U rezultatima izvršenja pravila ima nekoliko Flow-a, a jedan od njih je ugniježden unutar drugog, potrebno je ostaviti samo jedan od njih.

Rešenje: Zapravo, ponekad Checkmarx prikazuje nekoliko Flow-ova kretanja podataka, koji se mogu preklapati i biti skraćena verzija drugih. Za takve slučajeve postoji posebna metoda ReduceFlow. Ovisno o parametru, odabrati će najkraći ili najduži Flow:

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

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

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

Rešenje: U Checkmarxu postoje osnovna pravila, a rezultati njihova izvršenja koriste mnogi drugi upiti. Dodavanjem nekih takvih pravila podacima specifičnim za vašu aplikaciju, možete odmah poboljšati rezultate skeniranja. Ispod je primjer pravila s kojim možete započeti:

General_privacy_violation_list

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

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

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

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

Zadatak: Proširite popis varijabli s lozinkama

Rešenje: Preporučio bih odmah obratiti pozornost na osnovno pravilo za definiranje lozinki u kodu i dodati popis imena varijabli koje je uobičajeno koristiti u vašoj tvrtki.

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

// Dopunjavamo zadani popis varijabli
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);
	}
}

Zadatak: Dodajte korištene okvire koji nisu podržani Checkmarx

Rešenje: Svi upiti u Checkmarxu podijeljeni su prema jezicima, tako da je potrebno nadopuniti pravila za svaki jezik. Ispod su neki primjeri takvih pravila.

Ako se koriste biblioteke koje dopunjuju ili zamjenjuju standardnu funkcionalnost — lako ih je dodati u osnovno pravilo. Tada će svi koji ga koriste odmah saznati o novim informacijama. Kao primjer, biblioteke za evidentiranje u Androidu — Timber i Loggi. U osnovnom skupu pravila za određivanje nesistemskih poziva nema, tako da, ako lozinka ili identifikator sjednice završi u logu, nećemo o tome saznati. Pokušat ćemo dodati pravila Checkmarx za određivanje takvih metoda.

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 omogućuje dodavanje definicije poziva metoda Timber, kao točke izlaza podataka iz aplikacije:

FindAndroidOutputs

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

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

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

Također, moguće je dodati susjedno pravilo, ali koje se odnosi izravno na logiranje u Androidu:

FindAndroidLog_Outputs

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

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

Također, ako se u Android aplikacijama koristi WorkManager za asinkroni rad, dobro bi bilo dodatno obavijestiti Checkmarx dodavanjem metode za preuzimanje podataka iz zadatka getInputData:

FindAndroidRead

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

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

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

Zadatak: Pretraživanje osjetljivih podataka u plist za iOS projekte

Rešenje: Često se za pohranu raznih varijabli i vrijednosti u iOS koriste posebne datoteke s ekstenzijom .plist. Pohrana lozinki, tokena, ključeva i drugih osjetljivih podataka u tim datotekama nije preporučljiva, jer se bez problema mogu izdvojiti s uređaja.

Plist datoteke imaju osobitosti koje nisu očite golim okom, ali su važne za Checkmarx. Napisat ćemo pravilo koje će pretraživati potrebne podatke i obavijestiti nas ako se negdje spominju lozinke ili tokeni.

Primjer takve datoteke u kojoj je ugrađen token za komunikaciju s backend servisom:

DeviceDictionary
	
		phone
		iPhone 6s
	
	privatekey
	MIICXAIBAAKBgQCqGKukO1De7zhZj6+

I pravilo za Checkmarx, u kojem postoji 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: Pretraživanje informacija u XML-u

Rešenje: U Checkmarxu postoje vrlo korisne funkcije za rad s XML-om i pretraživanje vrijednosti, oznaka, atributa i sl. No, nažalost, u dokumentaciji je napravljena greška zbog koje nijedan primjer ne radi. Iako je taj nedostatak ispravljen u posljednjoj verziji dokumentacije — budite oprezni ako koristite ranije verzije dokumenata.

Evo pogrešan primjer iz dokumentacije:

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

Kao rezultat pokušaja izvršenja, dobit ćemo grešku da se Sve ne postoji takva metoda… I to je točno, jer za korištenje funkcija za rad s XML-om postoji posebno, odvojeno mjesto objekata — cxXPath. Ovo je ispravni upit za pronalaženje postavke u Androidu koja dopušta korištenje HTTP prometa:

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

Pogledajmo malo detaljnije, budući da je sintaksa svih funkcija slična; nakon što se razumije jedna, daljnje je potrebno samo odabrati onu koja odgovara. Dakle, redom po parametrima:

  • "*.xml"— maska datoteka prema kojima se treba izvršiti pretraživanje

  • 8 — id jezika za koji se pravilo primjenjuje

  • "cleartextTrafficPermitted"— naziv atributa u xml-u

  • "true" — vrijednost ovog atributa

  • false — korištenje regularnog izraza tijekom pretraživanja

  • true — označava da će pretraživanje biti izvedeno bez obzira na velika i mala slova, odnosno bez obzira na veličinu slova

Kao primjer korišteno je pravilo koje definira neispravne, s aspekta sigurnosti, postavke mrežne povezanosti u Androidu koje dopuštaju komunikaciju sa serverom preko HTTP protokola. Primjer postavke koja sadrži atribut cleartextTrafficPermitted s vrijednošću true:

example.com
        
            
        
        
            secure.example.com

Zadatak: Ograniči rezultate po imenu/putanji datoteke

Rešenje: U jednom od većih projekata vezanih uz razvoj mobilne aplikacije za Android, suočili smo se s lažnim alarmima pravila koje definira postavku obfuskacije. Naime, pravilo iz kutije traži u datoteci build.gradle postavku koja je odgovorna za primjenu pravila obfuskacije za izdanu verziju aplikacije.

No, u većim projektima ponekad se nalaze poddatoteke build.gradle, koje pripadaju bibliotekama uključenim u projekt. Osobitost je ta da, čak i ako u tim datotekama nije navedena potreba za obfuskacijom, pri kompilaciji će se primijeniti postavke nadređene datoteke gradnje.

Tako, zadatak se postavlja u odsijecanju aktivacija u poddatotekama koje pripadaju bibliotekama. Mogu se odrediti prema prisutnosti retka apply 'com.android.library'.

Primjer koda iz datoteke build.gradle, koji definira potrebu za obfuskacijom:

primijeniti plugin: 'com.android.application'

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

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

dependencies {
  ...
}

Primjer datoteke build.gradle za biblioteku integriranu u projekt koja nema takvu postavku:

primijeniti plugin: 'android-library'

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

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

I pravilo za Checkmarx:

ProGuardObfuscationNotInUse

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

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

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

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

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

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

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

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

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

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

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

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

Zadatak: Dodati podršku za vanjsku biblioteku, ako se sintaksa ne podržava u potpunosti

Rešenje: Broj različitih frameworka koji se koriste tijekom pisanja koda jednostavno je ogroman. Naravno, Checkmarx ne zna uvijek za njihovo postojanje i naš zadatak je naučiti ga da razumije da određene metode pripadaju tom frameworku. Ponekad to otežava činjenica da frameworki koriste nazive funkcija koji su veoma rasprostranjeni i ne može se nedvosmisleno utvrditi koje se pozive odnose na određenu biblioteku.

Teškoća leži u tome što se sintaksa takvih biblioteka ne prepoznaje uvijek ispravno, pa je potrebno eksperimentirati kako bismo izbjegli veliki broj lažnih dohvata. Postoji nekoliko načina za poboljšanje točnosti skeniranja i rješavanje postavljenog zadatka:

  • Prva opcija, znamo da se biblioteka koristi u određenom projektu i možemo primijeniti pravilo na razini tima. No, u slučaju da tim odluči koristiti drugi pristup ili koristi nekoliko biblioteka u kojima se preklapaju nazivi funkcija, možemo dobiti neuglednu sliku zbog brojnih lažnih dohvata.

  • Druga opcija, primijeniti pretraživanje po datotekama u kojima se jasno vrši uvoz biblioteke. Ovim pristupom možemo biti sigurni da se u ovoj datoteci sigurno koristi potrebna biblioteka.

  • I treća opcija, koristiti oba gore navedena pristupa zajedno.

Kao primjer razmotrit ćemo poznatu biblioteku u uskim krugovima slick za programski jezik Scala, a posebno, funkcionalnost Splicing Literal Values. Općenito, za prijenos parametara u SQL upit potrebno je koristiti operator $, koji umetne podatke u prethodno oblikovani SQL upit. Drugim riječima, zapravo je izravan analog Prepared Statement-a u Javi. Međutim, u slučaju da je potrebno dinamički konstruirati SQL upit, na primjer, ako treba prenijeti imena tablica, može se koristiti operator #$, koji izravno umeće podatke u upit (praktički, poput konkatenacije nizova).

Primjer koda:

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

Checkmarx trenutno ne može odrediti korištenje Splicing Literal Values i propušta operatore #$, pa ćemo pokušati naučiti ga odrediti potencijalne SQL injekcije i istaknuti potrebna 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: Pretraga korištenih ranjivih funkcija u Open-Source bibliotekama

Rešenje: U mnogim tvrtkama koriste se alati za kontrolu Open-Source-a (praksa OSA), koji omogućuju otkrivanje korištenja 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, a u drugim nema ni sigurne verzije. U tom slučaju pomaže kombinacija praksi SAST i OSA, koja omogućuje određivanje da funkcije koje dovode do eksploatacije ranjivosti nisu korištene u kodu.

Ali ponekad, posebno kada je riječ o JavaScript-u, to može biti prilično ne trivijalni zadatak. Ispod je rješenje, možda ne savršeno, ali svejedno funkcionalno, na primjeru ranjivosti u komponenti lodash u metodama predložak i *set.

Primjeri testnog 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 izravno poveže u html:

<!DOCTYPE html>
<html>
<head>
    <title>Lodash Tutorijal</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 ranjivostima:

// Ищем все строки: в которых встречается строка 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: Pretraga hardkodiranih certifikata u aplikaciji

Rešenje: Često aplikacije, posebno mobilne, koriste certifikate ili ključeve za pristup raznim poslužiteljima ili provjeru SSL-Pinning-a. Gledajući iz perspektive sigurnosti - čuvanje ovakvih stvari u kodu nije najbolja praksa. Pokušat ćemo napisati pravilo koje će tražiti takve datoteke u 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;

Zadatak: Pretraga kompromitiranih tokena u aplikaciji

Rešenje: Često se događa da je potrebno opozvati kompromitirane tokene ili druge važne informacije koje se nalaze u kodu. Naravno, čuvanje njih unutar izvora nije najbolja ideja, ali situacije su različite. Zahvaljujući CxQL upitima, lako je pronaći takve stvari:

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

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

Zaključak

Nadam se da će ova članka biti korisna onima koji započinju svoje upoznavanje s alatom Checkmarx. Moguće je da će i oni koji već dugo pišu svoja pravila pronaći nešto korisno u ovom vodiču.

Nažalost, trenutno nedostaje resursa gdje bi se moglo dobiti novih ideja tijekom razvoja pravila za Checkmarx. Stoga smo stvorili repozitorij na Githubu, gdje ćemo objavljivati svoje radove, tako da svatko tko koristi CxQL može pronaći nešto korisno, a također ima priliku podijeliti svoje radove s zajednicom. Repozitorij je u procesu popunjavanja i strukturiranja sadržaja, tako da su doprinosi dobrodošli!

Hvala na pažnji!

Izvor: habr.com

Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere 🔥 Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere | ProHoster