Hogyan írjunk szabályokat a Checkmarx számára, és ne őrüljünk meg

Szia Habr!

Munkánk során cégünk nagyon gyakran foglalkozik különféle statikus kódelemző eszközökkel (SAST). A dobozon kívül mindegyik átlagosan működik. Természetesen minden a projekttől és a benne alkalmazott technológiáktól függ, valamint attól, hogy ezekre a technológiákra mennyire vonatkoznak az elemzési szabályok. Véleményem szerint a SAST-eszköz kiválasztásánál az egyik legfontosabb kritérium az, hogy az alkalmazás sajátosságaihoz igazítható legyen, nevezetesen az elemzési szabályok vagy – ahogyan ezeket gyakrabban nevezik – egyéni lekérdezések írása és módosítása.

Hogyan írjunk szabályokat a Checkmarx számára, és ne őrüljünk meg

Leggyakrabban a Checkmarxot használjuk - egy nagyon érdekes és hatékony kódelemzőt. Ebben a cikkben az elemzési szabályok megírásával kapcsolatos tapasztalataimról fogok beszélni.

tartalomjegyzék

Belépés

Először is szeretném ajánlani azon kevés orosz nyelvű cikkek egyikét, amelyek a Checkmarx lekérdezések írásának jellemzőiről szólnak. 2019 végén jelent meg a Habrén a következő címmel: – Helló, Checkmarx! Hogyan írjunk Checkmarx SAST-lekérdezést, és keressünk sebezhető pontokat.

Részletesen megvizsgálja, hogyan kell az első lekérdezéseket CxQL-ben (Checkmarx Query Language) írni egyes tesztalkalmazásokhoz, és bemutatja az elemzési szabályok működésének alapelveit.

Nem ismétlem meg az abban leírtakat, bár néhány kereszteződés továbbra is jelen lesz. Cikkemben megpróbálok összeállítani egyfajta „receptgyűjteményt”, egy listát a megoldásokról olyan konkrét problémákra, amelyekkel a Checkmarx-szal végzett munkám során találkoztam. Sok ilyen problémán kellett törnöm az agyamat. Néha nem volt elég információ a dokumentációban, néha pedig még azt is nehéz volt megérteni, hogy mit kell tenni. Remélem, tapasztalataim és álmatlan éjszakáim nem lesznek hiábavalók, és ez az „Egyéni lekérdezések receptgyűjteménye” megspórol néhány órát vagy néhány idegsejtet. Szóval, kezdjük!

Általános információk a szabályokról

