Ahoj, Habr!
Ve své práci se naše společnost velmi často setkává s různými nástroji pro statickou analýzu kódu (SAST). Tyto nástroje obvykle fungují průměrně. Samozřejmě, všechno závisí na projektu a technologiích, které v něm používáme, a také na tom, jak dobře tyto technologie pokrývají pravidla analýzy. Můj názor je, že jedním z nejdůležitějších kritérií při výběru nástroje SAST je možnost přizpůsobit ho specifikům našich aplikací, konkrétně psát a měnit pravidla analýzy, nebo jak se často nazývají, vlastní dotazy.

Nejčastěji používáme Checkmarx — velmi zajímavý a mocný analýzátor kódu. V tomto článku se podělím o své zkušenosti s psaním pravidel analýzy pro něj.
Obsah
Úvod
Na začátek bych chtěl doporučit jeden z mála článků v češtině o zvláštnostech psaní dotazů pro Checkmarx. Byl publikován na Habré na konci roku 2019 pod názvem: .
V něm je podrobně popsáno, jak napsat první dotazy v jazyce CxQL (Checkmarx Query Language) pro určitou testovací aplikaci a jsou ukázány základní principy fungování pravidel analýzy.
Nebudu opakovat to, co je v něm popsáno, i když určitá překryvy se přesto objeví. Ve svém článku se pokusím sestavit určitý „sborník receptů“, seznam řešení konkrétních úloh, se kterými jsem se setkal během své práce s Checkmarx. U mnoha z těchto úloh jsem musel hodně přemýšlet. Občas chyběly údaje v dokumentaci, jindy bylo těžké pochopit, jak udělat to, co je potřeba. Doufám, že moje zkušenosti a probdělé noci nejsou k ničemu a tento „sborník vlastních dotazů“ vám ušetří několik hodin nebo pár nervových buněk. A teď začneme!
Obecné informace o pravidlech
Nejdříve si povíme o několika základních pojmech a o procesu práce s pravidly, aby bylo lépe pochopit, co se bude dít dál. A také proto, že to není v dokumentaci nikde zmíněno nebo je to silně rozptýleno po struktuře, což není příliš pohodlné.
Pravidla se aplikují při skenování v závislosti na vybraném preset (soubor aktivních pravidel) na začátku. Můžete vytvořit neomezený počet presetů a jak je strukturovat, závisí na specifikách vašeho procesu. Mohou být seskupena podle jazyků nebo lze vyčlenit presety pro jednotlivé projekty. Počet aktivních pravidel ovlivňuje rychlost a přesnost skenování.
Nastavení presetů v rozhraní CheckmarxPravidla se upravují ve speciálním nástroji nazvaném CxAuditor. Je to desktopová aplikace, která se připojuje k serveru s Checkmarx. Tento nástroj má dva režimy: úprava pravidel a analýza výsledků již provedeného skenování.
Rozhraní CxAuditPravidla v Checkmarxu jsou rozdělena podle jazyků, což znamená, že pro každý jazyk existuje specifická sada dotazů. Také existují některá obecná pravidla, která se aplikují bez ohledu na jazyk, známá jako základní dotazy. Většinou základní dotazy obsahují vyhledávání informací, které využívají jiná pravidla.
Rozdělení pravidel podle jazykůPravidla bývají „Executable“ a „Non-Executable“ (Prováděcí a Nepřeváděcí). Není to úplně správný název, podle mého názoru, ale co je. Podstata je v tom, že výsledek provedení „Executable“ pravidel bude zobrazen v výsledcích skenování v uživatelském rozhraní, zatímco „Non-Executable“ pravidla jsou potřebná pouze pro využití jejich výsledků v jiných dotazech (v podstatě – jen funkce).
Určení typu pravidla při vytvářeníNová pravidla lze vytvářet nebo doplňovat/přepisovat stávající. Aby bylo možné pravidlo přepsat, je třeba jej najít ve stromu, kliknout pravým tlačítkem myši a v místním menu vybrat položku „Override“. Je důležité mít na paměti, že nová pravidla nejsou zpočátku zahrnuta v presetech a nejsou aktivní. Aby bylo možné začít je používat, je nutné je aktivovat v menu „Preset Manager“ v nástroji. Přepsaná pravidla si uchovávají svá nastavení, což znamená, že pokud bylo pravidlo aktivní, takové zůstane a bude aplikováno ihned.
Příklad nového pravidla v rozhraní správce předvolebBěhem provádění se vytváří „strom“ dotazů, kde závisí, co na čem. Nejprve se provádějí pravidla, která shromažďují informace, a poté ta, která je používají. Výsledek provádění se ukládá do cache, takže pokud je možné použít výsledky existujícího pravidla, je lepší to tak udělat, což sníží čas skenování.
Pravidla lze aplikovat na různých úrovních:
Pro celý systém — bude použito pro jakékoli skenování jakéhokoli projektu
Na úrovni týmu (Team) — bude aplikováno pouze pro skenování projektů ve vybraném týmu.
Na úrovni projektu — bude aplikováno v konkrétním projektu
Určení úrovně, na které bude pravidlo aplikováno
„Slovník“ pro začátečníky
A začnu několika věcmi, které mi dělaly starosti, a také ukážu několik triků, které podstatně usnadní život.
Operace se seznamy
- odečtení jednoho od druhého (list2 - list1)
* průnik seznamů (list1 * list2)
+ součet seznamů (list1 + list2)
& (logické A) - spojuje seznamy na základě shody (list1 & list2), obdobně jako průnik (list1 * list2)
| (logické NEBO) - spojuje seznamy na základě širokého vyhledávání (list1 | list2)
Se seznamy nepracuje: ^ && || % / Všechny nalezené prvky
V rámci skenovaného jazyka lze získat seznam naprosto všech prvků, které definoval Checkmarx (řetězce, funkce, třídy, metody atd.). To je určité prostorové objekty, na které se lze odkazovat přes All. To znamená, že pro vyhledání objektu s konkrétním názvem searchMe, lze provést vyhledávání například podle jména ve všech nalezených objektech:
// Такой запрос выдаст все элементы
result = All;
// Такой запрос выдаст все элементы, в имени которых присутствует “searchMe“
result = All.FindByName("searchMe");Ale pokud potřebujete provést vyhledávání v jiném jazyce, který z nějakého důvodu nebyl zahrnut do skenování (například groovy v projektu pro Android), můžete rozšířit náš prostor objektů přes proměnnou:
result = AllMembers.All.FindByName("searchMe");Funkce pro analýzu Flow
Tyto funkce se používají v mnoha pravidlech a zde je malá příručka, co znamenají:
// Какие данные second влияют на first.
// Другими словами - ТО (second) что влияет на МЕНЯ (first).
result = first.DataInfluencedBy(second);
// Какие данные first влияют на second.
// Другими словами - Я (first) влияю на ТО (second).
result = first.DataInfluencingOn(second);Získání jména/cesty souboru
Existuje několik atributů, které lze získat z výsledků provádění dotazu (název souboru, ve kterém bylo nalezeno výskyt, řádek atd.), ale jak je získat a použít, v dokumentaci není uvedeno. Takže k tomu, abychom to udělali, je potřeba se obrátit na vlastnost LinePragma a uvnitř ní budou potřebné objekty:
// Для примера найдем все методы
CxList methods = Find_Methods();
// В методах найдем по имени метод scope
CxList scope = methods.FindByName("scope");
// Таким образом можо получить путь к файлу
string current_filename = scope.GetFirstGraph().LinePragma.FileName;
// А вот таким - строку, где нашлось срабатывание
int current_line = scope.GetFirstGraph().LinePragma.Line;
// Эти параметры можно использовать по разному
// Например получить все объекты в файле
CxList inFile = All.FindByFileName(current_filename);
// Или найти что происходит в конкретной строке
CxList inLine = inFile.FindByPosition(current_line);Je dobré mít na paměti, že FileName ve skutečnosti obsahuje cestu k souboru, protože jsme použili metodu GetFirstGraph.
Výsledek provedení
Uvnitř CxQL je k dispozici speciální proměnná result, která vrací výsledek provedení vašeho napsaného pravidla. Je inicializována hned na začátku a můžete do ní zapisovat mezivýsledky, měnit a upřesňovat je během procesu práce. Pokud však v pravidle nebude přiřazení této proměnné nebo funkce vrátit— výsledek provedení bude vždy nulový.
Následující dotaz nám nic nevrátí a vždy bude prázdný:
// Находим элементы foo
CxList libraries = All.FindByName("foo");Ale přiřazením výsledku provedení k magické proměnné result uvidíme, co nám tento příkaz vrací:
// Находим элементы foo
CxList libraries = All.FindByName("foo");
// Выводим, как результат выполнения правила
result = libraries
// Или еще короче
result = All.FindByName("foo");Využití výsledků provedení jiných pravidel
Pravidla v Checkmarx lze nazvat analogií funkcí v běžném programovacím jazyce. Při psaní pravidla můžete klidně použít výsledky jiných dotazů. Například, není potřeba znovu a znovu hledat všechny volání metod v kódu, stačí zavolat potřebné pravidlo:
// Получаем результат выполнения другого правила
CxList methods = Find_Methods();
// Ищем внутри метод foo.
// Второй параметр false означает, что ищем без чувствительности к регистру
result = methods.FindByShortName("foo", false);Takový přístup umožňuje zkrátit kód a výrazně zkrátit čas provádění pravidla.
Řešení problémů
Logování
Při práci s nástrojem se občas nedaří okamžitě napsat potřebný dotaz a je nutné experimentovat a vyzkoušet různé varianty. V takovém případě je v nástroji k dispozici logování, které se aktivuje následujícím způsobem:
// Находим что-то
CxList toLog = All.FindByShortName("log");
// Формируем строку и отправляем в лог
cxLog.WriteDebugMessage (“number of DOM elements =” + All.Count);Ale je třeba mít na paměti, že tento metod přijímá pouze řetězec, takže není možné získat úplný seznam nalezených prvků jako výsledek první operace. Druhou variantou, která se používá pro ladění, je občas přiřadit magické proměnné result výsledek provedení dotazu a sledovat, co dostaneme. Tento přístup není příliš pohodlný, je třeba být si jistý, že v kódu následně nedochází k přepsání nebo operacím s tímto result nebo jednoduše komentovat níže umístěný kód. Nebo se jako já může zapomenout odstranit z hotového pravidla několik takových volání a pak se divit, proč to nefunguje.
Pohodlnější způsob je zavolat metodu vrátit s potřebným parametrem. V takovém případě provedení pravidla skončí a my uvidíme, co vzniklo jako výsledek našeho psaní:
// Находим что-то
CxList toLog = All.FindByShortName("log");
// Выводим результат выполнения
return toLog
//Все, что написано дальше не будет выполнено
result = All.DataInfluencedBy(toLog)Problém s přihlášením
Někdy nastanou situace, kdy se nelze dostat do nástroje CxAudit (který se používá pro vytváření pravidel). Důvodů může být mnoho, jako například havarijní ukončení práce, náhlá aktualizace Windows, BSOD a další nepředvídané situace, na které nemáme vliv. V takovém případě někdy zůstane nezavřená relace v databázi, která brání opětovnému přihlášení. Pro opravu je třeba provést několik dotazů:
Pro Checkmarx do 8.6:
// Проверяем, что есть залогиненые пользователи, выполнив запрос в БД
SELECT COUNT(*) FROM [CxDB].[dbo].LoggedinUser WHERE [ClientType] = 6;
// Если что-то есть, а на самом деле даже если и нет, попробовать выполнить запрос
DELETE FROM [CxDB].[dbo].LoggedinUser WHERE [ClientType] = 6;
Pro Checkmarx po 8.6:
// Проверяем, что есть залогиненые пользователи, выполнив запрос в БД
SELECT COUNT(*) FROM LoggedinUser WHERE (ClientType = 'Audit');
// Если что-то есть, а на самом деле даже если и нет, попробовать выполнить запрос
DELETE FROM [CxDB].[dbo].LoggedinUser WHERE (ClientType = 'Audit');Psání pravidel
Konečně jsme se dostali k tomu nejzajímavějšímu. Když začnete psát pravidla na CxQL, často chybí nejen dokumentace, ale také nějaké konkrétní příklady řešení určitých úloh a popis procesu práce dotazů jako celku.
Pokusím se trochu zpříjemnit život těm, kteří se teprve začínají zabývat jazykem dotazů, a uvedu několik příkladů použití Custom Queries pro řešení specifických úloh. Některé z nich jsou dost obecné a mohou být aplikovány ve vaší společnosti prakticky bez změn, jiné jsou specifičtější, ale také se dají využít, pokud upravíte kód podle specifik vašich aplikací.
Takže zde jsou úkoly, se kterými jsme se setkávali nejčastěji:
Úkol: Ve výsledcích provádění pravidla se nachází několik Flow a jeden z nich je vložením jiného, je nutné ponechat jeden z nich.
Řešení: Opravdu, někdy Checkmarx zobrazuje několik toků přenosu dat, které se mohou překrývat a být zkrácenou verzí jiných. Pro takové případy existuje speciální metoda ReduceFlow. V závislosti na parametru vybere nejkratší nebo nejdelší tok:
// Оставить только длинные Flow
result = result.ReduceFlow(CxList.ReduceFlowType.ReduceSmallFlow);
// Оставить только короткие Flow
result = result.ReduceFlow(CxList.ReduceFlowType.ReduceBigFlow);Úkol: Rozšířit seznam citlivých dat, na které reaguje nástroj
Řešení: V Checkmarx existují základní pravidla, jejichž výsledky využívají mnohé další dotazy. Doplněním některých z těchto pravidel o data specifická pro vaši aplikaci můžete ihned zlepšit výsledky skenování. Následuje příklad pravidla, od kterého lze začít:
General_privacy_violation_list
Přidáme několik proměnných, které se používají v naší aplikaci pro uchovávání citlivých informací:
// Получаем результат выполнения базового правила
result = base.General_privacy_violation_list();
// Ищем элементы, которые попадают под простые регулярные выражения. Можно дополнить характерными для вас паттернами.
CxList personalList = All.FindByShortNames(new List<string> {
"*securityToken*", "*sessionId*"}, false);
// Добавляем к конечному результату
result.Add(personalList);Úkol: Rozšířit seznam proměnných s hesly
Řešení: Doporučuji okamžitě věnovat pozornost základnímu pravidlu pro definování hesel v kódu a přidat k němu seznam názvů proměnných, které se běžně používají ve vaší společnosti.
Password_privacy_violation_list
CxList allStrings = All.FindByType("String");
allStrings.Add(All.FindByType(typeof(StringLiteral)));
allStrings.Add(Find_UnknownReference());
allStrings.Add(All.FindByType(typeof (Declarator)));
allStrings.Add(All.FindByType(typeof (MemberAccess)));
allStrings.Add(All.FindByType(typeof(EnumMemberDecl)));
allStrings.Add(Find_Methods().FindByShortName("get*"));
// Doplňujeme výchozí seznam proměnných
List pswdIncludeList = new List{"*password*", "*psw", "psw*", "pwd*", "*pwd", "*authKey*", "pass*", "cipher*", "*cipher", "pass", "adgangskode", "benutzerkennwort", "chiffre", "clave", "codewort", "contrasena", "contrasenya", "geheimcode", "geslo", "heslo", "jelszo", "kennwort", "losenord", "losung", "losungswort", "lozinka", "modpas", "motdepasse", "parol", "parola", "parole", "pasahitza", "pasfhocal", "passe", "passord", "passwort", "pasvorto", "paswoord", "salasana", "schluessel", "schluesselwort", "senha", "sifre", "wachtwoord", "wagwoord", "watchword", "zugangswort", "PAROLACHIAVE", "PAROLA CHIAVE", "PAROLECHIAVI", "PAROLE CHIAVI", "paroladordine", "verschluesselt", "sisma",
"pincode",
"pin"};
List pswdExcludeList = new List{"*pass", "*passable*", "*passage*", "*passenger*", "*passer*", "*passing*", "*passion*", "*passive*", "*passover*", "*passport*", "*passed*", "*compass*", "*bypass*", "pass-through", "passthru", "passthrough", "passbytes", "passcount", "passratio"};
CxList tempResult = allStrings.FindByShortNames(pswdIncludeList, false);
CxList toRemove = tempResult.FindByShortNames(pswdExcludeList, false);
tempResult -= toRemove;
tempResult.Add(allStrings.FindByShortName("pass", false));
foreach (CxList r in tempResult)
{
CSharpGraph g = r.data.GetByIndex(0) as CSharpGraph;
if(g != null && g.ShortName != null && g.ShortName.Length < 50)
{
result.Add(r);
}
}Úkol: Přidat používané frameworky, které nejsou podporovány Checkmarx
Řešení: Všechny dotazy v Checkmarx jsou rozděleny podle jazyků, takže je třeba doplnit pravidla pro každý jazyk. Níže je několik příkladů takových pravidel.
Pokud se používají knihovny, které doplňují nebo nahrazují standardní funkce, lze je snadno přidat do výchozího pravidla. Všichni, kdo je používají, se tak okamžitě dozví o nových zavedeních. Například knihovny pro logování v Androidu - Timber a Loggi. Ve výchozím balíku pravidel pro definici nesystémových volání chybí, takže pokud se heslo nebo identifikátor sezení dostane do logu, nebudeme o tom vědět. Zkusíme přidat do pravidel Checkmarx definice těchto metod.
Ukázkový příklad kódu, který používá knihovnu Timber pro logování:
balíček com.death.timberdemo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import timber.log.Timber;
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Timber.e("Chybová zpráva");
Timber.d("Debugovací zpráva");
Timber.tag("Nějaký jiný tag").e("A chybová zpráva");
}
}Zde je příklad dotazu pro Checkmarx, který umožní přidat definici volání metod Timber jako bod výstupu dat z aplikace:
NajítAndroidVýstupy
// Получаем результат выполнения базового правила
result = base.Find_Android_Outputs();
// Дополняем вызовами, которые приходят из библиотеки Timber
CxList timber = All.FindByExactMemberAccess("Timber.*") +
All.FindByShortName("Timber").GetMembersOfTarget();
// Добавляем к конечному результату
result.Add(timber);A také lze doplnit sousední pravidlo, které se ale přímo týká logování v Androidu:
NajítAndroidLog_Výstupy
// Получаем результат выполнения базового правила
result = base.Find_Android_Log_Outputs();
// Дополняем вызовами, которые приходят из библиотеки Timber
result.Add(
All.FindByExactMemberAccess("Timber.*") +
All.FindByShortName("Timber").GetMembersOfTarget()
);Pokud se v Android aplikacích používá pro asynchronní práci, bylo by dobré také o tom informovat Checkmarx přidáním metody pro získání dat ze úkolu getInputData:
NajítAndroidČíst
// Получаем результат выполнения базового правила
result = base.Find_Android_Read();
// Дополняем вызовом функции getInputData, которая используется в WorkManager
CxList getInputData = All.FindByShortName("getInputData");
// Добавляем к конечному результату
result.Add(getInputData.GetMembersOfTarget());Úkol: Hledání citlivých dat v plist pro projekty iOS
Řešení: Často se pro ukládání různých proměnných a hodnot v iOS používají speciální soubory s příponou .plist. Ukládání hesel, tokenů, klíčů a dalších citlivých dat do těchto souborů se nedoporučuje, protože mohou být s relativně malými potížemi extrahována z zařízení.
Soubory plist mají vlastnosti, které nejsou očima patrné, ale jsou důležité pro Checkmarx. Napíšeme pravidlo, které bude vyhledávat potřebná data a informovat nás, pokud se někde zmiňují hesla nebo tokeny.
Příklad takového souboru, ve kterém je zakódován token pro komunikaci se službou backend:
DeviceDictionary
phone
iPhone 6s
privatekey
MIICXAIBAAKBgQCqGKukO1De7zhZj6+A pravidlo pro Checkmarx, které zahrnuje několik nuancí, které je třeba mít na paměti při psaní:
// Используем результат выполнения правила по поиску файлов 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);Úkol: Hledání informací v XML
Řešení: V Checkmarx existují velmi užitečné funkce pro práci s XML a hledání hodnot, tagů, atributů a dalších. Bohužel, v dokumentaci se stala chyba, kvůli které žádný příklad nefunguje. I když v poslední verzi dokumentace byla tato chyba opravena — buďte opatrní, pokud používáte starší verze dokumentů.
Zde je nesprávný příklad z dokumentace:
// Код работать не будет
result = All.FindXmlAttributesByNameAndValue("*.app", 8, “id”, "error- section", false, true);V důsledku pokusu o provedení dostaneme chybu, že u All taková metoda neexistuje… A to je pravda, protože pro použití funkcí pro práci s XML existuje speciální oddělený objektový prostor — cxXPath. Takto vypadá správný dotaz pro nalezení nastavení v Androidu, které povoluje používání HTTP provozu:
// Правильный вариант с использованием cxXPath
result = cxXPath.FindXmlAttributesByNameAndValue("*.xml", 8, "cleartextTrafficPermitted", "true", false, true);Podívejme se na to podrobněji, protože syntaxe u všech funkcí je podobná, a poté, co se porozumí jedné, stačí jen vybrat tu správnou. A tedy postupně podle parametrů:
"*.xml"— maska souborů, podle které se má provádět hledání8— id jazyka, pro který se pravidlo používá"cleartextTrafficPermitted"— název atributu v xml"true"— hodnota tohoto atributufalse— používání regulárního výrazu při hledánítrue— znamená, že hledání proběhne bez ohledu na velikost písmen, tzn. case-insensitive
Pro příklad bylo použito pravidlo, které určuje nekorektní, z pohledu bezpečnosti, nastavení síťového připojení v Androidu, které umožňuje komunikaci se serverem prostřednictvím protokolu HTTP. Příklad nastavení, které obsahuje atribut cleartextTrafficPermitted s hodnotou true:
example.com
secure.example.comÚkol: Omezit výsledky podle názvu/cesty souboru
Řešení: V jednom velkém projektu souvisejícím s vývojem mobilní aplikace pro Android jsme se setkali s falešnými alarmy pravidla, které určuje nastavení obfuskace. Jde o to, že pravidlo zkrátka hledá v souboru build.gradle nastavení, které je zodpovědné za uplatnění pravidel obfuskace pro vydanou verzi aplikace.
Ale ve velkých projektech se občas objevují podřízené soubory build.gradle, které se vztahují k knihovnám zahrnutým v projektu. Specifikum je v tom, že i když v těchto souborech není uvedena potřeba obfuskace, při kompilaci budou aplikována nastavení rodičovského souboru sestavení.
Tím pádem je úkolem omezit výskyty v podřízených souborech, které se vztahují k knihovnám. Je možné je určit podle přítomnosti řetězce apply 'com.android.library'.
Příklad kódu ze souboru build.gradle, určující potřebu obfuskace:
apply plugin: 'com.android.application'
android {
compileSdkVersion 24
buildToolsVersion "24.0.2"
defaultConfig {
...
}
buildTypes {
release {
minifyEnabled true
...
}
}
}
dependencies {
...
}Příklad souboru build.gradle pro knihovnu, která je zahrnuta do projektu a nemá takové nastavení:
apply plugin: 'android-library'
dependencies {
compile 'com.android.support:support-v4:18.0.+'
}
android {
compileSdkVersion 14
buildToolsVersion '17.0.0'
...
}A pravidlo pro 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);
}
}
}Takový přístup může být dost univerzální a může se hodit nejen pro aplikace Android, ale i pro další případy, kdy je potřeba určit příslušnost výsledku k určitému souboru.
Úkol: Přidat podporu externí knihovny, pokud není syntaktická podpora plně zajištěna
Řešení: Počet různých rámců používaných při psaní kódu je ohromný. Samozřejmě, Checkmarx ne vždy zná jejich existenci a naším úkolem je naučit ho chápat, že určité metody patří k tomuto rámci. Někdy to komplikuje, že rámce používají názvy funkcí, které jsou velmi běžné a nelze jednoznačně určit příslušnost daného volání k určité knihovně.
Obtížnost spočívá v tom, že syntaktika takových knihoven není vždy správně rozpoznána a je třeba experimentovat, abychom neobdrželi velké množství falešných pozitiv. Existuje několik variant, jak zlepšit přesnost skenování a vyřešit daný úkol:
První možnost, kdy přesně víme, že knihovna je použita v určitém projektu a můžeme pravidlo aplikovat na úrovni týmu. Ale v případě, že se tým rozhodne použít jiný přístup nebo používá několik knihoven, kde se překrývají názvy funkcí, můžeme získat velmi nepříjemný obraz kvůli mnoha falešným pozitivům.
Druhá možnost, aplikovat vyhledávání v souborech, ve kterých je knihovna jasně importována. Při tomto přístupu si můžeme být jisti, že v tomto souboru je opravdu použita požadovaná knihovna.
A třetí možnost, to je použití obou výše uvedených přístupů společně.
Jako příklad si rozebíráme známou knihovnu v úzkých kruzích pro programovací jazyk Scala, a to konkrétně, funkčnost Obecně platí, že pro předávání parametrů do SQL dotazu je nutné použít operátor $, který vkládá data do předem sestaveného SQL dotazu. To znamená, že je přímo analogický k Prepared Statement v Javě. Nicméně, pokud je potřeba dynamicky konstruovat SQL dotaz, například pokud je nutné předávat názvy tabulek, je možné použít operátor #$, který přímo vloží data do dotazu (prakticky jako concatenace řetězců).
Ukázka kódu:
// В общем случае - значения, контролируемые пользователем
val table = "coffees"
sql"select * from #$table where name = $name".as[Coffee].headOptionCheckmarx zatím neumí rozpoznat použití Splicing Literal Values a ignoruje operátory #$, takže se pokusíme naučit ho rozpoznávat potenciální SQL injekce a zvýrazňovat potřebná místa v kódu:
// Находим все импорты
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));
}Úkol: Hledání používaných zranitelných funkcí v Open-Source knihovnách
Řešení: Mnoho společností používá nástroje pro kontrolu Open-Source (praktika OSA), které umožňují odhalit použití zranitelných verzí knihoven v vyvíjených aplikacích. Někdy není možné aktualizovat takovou knihovnu na bezpečnou verzi. V některých případech existují funkční omezení, v jiných prostě není bezpečná verze. V takovém případě pomůže kombinace praktik SAST a OSA, která umožňuje určit, že funkce, které vedou k exploataci zranitelnosti, nejsou v kódu používány.
Ale někdy, zejména pokud zvažujeme JavaScript, to může být ne úplně triviální úkol. Níže je uvedeno řešení, možná ne ideální, ale přesto funkční, na příkladu zranitelností v komponentě lodash v metodách template a *set.
Příklady testovacího potenciálně zranitelného kódu v JS souboru:
/**
* Template example
*/
'use strict';
var _ = require("./node_modules/lodash.js");
// Use the "interpolate" delimiter to create a compiled template.
var compiled = _.template('hello <%= js %>!');
console.log(compiled({ 'js': 'lodash' }));
// => 'hello lodash!'
// Use the internal `print` function in "evaluate" delimiters.
var compiled = _.template('<% print("hello " + js); %>!');
console.log(compiled({ 'js': 'lodash' }));
// => 'hello lodash!'A při připojení přímo v html:
<!DOCTYPE html>
<html>
<head>
<title>Lodash Učebnice</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>Hledáme všechny naše zranitelné metody, které jsou uvedeny v zranitelnostech:
// Ищем все строки: в которых встречается строка 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));Úkol: Hledání zabudovaných certifikátů v aplikaci
Řešení: Často aplikace, zejména mobilní, používají certifikáty nebo klíče pro přístup k různým serverům nebo pro ověřování SSL-Pinning. Z hlediska bezpečnosti není nejlepší praxí uchovávat takové věci v kódu. Pokusíme se napsat pravidlo, které bude hledat podobné soubory v repozitáři:
// Найдем все сертификаты по маске файла
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;Úkol: Hledání kompromitovaných tokenů v aplikaci
Řešení: Někdy je nutné odvolávat kompromitované tokeny nebo jiné důležité informace, které jsou obsažené v kódu. Samozřejmě, uchovávat je v původních souborech není nejlepší nápad, ale situace bývají různé. Díky dotazům CxQL je nalezení takových položek dost jednoduché:
// Получаем все строки, которые содержатся в коде
CxList strings = base.Find_Strings();
// Ищем среди всех строк нужное нам значение. В примере токен в виде строки "qwerty12345"
result = strings.FindByShortName("qwerty12345");Závěr
Doufám, že tento článek bude užitečný pro ty, kteří začínají svou cestu s nástrojem Checkmarx. Možná i ti, kteří už dlouho píší své pravidla, najdou v tomto průvodci něco užitečného.
Bohužel, v současnosti chybí zdroj, kde by bylo možné čerpat nové nápady při vytváření pravidel pro Checkmarx. Proto jsme vytvořili , kde budeme zveřejňovat naše práce, aby každý, kdo používá CxQL, mohl v něm najít něco užitečného a měl možnost sdílet se společenstvím své výtvory. Repozitář je v procesu doplňování a strukturování obsahu, takže přispěvatelé jsou vítáni!
Děkujeme za pozornost!
Zdroj: habr.com

Nastavení presetů v rozhraní Checkmarx
Rozhraní CxAudit
Rozdělení pravidel podle jazyků
Určení typu pravidla při vytváření
Příklad nového pravidla v rozhraní správce předvoleb
Určení úrovně, na které bude pravidlo aplikováno