Helló, Habr!
A munkánk során cégünk gyakran találkozik különböző statikus kódellenőrző eszközökkel (SAST). Alapértelmezett beállításokkal ezek általában közepesen működnek. Természetesen minden a projekttől és a használt technológiáktól függ, valamint attól, hogy ezek a technológiák mennyire vannak lefedve az elemzési szabályokkal. Véleményem szerint az egyik legfontosabb szempont az SAST eszköz kiválasztásakor az a lehetőség, hogy a saját alkalmazásaink sajátosságaihoz illesszük, különösen az elemzési szabályok írására és módosítására, vagy ahogy gyakran nevezik, a Custom Queries-re.

Leggyakrabban a Checkmarx-ot használjuk — egy nagyon érdekes és erőteljes kódellenőrző rendszert. Ebben a cikkben a Checkmarx számára írt elemzési szabályokkal kapcsolatos tapasztalataimat osztom meg.
Tartalomjegyzék
Bevezetés
Először is, szeretném ajánlani az egyik kevés orosz nyelvű cikket a Checkmarx kérésének írására vonatkozó sajátosságairól. Ezt a Habr-on tették közzé 2019 végén a következő címmel: .
A cikk részletesen bemutatja, hogyan lehet írni az első lekérdezéseket a CxQL (Checkmarx Query Language) nyelven egy tesztalkalmazás számára, és bemutatja az elemzési szabályok működésének alapvető elveit.
Nem szeretném megismételni, ami benne van, bár némi átfedés mégis lesz. Cikkemben megpróbálok összeállítani egyfajta „receptgyűjteményt”, konkrét problémák megoldásainak listáját, amelyekkel a Checkmarx használata során szembesültem. Sok ilyen feladaton komolyan el kellett gondolkodnom. Néha a dokumentációban hiányoztak az adatok, máskor pedig nehezen értettem, hogyan kell megvalósítani, amit szükséges. Remélem, tapasztalataim és álmatlan éjszakáim nem lesznek hiábavalók, és ez a „Custom Queries receptgyűjtemény” néhány órát vagy pár idegsejtet meg fog spórolni nektek. Tehát kezdjük!
Általános információk a szabályokról
Először is, nézzük meg néhány alapvető fogalmat és a szabályokkal való munka folyamatát, hogy jobban megértsük, mi fog történni a továbbiakban. És azért is, mert a dokumentációban erről nem esik szó, vagy nagyon szét van húzva a struktúrája, ami nem túl kényelmes.
A szabályok alkalmazása a kiválasztott előpreset (az aktív szabályok készlete) alapján történik a szkennelés során. Korlátlan számú előpresetet lehet létrehozni, és hogy ezeket hogyan strukturálja, az a folyamat sajátosságaitól függ. Csoportosíthatja őket nyelvek szerint, vagy elkülönítheti az egyes projektekhez tartozó előpreseteket. Az aktív szabályok száma befolyásolja a szkennelés sebességét és pontosságát.
A Preset beállítása a Checkmarx felületénA szabályokat egy speciális eszközzel, a CxAuditorral szerkesztjük. Ez egy asztali alkalmazás, amely csatlakozik a Checkmarx szerverhez. Ennek az eszköznek két működési módja van: a szabályok szerkesztése és a már elvégzett szkennelés eredményeinek elemzése.
A CxAudit felületeA szabályok a Checkmarx-ban nyelvek szerint vannak csoportosítva, azaz minden nyelvhez saját lekérdezés készlet tartozik. Vannak általános szabályok is, amelyek nyelvtől függetlenül alkalmazhatók, ezek az úgynevezett alaplekérdezések. Ezek jellemzően információkeresést tartalmaznak, amelyeket más szabályok használnak.
A szabályok nyelvek szerinti csoportosításaA szabályok "Executable" és "Non-Executable" (Végrehajtható és Nem végrehajtható) lehetnek. Ezt a megnevezést nem tartom teljesen pontosnak, de ez a helyzet. A lényeg, hogy az "Executable" szabályok végrehajtásának eredménye a UI-ban a szkennelés eredményeiben jelenik meg, míg a "Non-Executable" szabályok csak más lekérdezésekben hasznosítható eredményeket szolgáltatnak (tulajdonképpen csak egy funkció).
A szabály típusa a létrehozáskorÚj szabályokat lehet létrehozni, vagy meglévőket kiegészíteni/áttani. Ahhoz, hogy egy szabályt átírjunk, meg kell találni a fában, jobb gombbal kattintani rá, és a legördülő menüben a "Override" lehetőséget kell választani. Itt fontos megjegyezni, hogy az új szabályok kezdetben nincsenek aktiválva az előpresetekben. Ahhoz, hogy használni tudja őket, aktiválnia kell őket a "Preset Manager" menüben az eszközben. Az átkonfigurált szabályok megőrzik beállításaikat, azaz ha a szabály aktív volt, akkor az is marad, és azonnal alkalmazásra kerül.
A példa új szabályra a Preset Manager felületénA végrehajtás során egy „fa” készül az igénylésekből, hogy mi függ mitől. Először a szabályok futnak, amelyek információt gyűjtenek, majd azok, amelyek ezt felhasználják. A végrehajtási eredmények gyorsítótárazva vannak, így ha van lehetőség meglévő szabályok eredményeit felhasználni, akkor érdemes ezt tenni, mivel csökkenti a bejárás idejét.
A szabályokat különböző szinteken lehet alkalmazni:
Rendszeren belül — minden projekt bármely bejárásához használva lesz
Csapat szintjén (Team) — csak a választott csapat projekteinél alkalmazzák.
Projekt szintjén — adott projektre alkalmazzák
A szabály alkalmazási szintjének meghatározása
A kezdők szótára
És néhány dologgal kezdem, amelyek kérdéseket okoztak számomra, emellett bemutatok néhány trükköt, amelyek jelentősen megkönnyítik az életet.
Lista műveletek
- egy listából kivonni a másikat (list2 - list1)
* a listák metszete (list1 * list2)
+ listák összeadása (list1 + list2)
& (logikai ÉS) - a listákat egybe vonja egyezés alapján (list1 & list2), hasonlóan a metszéshez (list1 * list2)
| (logikai VAGY) - a listákat széles körű keresés szerint egybe vonja (list1 | list2)
A listákkal nem működik: ^ && || % \/ Minden található elem
A bejárás során használt nyelven teljesen minden, a Checkmarx által meghatározott elem listáját megkaphatjuk (sorok, függvények, osztályok, metódusok stb.). Ez egy objektumok bizonyos területe, amelyhez hozzáférhetünk az All. Tehát egy konkrét néven található objektum kereséséhez searchMe, például az összes talált objektum név alapján történő keresés végezhető el:
// Такой запрос выдаст все элементы
result = All;
// Такой запрос выдаст все элементы, в имени которых присутствует “searchMe“
result = All.FindByName("searchMe");De ha más nyelven kell keresni, amely valamilyen okból nem került be a bejárás során (például groovy az Android projektben), akkor a változón keresztül bővíthetjük az objektumaink területét:
result = AllMembers.All.FindByName("searchMe");Funkciók a Flow elemzéshez
Ezeket a funkciókat sok szabályban használják, és itt egy rövid útmutató, hogy mit jelentenek:
// Какие данные second влияют на first.
// Другими словами - ТО (second) что влияет на МЕНЯ (first).
result = first.DataInfluencedBy(second);
// Какие данные first влияют на second.
// Другими словами - Я (first) влияю на ТО (second).
result = first.DataInfluencingOn(second);Fájl neve/útvonalának megszerzése
Vannak olyan attribútumok, amelyeket a lekérdezés végrehajtásának eredményeiből kaphatunk (a fájl neve, amelyben a találat található, sor stb.), de hogy ezeket hogyan szerezhetjük meg és használhatjuk, azt a dokumentáció nem mondja el. Tehát ahhoz, hogy ezt megtegyük, hivatkoznunk kell a LinePragma tulajdonságra, és már belül találhatók a szükséges objektumok:
// Для примера найдем все методы
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);Fontos figyelembe venni, hogy FileName valójában a fájl elérési útját tartalmazza, mivel a módszert használtuk GetFirstGraph.
A végrehajtás eredménye
A CxQL belsejében egy speciális változó található eredmény, amely a megírt szabályának végrehajtásának eredményét adja vissza. Ez azonnal inicializálva van, és átmeneti eredményeket lehet belerakni, miközben a munka folyik. De ha a szabályon belül nem lesz hozzárendelés ehhez a változóhoz vagy funkcióhoz return— a végrehajtás eredménye mindig nullás lesz.
A következő kérés nem fog nekünk semmit visszaadni a végrehajtás során, és mindig üres lesz:
// Находим элементы foo
CxList libraries = All.FindByName("foo");De ha a végrehajtás eredményét a varázs változónak, result-nak adjuk meg, akkor látni fogjuk, mit ad vissza ez a hívás:
// Находим элементы foo
CxList libraries = All.FindByName("foo");
// Выводим, как результат выполнения правила
result = libraries
// Или еще короче
result = All.FindByName("foo");Más szabályok végrehajtásának eredményeinek felhasználása
A Checkmarx szabályai hasonlíthatók a hagyományos programozási nyelvek funkcióihoz. Szabály írásakor teljesen használhatja más lekérdezések eredményeit. Például, nem szükséges minden alkalommal megkeresni az összes metódushívást a kódban, elég, ha csak a szükséges szabályt hívja meg:
// Получаем результат выполнения другого правила
CxList methods = Find_Methods();
// Ищем внутри метод foo.
// Второй параметр false означает, что ищем без чувствительности к регистру
result = methods.FindByShortName("foo", false);Ez a megközelítés lehetővé teszi a kód lerövidítését és jelentősen csökkenti a szabály végrehajtási idejét.
Problémák megoldása
Naplózás
Az eszközzel való munka során néha nem sikerül azonnal megírni a szükséges lekérdezést, és kísérletezni kell, próbálkozva különböző változatokkal. Ilyen esetekre az eszköz nyilvántartást vezet, amelyet a következőképpen hívnak meg:
// Находим что-то
CxList toLog = All.FindByShortName("log");
// Формируем строку и отправляем в лог
cxLog.WriteDebugMessage (“number of DOM elements =” + All.Count);De érdemes megjegyezni, hogy ez a módszer csak stringet, így az első művelet végrehajtásának eredményeként nem lehet a teljes listát kiírni. A második módszer, amelyet a hibakereséshez használnak — időnként a varázs változónak eredmény a lekérdezés végrehajtásának eredményét kell hozzárendelni, és megnézni, mi lesz az eredmény. Ez a megközelítés nem túl kényelmes, biztosnak kell lenni, hogy a kódban később nincs újradefiniálás vagy műveletek ezzel eredmény vagy egyszerűen kommentálni kell az alatta lévő kódot. Vagy mint én, elfelejteni néhány ilyen hívást eltávolítani a kész szabályból, és meglepődni, hogy miért nem működik semmi.
Kellemesebb módszer — ezt a metódust hívni return a megfelelő paraméterrel. Ebben az esetben a szabály végrehajtása befejeződik, és láthatjuk, mi lett az így írt eredmény:
// Находим что-то
CxList toLog = All.FindByShortName("log");
// Выводим результат выполнения
return toLog
//Все, что написано дальше не будет выполнено
result = All.DataInfluencedBy(toLog)Bejelentkezési probléma
Vannak olyan helyzetek, amikor nem lehet belépni a CxAudit eszközbe (amely a szabályok írására szolgál). Ennek sok oka lehet, például hiba miatt bekövetkezett leállás, váratlan Windows frissítés, BSOD és más előre nem látható helyzetek, amelyek felett nincs hatalmunk. Ilyen esetekben néha egy befejezetlen munkamenet marad az adatbázisban, amely megakadályozza a belépést. A hiba kijavításához több lekérdezést kell végrehajtani:
Checkmarx 8.6-ig:
// Проверяем, что есть залогиненые пользователи, выполнив запрос в БД
SELECT COUNT(*) FROM [CxDB].[dbo].LoggedinUser WHERE [ClientType] = 6;
// Если что-то есть, а на самом деле даже если и нет, попробовать выполнить запрос
DELETE FROM [CxDB].[dbo].LoggedinUser WHERE [ClientType] = 6;
Checkmarx 8.6 után:
// Проверяем, что есть залогиненые пользователи, выполнив запрос в БД
SELECT COUNT(*) FROM LoggedinUser WHERE (ClientType = 'Audit');
// Если что-то есть, а на самом деле даже если и нет, попробовать выполнить запрос
DELETE FROM [CxDB].[dbo].LoggedinUser WHERE (ClientType = 'Audit');Szabályok írása
Íme, eljutottunk a legérdekesebb pontra. Amikor elkezded írni a szabályokat a CxQL nyelven, gyakran nem is annyira a dokumentációból, mint inkább néhány élő példa megoldásokra és a lekérdezések működési folyamatának leírására van szükség.
Megpróbálom egy kicsit megkönnyíteni azok életét, akik belemerülnek a lekérdezések nyelvébe, és néhány példát hozok a Custom Queries használatára a konkrét feladatok megoldására. Néhány közülük elég általános, és a cégedben szinte változtatások nélkül alkalmazható, míg mások specifikusabbak, de azokat szintén lehet használni, ha az alkalmazásaid sajátosságaihoz igazítod a kódot.
Tehát, ezekkel a feladatokkal találkoztunk a leggyakrabban:
Feladat: A szabály végrehajtása során több Flow adatfolyam van, és az egyik egy másiknak a beágyazása, szükséges az egyiket megtartani.
Megoldás: Valóban, néha a Checkmarx több adatmozgási Flow-t mutat, amelyek átfedhetik egymást, és rövidebb verziói lehetnek másoknak. Az ilyen esetekhez létezik egy speciális módszer: ReduceFlow. A paramétertől függően a legrövidebb vagy leghosszabb Flow-t választja ki:
// Оставить только длинные Flow
result = result.ReduceFlow(CxList.ReduceFlowType.ReduceSmallFlow);
// Оставить только короткие Flow
result = result.ReduceFlow(CxList.ReduceFlowType.ReduceBigFlow);Feladat: Bővítsük ki az érzékeny adatok listáját, amelyekre az eszköz reagál.
Megoldás: A Checkmarxban alapvető szabályok léteznek, amelyek végrehajtásának eredményeit sok más lekérdezés használja. Ha kiegészítjük az ilyen típusú szabályokat az alkalmazásodra jellemző adatokkal, azonnal javíthatjuk a szkennelési eredményeket. Íme egy példa egy szabályra, amivel lehet kezdeni:
General_privacy_violation_list
Adjunk hozzá néhány változót, amelyeket az alkalmazásunk érzékeny információinak tárolására használunk:
// Получаем результат выполнения базового правила
result = base.General_privacy_violation_list();
// Ищем элементы, которые попадают под простые регулярные выражения. Можно дополнить характерными для вас паттернами.
CxList personalList = All.FindByShortNames(new List<string> {
"*securityToken*", "*sessionId*"}, false);
// Добавляем к конечному результату
result.Add(personalList);Feladat: Bővítsük ki a jelszavakat tároló változók listáját.
Megoldás: Ajánlom, hogy azonnal figyeljen a kód jelszómeghatározásának alapvető szabályára, és adjon hozzá egy listát a változók neveiről, amelyeket a cégnél alkalmaznak.
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*"));
// Kiegészítjük az alapértelmezett változólistát
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);
}
}Feladat: Adja hozzá azokat a keretrendszereket, amelyeket a Checkmarx nem támogat.
Megoldás: Minden kérés a Checkmarx-ban nyelvek szerint van csoportosítva, így a szabályokat minden nyelvre ki kell egészíteni. Az alábbiakban néhány példa ilyen szabályokra.
Ha olyan könyvtárakat használnak, amelyek kiegészítik vagy helyettesítik a standard funkciókat, akkor ezeket könnyen hozzá lehet adni az alapvető szabályhoz. Így mindenki, aki használja őket, azonnal értesül az új szabályokról. Például az Android loggolási könyvtárai — Timber és Loggi. Az alapvető szabályokban nincsenek nem rendszerszintű hívások, így ha a jelszó vagy a munkamenet azonosítója bekerül a naplóba, arról nem tudunk. Próbáljuk meg a Checkmarx szabályaiba beilleszteni ezen módszerek észlelését.
Teszt példa kód, amely a Timber könyvtárat használja a naplózáshoz:
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");
}
}Itt van egy példa a Checkmarx számára, amely lehetővé teszi a Timber metódushívások definiálását mint az alkalmazásból való adatkilépés pontját:
FindAndroidOutputs
// Получаем результат выполнения базового правила
result = base.Find_Android_Outputs();
// Дополняем вызовами, которые приходят из библиотеки Timber
CxList timber = All.FindByExactMemberAccess("Timber.*") +
All.FindByShortName("Timber").GetMembersOfTarget();
// Добавляем к конечному результату
result.Add(timber);A mellékelt szabályt is ki lehet egészíteni, de már a loggolásra vonatkozóan Android alatt:
FindAndroidLog_Outputs
// Получаем результат выполнения базового правила
result = base.Find_Android_Log_Outputs();
// Дополняем вызовами, которые приходят из библиотеки Timber
result.Add(
All.FindByExactMemberAccess("Timber.*") +
All.FindByShortName("Timber").GetMembersOfTarget()
);Ha Android-alkalmazásokban használják a aszimmetrikus munkához, érdemes ezt a Checkmarx számára is jelezni, hozzáadva az adatok megszerzésére szolgáló metódust: getInputData:
FindAndroidRead
// Получаем результат выполнения базового правила
result = base.Find_Android_Read();
// Дополняем вызовом функции getInputData, которая используется в WorkManager
CxList getInputData = All.FindByShortName("getInputData");
// Добавляем к конечному результату
result.Add(getInputData.GetMembersOfTarget());Feladat: Érzékeny adatok keresése plist fájlokban iOS projekteknél
Megoldás: Gyakran különböző változók és értékek tárolására iOS alatt speciális, .plist kiterjesztésű fájlokat használnak. Jelszavak, tokenek, kulcsok és egyéb érzékeny adatok tárolása e fájlokban nem ajánlott, mivel könnyen kinyerhetők az eszközből.
A plist fájloknak vannak rejtett sajátosságaik, amelyek nem nyilvánvalóak, de fontosak a Checkmarx számára. Írjunk egy szabályt, amely keresni fogja a kívánt adatokat, és értesít minket, ha bárhol megemlítik a jelszavakat vagy tokeneket.
Íme egy példa egy ilyen fájlra, amelyben egy token van a backend szolgáltatással való kommunikációhoz:
DeviceDictionary
phone
iPhone 6s
privatekey
MIICXAIBAAKBgQCqGKukO1De7zhZj6+Ez egy szabály a Checkmarx számára, amely néhány olyan szempontot tartalmaz, amelyet figyelembe kell venni a megírásakor:
// Используем результат выполнения правила по поиску файлов 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);Feladat: Információ keresése XML-ben
Megoldás: A Checkmarx nagyon kényelmes funkciókat kínál az XML kezelésére és az értékek, tagek, attribútumok és egyebek keresésére. Sajnos a dokumentációban van egy hiba, amely miatt egyetlen példa sem működik. Annyira, hogy a legutolsó dokumentációs verzióban ezt a hibát javították - de legyen óvatos, ha korábbi dokumentumverziókat használsz.
Íme egy hibás példa a dokumentációból:
// Код работать не будет
result = All.FindXmlAttributesByNameAndValue("*.app", 8, “id”, "error- section", false, true);A végrehajtási kísérlet eredményeként hibát kapunk, hogy a All nincs ilyen módszer... És ez igaz, mivel az XML-funkciók használatához külön objektumtér szükséges - cxXPath. Így néz ki a helyes lekérdezés, amely megkeresi azt a beállítást az Androidban, amely lehetővé teszi a HTTP-forgalom használatát:
// Правильный вариант с использованием cxXPath
result = cxXPath.FindXmlAttributesByNameAndValue("*.xml", 8, "cleartextTrafficPermitted", "true", false, true);Nézzük meg kicsit részletesebben, mivel a szintaxis minden funkció esetében hasonló, miután megértk egyet, csak a megfelelőt kell kiválasztani. Tehát lépésről lépésre a paraméterek szerint:
"*.xml"— a keresett fájlok maszkja8— a nyelv azonosítója, amelyre a szabály vonatkozik"cleartextTrafficPermitted"— az xml-ben szereplő attribútum neve"true"— ennek az attribútumnak az értékehamis— reguláris kifejezés használata a keresés soránigaz— azt jelenti, hogy a keresés kis- és nagybetű érzéketlen módon fog végrehajtódni
Példaként egy olyan szabályt használtunk, amely az Android hálózati kapcsolatának biztonsági szempontból helytelen beállításait határozza meg, amelyek lehetővé teszik a szerverrel való kommunikációt HTTP protokollon keresztül. Például a beállítás, amely az attribútumot tartalmazza cleartextTrafficPermitted az értékkel igaz:
example.com
secure.example.comFeladat: Keresési eredmények korlátozása fájl név/útvonal szerint
Megoldás: Egy nagyobb projektnél, amely mobilalkalmazás fejlesztésével kapcsolatos Androidra, hamis riasztásokkal szembesültünk a maszkolásra vonatkozó beállításokat megadó szabály miatt. Az a helyzet, hogy a szabály alapértelmezés szerint keres a fájlban build.gradle a maszkolási szabályok alkalmazásáért felelős beállítást.
De nagy projektekben néha megjelennek alárendelt fájlok build.gradle, amelyek a projektbe beépített könyvtárakhoz tartoznak. Az a sajátosság, hogy még ha ezekben a fájlokban nincs is megjelölve a maszkolás szükségessége, a fordítás során a szülőépítési fájl beállításai alkalmazásra kerülnek.
Ezért a feladat az alárendelt fájlok hamis riasztásainak leválasztása, amelyek könyvtárakra vonatkoznak. Ezeket a 'apply 'com.android.library' szöveg alapján lehet azonosítani. Példa a kódból a fájlban.
, amely meghatározza a maszkolás szükségességét: build.gradle, определяющий необходимость обфускации:
alkalmazd a plugint: 'com.android.application'
android {
compileSdkVersion 24
buildToolsVersion "24.0.2"
defaultConfig {
...
}
buildTypes {
release {
minifyEnabled true
...
}
}
}
dependencies {
...
}Fájl példa build.gradle a projektbe integrált könyvtárhoz, ha nincs ilyen beállítás:
alkalmazd a plugint: 'android-library'
dependencies {
compile 'com.android.support:support-v4:18.0.+'
}
android {
compileSdkVersion 14
buildToolsVersion '17.0.0'
...
}És a Checkmarx szabály:
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);
}
}
}Ez a megközelítés elég univerzális lehet, és nemcsak Android alkalmazások esetén hasznos, hanem más helyzetekben is, amikor meg kell határozni az eredmény egy adott fájlhoz való tartozását.
Feladat: Támogatás hozzáadása harmadik fél könyvtára számára, ha a szintaxis nem teljesen támogatott
Megoldás: A kódírás során használt lehetséges keretrendszerek száma elképesztő. Nyilvánvaló, hogy a Checkmarx nem mindig tud a létezésükről, és nekünk az a feladatunk, hogy megtanítsuk neki, hogy bizonyos módszerek ezekhez a keretrendszerekhez tartoznak. Néha ez bonyolítja a helyzetet, mivel a keretrendszerek széles körben elterjedt funkcióneveket használnak, így nem lehet egyértelműen meghatározni, hogy egy adott hívás melyik könyvtárral kapcsolatos.
A nehézség abban rejlik, hogy az ilyen könyvtárak szintaxisa nem mindig ismerhető fel pontosan, és kísérletezni kell, hogy elkerüljük a sok hamis pozitív eredményt. Számos lehetőség van a szkennelés pontosságának javítására és a feladat megoldására:
Az első lehetőség, hogy pontosan tudjuk, hogy a könyvtár egy adott projektben van használatban, és alkalmazhatjuk a szabályt a csapat szintjén. De ha a csapat úgy dönt, hogy más megközelítést alkalmaz, vagy több könyvtárat használ, amelyekben átfedések vannak a funkciónevek között, nem túl kellemes képet kaphatunk a sok hamis pozitív miatt.
A második lehetőség, hogy fájlokban keresünk, ahol a könyvtár nyilvánvalóan importálva van. Ezzel a megközelítéssel biztosak lehetünk abban, hogy ezen a fájlon belül biztosan a szükséges könyvtárat használják.
A harmadik lehetőség pedig az, hogy a fenti két megközelítést együtt alkalmazzuk.
Példaként vegyük egy ismert keretrendszert a Scala programozási nyelvhez, nevezetesen, a funkcióit . Általánosságban elmondható, hogy az SQL-keresés paramétereinek átadásához használni kell a $, amely adatokat helyez el a előre megformált SQL-kérdésben. Tehát valójában a Java-beli Prepared Statement közvetlen megfelelője. De ha szükség van dinamikusan SQL-kérdést létrehozni, például ha táblaneveket kell átadni, akkor lehet használni a #$, amely közvetlenül helyezi el az adatokat a lekérdezésben (gyakorlatilag mint a karakterláncok összefűzése).
Kódminta:
// В общем случае - значения, контролируемые пользователем
val table = "coffees"
sql"select * from #$table where name = $name".as[Coffee].headOptionA Checkmarx egyelőre nem tudja meghatározni a Splicing Literal Values használatát, és kihagyja a #$, ezért próbáljuk meg megtanítani neki, hogy észlelje a potenciális SQL-injekciókat és kiemelje a szükséges helyeket a kódban:
// Находим все импорты
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));
}Feladat: A használt sebezhető funkciók keresése nyílt forráskódú könyvtárakban
Megoldás: Sok vállalat használ nyílt forráskódú ellenőrző eszközöket (OSA gyakorlat), amelyek lehetővé teszik sebezhető könyvtárverziók felfedezését a fejlesztett alkalmazásokban. Néha nem lehetséges egy ilyen könyvtár frissítése a biztonságos verzióra. Bizonyos esetekben funkcionális korlátozások állnak fenn, más esetekben pedig nincs biztonságos verzió. Ilyen esetekben a SAST és OSA gyakorlatok kombinációja segíthet, hogy meghatározzuk, hogy azok a funkciók, amelyek a sebezhetőség kihasználásához vezetnek, nem használatosak a kódban.
De néha, különösen, ha a JavaScriptet nézzük, ez nem teljesen triviális feladat lehet. Az alábbiakban egy megoldás látható, amely lehet, hogy nem tökéletes, de mindenesetre működik, a sebezhetőségek példáján keresztül a lodash módszerekben template és *set.
Példák potenciálisan sebezhető tesztkódra JS fájlban:
/**
* 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!'És amikor közvetlenül csatlakoztatva a html-hez:
<!DOCTYPE html>
<html>
<head>
<title>Lodash Útmutató</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>Keresünk minden sebezhető módszert, amely a sebezhetőségek között szerepel:
// Ищем все строки: в которых встречается строка 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));Feladat: Beépített tanúsítványok keresése az alkalmazásban
Megoldás: Gyakran az alkalmazások, különösen a mobilok, tanúsítványokat vagy kulcsokat használnak különböző kiszolgálókhoz való hozzáféréshez vagy SSL-Pinning ellenőrzésére. Biztonsági szempontból – az ilyen dolgok kódban tárolása nem a legjobb gyakorlat. Próbáljunk meg írni egy szabályt, amely hasonló fájlokat keres a repository-ban:
// Найдем все сертификаты по маске файла
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;Feladat: Kompromittált tokenek keresése az alkalmazásban
Megoldás: Gyakran előfordul, hogy vissza kell vonni a kompromittált tokeneket vagy más fontos információt, amely megtalálható a kódban. Természetesen nem a legjobb ötlet őket a forráskódon belül tárolni, de különböző helyzetek adódhatnak. A CxQL lekérdezések segítségével ilyen dolgokat könnyen megtalálhatunk:
// Получаем все строки, которые содержатся в коде
CxList strings = base.Find_Strings();
// Ищем среди всех строк нужное нам значение. В примере токен в виде строки "qwerty12345"
result = strings.FindByShortName("qwerty12345");Zárás
Remélem, hogy ez a cikk hasznos lesz azok számára, akik most ismerkednek a Checkmarx eszközzel. Talán azok is találnak benne hasznos ötleteket, akik már régóta írják a saját szabályaikat.
Sajnos jelenleg nagyon hiányzik egy olyan forrás, ahol új ötleteket meríthetnénk a Checkmarx szabályaival kapcsolatban. Ezért létrehoztuk , ahol megosztjuk a munkánkat, hogy mindenki, aki használja a CxQL-t, találjon benne valami hasznosat, és lehetősége legyen megosztani az általa készített anyagokat a közösséggel. A tárolónk tartalmának feltöltése és szerkezete jelenleg folyamatban van, így a contributors are welcome!
Köszönöm a figyelmet!
Forrás: habr.com

A Preset beállítása a Checkmarx felületén
A CxAudit felülete
A szabályok nyelvek szerinti csoportosítása
A szabály típusa a létrehozáskor
A példa új szabályra a Preset Manager felületén
A szabály alkalmazási szintjének meghatározása