Először is nézzünk meg néhány alapfogalmat és a szabályokkal való munka folyamatát, hogy jobban megértsük, mi fog történni ezután. És azért is, mert a dokumentáció nem mond erről semmit, vagy nagyon szét van húzva a szerkezetben, ami nem túl kényelmes.

  1. A szabályok az induláskor kiválasztott előre beállított értéktől (aktív szabályok halmazától) függően kerülnek alkalmazásra a szkennelés során. Korlátlan számú előre beállított beállítást hozhat létre, és ezek pontos szerkezete a folyamat sajátosságaitól függ. Csoportosíthatja őket nyelv szerint, vagy kiválaszthat előre beállított értékeket az egyes projektekhez. Az aktív szabályok száma befolyásolja a szkennelés sebességét és pontosságát.

    Hogyan írjunk szabályokat a Checkmarx számára, és ne őrüljünk megPreset beállítása a Checkmarx felületen

  2. A szabályokat a CxAuditor nevű speciális eszköz szerkeszti. Ez egy asztali alkalmazás, amely a Checkmarxot futtató szerverhez csatlakozik. Ennek az eszköznek két működési módja van: szabályok szerkesztése és egy már elvégzett vizsgálat eredményeinek elemzése.

    Hogyan írjunk szabályokat a Checkmarx számára, és ne őrüljünk megCxAudit felület

  3. A Checkmarx szabályai nyelvenként vannak felosztva, azaz minden nyelvnek saját lekérdezéskészlete van. Van néhány általános szabály is, amelyek nyelvtől függetlenül érvényesek, ezek az úgynevezett alaplekérdezések. Az alapvető lekérdezések többnyire olyan információk keresését jelentik, amelyeket más szabályok használnak.

    Hogyan írjunk szabályokat a Checkmarx számára, és ne őrüljünk megA szabályok nyelv szerinti felosztása

  4. A szabályok a következők: „Végrehajtható” és „Nem végrehajtható” (végrehajtva és nem végrehajtva). Szerintem nem egészen helyes név, de ez van. A lényeg az, hogy a „Végrehajtható” szabályok végrehajtásának eredménye megjelenik a felhasználói felület vizsgálati eredményei között, és a „Nem végrehajtható” szabályok csak ahhoz szükségesek, hogy eredményeiket más kérésekben használják (lényegében csak egy függvény).

    Hogyan írjunk szabályokat a Checkmarx számára, és ne őrüljünk megA szabály típusának meghatározása létrehozáskor

  5. Létrehozhat új szabályokat, vagy kiegészítheti/átírhatja a meglévőket. A szabály átírásához meg kell találnia a fában, kattintson a jobb gombbal, és válassza ki a „Felülbírálás” lehetőséget a legördülő menüből. Fontos megjegyezni, hogy az új szabályok kezdetben nem szerepelnek az előre beállított értékekben, és nem aktívak. A használatuk megkezdéséhez aktiválni kell őket a műszer „Preset Manager” menüjében. Az átírt szabályok megtartják beállításaikat, vagyis ha a szabály aktív volt, akkor az is marad, és azonnal alkalmazásra kerül.

    Hogyan írjunk szabályokat a Checkmarx számára, és ne őrüljünk megPélda egy új szabályra a Preset Manager felületen

  6. A végrehajtás során a kérések „fája” épül fel, ami attól függ, hogy mit. Az információkat gyűjtő szabályokat hajtják végre először, és azután azokat, akik használják. A végrehajtás eredménye gyorsítótárban van, így ha lehetséges egy meglévő szabály eredményeit használni, akkor jobb, ha ezt megteszi, ez csökkenti a vizsgálati időt.

  7. A szabályok különböző szinteken alkalmazhatók:

  • A teljes rendszerre vonatkozóan – bármely projekt szkenneléséhez használható

  • Csapatszinten (Csapat) - csak a kiválasztott csapat projektjeinek szkennelésére szolgál.

  • Projektszinten – Egy adott projektben alkalmazzák

    Hogyan írjunk szabályokat a Checkmarx számára, és ne őrüljünk megA szabály alkalmazási szintjének meghatározása

„Szótár” kezdőknek

És kezdem néhány olyan dologgal, ami kérdéseket okozott bennem, és számos olyan technikát is mutatok, amelyek jelentősen leegyszerűsítik az életet.

Műveletek listákkal

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

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

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

Minden talált elem

A beolvasott nyelven belül egy listát kaphat az összes elemről, amelyet a Checkmarx azonosított (karakterláncok, függvények, osztályok, metódusok stb.). Ez az objektumok egy része, amelyen keresztül hozzá lehet férni All. Vagyis egy adott nevű objektumot keresni searchMe, kereshet például név szerint az összes talált objektum között:

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

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

De ha olyan másik nyelven kell keresnie, amely valamilyen okból nem szerepelt a vizsgálatban (például groovy egy Android-projektben), akkor az objektumterünket egy változóval bővítheti:

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

Az áramláselemzés funkciói

Ezeket a funkciókat számos szabály használja, és itt van egy kis csalólap, hogy mit jelentenek:

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

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

Fájlnév/elérési út lekérése

Egy lekérdezés eredményéből több attribútum is megkapható (annak a fájlnak a neve, amelyben a bejegyzés található, string stb.), de a dokumentáció nem írja le, hogyan lehet ezeket megszerezni és használni. Tehát ehhez hozzá kell férnie a LinePragma tulajdonsághoz, és a szükséges objektumok benne lesznek:

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

Ezt érdemes szem előtt tartani FileName valójában a fájl elérési útját tartalmazza, mivel ezt a metódust használtuk GetFirstGraph.

A végrehajtás eredménye

A CxQL belsejében van egy speciális változó result, amely visszaadja az írott szabály végrehajtásának eredményét. Azonnal inicializálódik, és beleírhatja a köztes eredményeket, amelyeket munka közben módosíthat és finomíthat. De ha a szabályon belül nincs hozzárendelés ehhez a változóhoz vagy függvényhez return— a végrehajtás eredménye mindig nulla lesz.

A következő lekérdezés nem ad vissza semmit a végrehajtás eredményeként, és mindig üres lesz:

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

De miután a végrehajtási eredményt a mágikus változó eredményéhez rendeltük, látni fogjuk, hogy ez a hívás mit ad vissza nekünk:

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

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

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

Más szabályok eredményeinek felhasználása

A Checkmarx szabályait a szokványos programozási nyelv függvényeivel analógnak nevezhetjük. Szabály írásakor felhasználhatja más lekérdezések eredményeit is. Például nem kell minden alkalommal megkeresni az összes metódushívást a kódban, csak hívja meg a kívánt szabályt:

// Получаем результат выполнения другого правила
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 a szabályvégrehajtási idő jelentős csökkentését.

Problémamegoldás

Fakitermelés

Amikor az eszközzel dolgozik, előfordul, hogy nem lehet azonnal megírni a kívánt lekérdezést, és kísérletezni kell, különféle lehetőségeket kipróbálni. Ilyen esetekben az eszköz naplózást biztosít, amelynek neve a következő:

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

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

De érdemes megjegyezni, hogy ez a módszer csak bemenetként fogad el húr, így az első művelet eredményeként nem lehet majd megjeleníteni a talált elemek teljes listáját. A második lehetőség, amelyet hibakeresésre használnak, az, hogy időnként hozzárendelünk egy mágikus változót result a lekérdezés eredménye, és meglátjuk, mi történik. Ez a megközelítés nem túl kényelmes, meg kell győződnie arról, hogy a kódban nincsenek felülírások vagy műveletek. result vagy egyszerűen kommentálja az alábbi kódot. Vagy, mint én, elfelejthet több ilyen hívást eltávolítani egy kész szabályból, és elgondolkodik, miért nem működik semmi.

Egy kényelmesebb módja a módszer meghívása return a szükséges paraméterrel. Ebben az esetben a szabály végrehajtása véget ér, és láthatjuk, mi történt az általunk írottak hatására:

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

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

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

Bejelentkezési probléma

Vannak olyan helyzetek, amikor nem tud hozzáférni a CxAudit eszközhöz (amelyet szabályok írására használnak). Ennek számos oka lehet, többek között összeomlások, hirtelen Windows-frissítések, BSOD és egyéb, rajtunk kívül álló, előre nem látható helyzetek. Ilyenkor előfordul, hogy egy befejezetlen munkamenet van az adatbázisban, ami megakadályozza az újbóli bejelentkezést. A javításhoz több lekérdezést kell futtatnia:

Checkmarx esetén 8.6 előtt:

// Проверяем, что есть залогиненые пользователи, выполнив запрос в БД
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');

Írásszabályok

Most elérkezünk a legérdekesebb részhez. Amikor elkezdi írni a szabályokat a CxQL-ben, gyakran nem annyira a dokumentáció hiányzik, mint néhány élő példa bizonyos problémák megoldására és a lekérdezések működésének általános leírására.

Megpróbálom egy kicsit megkönnyíteni azok életét, akik kezdenek belemerülni a lekérdezési nyelvbe, és néhány példát hozok az egyéni lekérdezések használatára bizonyos problémák megoldására. Ezek egy része meglehetősen általános, és gyakorlatilag változtatás nélkül használható az Ön cégében, mások konkrétabbak, de a kód módosításával is használhatók az alkalmazások sajátosságainak megfelelően.

Tehát itt vannak a leggyakrabban tapasztalt problémák:

Feladat: A szabály végrehajtásának eredményeiben több folyamat is található, és ezek egyike egy másik beágyazása, az egyiket el kell hagynia.

megoldás: Valójában néha a Checkmarx több adatfolyamot is megjelenít, amelyek átfedhetik egymást, és mások rövidített változatai lehetnek. Az ilyen esetekre van egy speciális módszer ReduceFlow. A paramétertől függően a legrövidebb vagy leghosszabb áramlást választja ki:

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

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

Feladat: Bontsa ki azon érzékeny adatok listáját, amelyekre az eszköz reagál

megoldás: A Checkmarx alapvető szabályokkal rendelkezik, amelyek eredményeit sok más lekérdezés is felhasználja. Ezen szabályok némelyikének az alkalmazására vonatkozó adatokkal való kiegészítésével azonnal javíthatja a vizsgálati eredményeket. Az alábbiakban bemutatunk egy példaszabályt a kezdéshez:

General_privacy_violation_list

Adjunk hozzá néhány olyan változót, amelyeket az alkalmazásunk érzékeny információk tárolására használ:

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

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

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

Feladat: Bővítse ki a változók listáját jelszavakkal

megoldás: Azt javaslom, hogy azonnal figyeljen a jelszavak kódban történő definiálására vonatkozó alapszabályra, és adja hozzá a cégében általánosan használt változónevek listáját.

Password_privacy_violation_list

CxList allStrings = All.FindByType("String"); 
allStrings.Add(All.FindByType(typeof(StringLiteral))); 
allStrings.Add(Find_UnknownReference());
allStrings.Add(All.FindByType(typeof (Declarator)));
allStrings.Add(All.FindByType(typeof (MemberAccess)));
allStrings.Add(All.FindByType(typeof(EnumMemberDecl))); 
allStrings.Add(Find_Methods().FindByShortName("get*"));

// Дополняем дефолтный список переменных
List < string > pswdIncludeList = new List<string>{"*password*", "*psw", "psw*", "pwd*", "*pwd", "*authKey*", "pass*", "cipher*", "*cipher", "pass", "adgangskode", "benutzerkennwort", "chiffre", "clave", "codewort", "contrasena", "contrasenya", "geheimcode", "geslo", "heslo", "jelszo", "kennwort", "losenord", "losung", "losungswort", "lozinka", "modpas", "motdepasse", "parol", "parola", "parole", "pasahitza", "pasfhocal", "passe", "passord", "passwort", "pasvorto", "paswoord", "salasana", "schluessel", "schluesselwort", "senha", "sifre", "wachtwoord", "wagwoord", "watchword", "zugangswort", "PAROLACHIAVE", "PAROLA CHIAVE", "PAROLECHIAVI", "PAROLE CHIAVI", "paroladordine", "verschluesselt", "sisma",
                "pincode",
								"pin"};
								
List < string > pswdExcludeList = new List<string>{"*pass", "*passable*", "*passage*", "*passenger*", "*passer*", "*passing*", "*passion*", "*passive*", "*passover*", "*passport*", "*passed*", "*compass*", "*bypass*", "pass-through", "passthru", "passthrough", "passbytes", "passcount", "passratio"};

CxList tempResult = allStrings.FindByShortNames(pswdIncludeList, false);
CxList toRemove = tempResult.FindByShortNames(pswdExcludeList, false);
tempResult -= toRemove;
tempResult.Add(allStrings.FindByShortName("pass", false));

foreach (CxList r in tempResult)
{
	CSharpGraph g = r.data.GetByIndex(0) as CSharpGraph;
	if(g != null && g.ShortName != null && g.ShortName.Length < 50)
	{
		result.Add(r);
	}
}

Feladat: Adjon hozzá használt keretrendszereket, amelyeket a Checkmarx nem támogat

megoldás: A Checkmarx összes lekérdezése nyelv szerint van felosztva, ezért minden nyelvhez szabályokat kell felvennie. Az alábbiakban néhány példa az ilyen szabályokra.

Ha olyan könyvtárakat használnak, amelyek kiegészítik vagy helyettesítik a szabványos funkcionalitást, könnyen hozzáadhatók az alapszabályhoz. Ekkor mindenki, aki használja, azonnal értesül az új bemutatkozásokról. Az Android rendszerbe való bejelentkezéshez használható könyvtárak például a Timber és a Loggi. Az alapcsomagban nincsenek szabályok a nem rendszerhívások azonosítására, így ha jelszó vagy munkamenet-azonosító kerül a naplóba, arról nem fogunk tudni. Próbáljuk meg az ilyen módszerek definícióit hozzáadni a Checkmarx-szabályokhoz.

Példa tesztkódra, 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");
    }
}

És itt van egy példa a Checkmarx kérésére, amely lehetővé teszi a Timber metódusok meghívásának meghatározását az alkalmazásból származó adatok kilépési pontjaként:

Keresse meg az Android kimeneteket

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

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

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

És hozzáadhatja a szomszédos szabályt is, de ez közvetlenül az Androidba való bejelentkezéshez kapcsolódik:

FindAndroidLog_Outputs

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

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

Akkor is, ha Android alkalmazások használnak WorkManager aszinkron munkához érdemes erről a Checkmarxot is tájékoztatni egy módszer hozzáadásával a feladatból való adatgyűjtéshez. getInputData:

FindAndroidRead

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

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

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

Feladat: Érzékeny adatok keresése a plistben iOS-projektekhez

megoldás: Az iOS gyakran használ speciális, .plist kiterjesztésű fájlokat különféle változók és értékek tárolására. Jelszavak, tokenek, kulcsok és egyéb kényes adatok tárolása ezekben a fájlokban nem javasolt, mivel ezek gond nélkül kimásolhatók a készülékről.

A Plist fájlok olyan funkciókkal rendelkeznek, amelyek szabad szemmel nem nyilvánvalóak, de fontosak a Checkmarx számára. Írjunk egy szabályt, amely megkeresi a szükséges adatokat, és közli velünk, ha valahol jelszavakat vagy tokeneket említenek.

Példa egy ilyen fájlra, amely egy tokent tartalmaz a háttérszolgáltatással való kommunikációhoz:

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

És egy szabály a Checkmarx számára, amelynek számos árnyalata van, amelyeket figyelembe kell venni az írás során:

// Используем результат выполнения правила по поиску файлов 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ókkal rendelkezik az XML-lel való munkavégzéshez és az értékek, címkék, attribútumok és egyebek kereséséhez. De sajnos volt egy hiba a dokumentációban, ami miatt egyetlen példa sem működik. Annak ellenére, hogy ezt a hibát a dokumentáció legújabb verziója kiküszöbölte, legyen óvatos, ha a dokumentumok korábbi verzióit használja.

Íme egy helytelen 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 olyan hibát kapunk, hogy All nincs ilyen módszer... És ez igaz, hiszen van egy speciális, külön objektumtér az XML-lel való munkavégzés függvényeinek használatára - cxXPath. Így néz ki a helyes lekérdezés egy olyan beállítás megtalálásához 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, hiszen minden függvény szintaxisa hasonló, miután kitalált egyet, akkor már csak ki kell választani a kívántat. Tehát sorrendben a paraméterek szerint:

  • "*.xml"— a keresendő fájlok maszkja

  • 8 — annak a nyelvnek az azonosítója, amelyre a szabályt alkalmazzák

  • "cleartextTrafficPermitted"— attribútum neve xml-ben

  • "true" — ennek az attribútumnak az értéke

  • false — reguláris kifejezés használata kereséskor

  • true — azt jelenti, hogy a keresés a kis- és nagybetűk figyelmen kívül hagyásával, azaz a kis- és nagybetűk megkülönböztetése nélkül történik

Példaként egy olyan szabályt használtunk, amely biztonsági szempontból helytelen hálózati kapcsolati beállításokat azonosít az Androidban, amelyek lehetővé teszik a HTTP protokollon keresztüli kommunikációt a szerverrel. Példa egy attribútumot tartalmazó beállításra cleartextTrafficPermitted jelentéssel true:

<network-security-config>
    <domain-config>
        <domain includeSubdomains="true">example.com</domain>
        <trust-anchors>
            <certificates src="@raw/my_ca"/>
        </trust-anchors>
        <domain-config cleartextTrafficPermitted="true">
            <domain includeSubdomains="true">secure.example.com</domain>
        </domain-config>
    </domain-config>
</network-security-config>

Feladat: Az eredmények korlátozása fájlnév/útvonal szerint

megoldás: Az egyik nagy projektben, amely egy Androidos mobilalkalmazás fejlesztéséhez kapcsolódott, az obfuszkációs beállítást meghatározó szabály hamis pozitívumaival találkoztunk. A tény az, hogy a szabály a dobozban keres a fájlban build.gradle egy beállítás, amely az alkalmazás kiadási verziójára vonatkozó obfuszkációs szabályok alkalmazásáért felelős.

De a nagy projektekben néha vannak gyermekfájlok build.gradle, amelyek a projektben szereplő könyvtárakra vonatkoznak. A sajátosság az, hogy ha ezek a fájlok nem is jelzik az obfuszkálás szükségességét, a fordítás során a szülő összeállítási fájl beállításai kerülnek alkalmazásra.

Így a feladat az, hogy levágja a triggereket a könyvtárakhoz tartozó gyermekfájlokban. A vonal jelenléte alapján azonosíthatók apply 'com.android.library'.

Példakód fájlból build.gradle, amely meghatározza a homályosítás szükségességét:

apply plugin: 'com.android.application'

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

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

dependencies {
  ...
}

Fájl példa build.gradle a projektben szereplő olyan könyvtárhoz, amely nem rendelkezik ezzel a beállítással:

apply plugin: 'android-library'

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

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

És a Checkmarx szabálya:

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 meglehetősen univerzális lehet, és nemcsak az Android-alkalmazások számára hasznos, hanem más esetekben is, amikor meg kell határoznia, hogy az eredmény egy adott fájlhoz tartozik-e.

Feladat: Ha a szintaxis nem teljesen támogatott, adjon hozzá támogatást egy harmadik féltől származó könyvtárhoz

megoldás: A kódírás során használt különféle keretrendszerek száma egyszerűen nem szerepel a diagramokon. Természetesen a Checkmarx nem mindig tud ezek létezéséről, és a mi feladatunk az, hogy megtanítsuk megérteni, hogy bizonyos módszerek kifejezetten ebbe a keretbe tartoznak. Néha ezt bonyolítja az a tény, hogy a keretrendszerek nagyon gyakori függvényneveket használnak, és lehetetlen egyértelműen meghatározni egy adott hívás és egy adott könyvtár kapcsolatát.

A nehézség az, hogy az ilyen könyvtárak szintaxisát nem mindig ismeri fel megfelelően, és kísérletezni kell, hogy elkerülje a nagyszámú hamis pozitív eredményt. Számos lehetőség kínálkozik a szkennelési pontosság javítására és a probléma megoldására:

  • Az első lehetőség, biztosan tudjuk, hogy a könyvtárat egy adott projektben használják, és a szabályt csapatszinten is alkalmazhatjuk. De ha a csapat úgy dönt, hogy más megközelítést alkalmaz, vagy több olyan könyvtárat használ, amelyekben a függvénynevek átfedésben vannak, akkor számos téves pozitív képet kaphatunk.

  • A második lehetőség az olyan fájlok keresése, amelyekben a könyvtár egyértelműen importálva van. Ezzel a megközelítéssel biztosak lehetünk abban, hogy a szükséges könyvtárat pontosan használjuk ebben a fájlban.

  • A harmadik lehetőség pedig a két fenti megközelítés együttes alkalmazása.

Példaként nézzünk meg egy szűk körökben jól ismert könyvtárat sima a Scala programozási nyelvre, nevezetesen a funkcionalitásra A szó szerinti értékek összeillesztése. Általában a paraméterek SQL-lekérdezésnek való átadásához az operátort kell használni $, amely az adatokat egy előre kialakított SQL-lekérdezésbe helyettesíti. Ez valójában a Java Prepared Statement közvetlen analógja. De ha dinamikusan kell létrehoznia egy SQL lekérdezést, például ha táblaneveket kell átadnia, használhatja az operátort. #$, amely közvetlenül behelyettesíti az adatokat a lekérdezésbe (majdnem olyan, mint a karakterlánc-összefűzés).

Minta kód:

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

A Checkmarx még nem tudja, hogyan észlelje a Splicing Literal Values ​​használatát, és kihagyja az operátorokat #$, tehát próbáljuk megtanítani arra, hogy azonosítsa a lehetséges SQL-injekciókat, és jelölje ki a megfelelő 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: Használt sebezhető funkciók keresése nyílt forráskódú könyvtárakban

megoldás: Sok vállalat nyílt forráskódú megfigyelő eszközöket (OSA gyakorlat) használ a könyvtárak sérülékeny verzióinak használatának észlelésére a fejlesztett alkalmazásokban. Néha nem lehetséges egy ilyen könyvtárat biztonságos verzióra frissíteni. Egyes esetekben működési korlátok vannak, másokban pedig egyáltalán nincs biztonságos verzió. Ebben az esetben a SAST és az OSA gyakorlatok kombinációja segít meghatározni, hogy a biztonsági rés kihasználásához vezető funkciókat ne használja a kód.

De néha, különösen ha a JavaScriptet vesszük figyelembe, ez nem teljesen triviális feladat. Az alábbiakban egy megoldás, talán nem ideális, de mégis működőképes, az összetevő sérülékenységeinek példáján lodash módszerekben template и *set.

Példák a potenciálisan sebezhető kód tesztelésére 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 html-ben csatlakozik:

<!DOCTYPE html>
<html>
<head>
    <title>Lodash Tutorial</title>
    <script src="./node_modules/lodash.js"></script>
    <script type="text/javascript">
  // Lodash chunking array
        nums = [1, 2, 3, 4, 5, 6, 7, 8, 9];

        let c1 = _.template('<% print("hello " + js); %>!');
        console.log(c1);

        let c2 = _.template('<% print("hello " + js); %>!');
        console.log(c2);
    </script>
</head>
<body></body>
</html>

Keresünk minden sebezhető módszerünket, amelyek a sebezhetőségek között vannak felsorolva:

// Ищем все строки: в которых встречается строка 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: Az alkalmazásba beágyazott tanúsítványok keresése

megoldás: Nem ritka, hogy az alkalmazások, különösen a mobilok, tanúsítványokat vagy kulcsokat használnak a különböző szerverek eléréséhez vagy az SSL-rögzítés ellenőrzéséhez. Biztonsági szempontból az ilyen dolgok kódban való tárolása nem a legjobb gyakorlat. Próbáljunk meg írni egy szabályt, amely hasonló fájlokat keres a tároló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: Feltört tokenek keresése az alkalmazásban

megoldás: Gyakran vissza kell vonni a feltört tokeneket vagy a kódban található egyéb fontos információkat. Természetesen nem jó ötlet a forráskódban tárolni őket, de a helyzetek eltérőek. A CxQL lekérdezéseknek köszönhetően az ehhez hasonló dolgok megtalálása meglehetősen egyszerű:

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

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

Következtetés

Remélem, hogy ez a cikk hasznos lesz azoknak, akik elkezdik ismerkedni a Checkmarx eszközzel. Talán azok is találnak valami hasznosat ebben az útmutatóban, akik már régóta írják saját szabályaikat.

Sajnos jelenleg hiányzik az a forrás, ahol új ötleteket lehetne gyűjteni a Checkmarx szabályainak kidolgozása során. Ezért alkottunk adattár a Githubon, ahol közzétesszük munkáinkat, hogy mindenki, aki CxQL-t használ, találjon benne valami hasznosat, és legyen lehetősége megosztani munkáját a közösséggel. Az adattár feltöltése, tartalom strukturálása folyamatban van, ezért szeretettel várjuk a hozzászólókat!

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

Forrás: will.com

Hozzászólás