Hur man skriver regler för Checkmarx utan att bli galen

Hej Habr!

I vårt arbete sysslar vårt företag mycket ofta med olika verktyg för statisk kodanalys (SAST). Ur lådan fungerar de alla medelmåttigt. Naturligtvis beror allt på projektet och de tekniker som används i det, samt hur väl dessa tekniker omfattas av analysreglerna. Enligt min åsikt är ett av de viktigaste kriterierna när du väljer ett SAST-verktyg möjligheten att anpassa det efter specifikationerna för dina applikationer, nämligen att skriva och ändra analysregler eller, som de oftare kallas, Custom Queries.

Hur man skriver regler för Checkmarx utan att bli galen

Vi använder oftast Checkmarx - en mycket intressant och kraftfull kodanalysator. I den här artikeln kommer jag att berätta om min erfarenhet av att skriva analysregler för det.

innehållsförteckning

Entry

Till att börja med skulle jag vilja rekommendera en av få artiklar på ryska om funktionerna i att skriva frågor för Checkmarx. Den publicerades på Habré i slutet av 2019 under titeln: "Hej, Checkmarx!" Hur man skriver en Checkmarx SAST-fråga och hittar coola sårbarheter.

Den undersöker i detalj hur man skriver de första frågorna i CxQL (Checkmarx Query Language) för vissa testapplikationer och visar de grundläggande principerna för hur analysregler fungerar.

Jag kommer inte att upprepa det som beskrivs i den, även om vissa korsningar fortfarande kommer att finnas. I min artikel kommer jag att försöka sammanställa en sorts "receptsamling", en lista med lösningar på specifika problem som jag stötte på under mitt arbete med Checkmarx. Jag var tvungen att tappa hjärnan över många av dessa problem. Ibland fanns det inte tillräckligt med information i dokumentationen, och ibland var det till och med svårt att förstå hur man skulle göra det som krävdes. Jag hoppas att min erfarenhet och sömnlösa nätter inte kommer att vara förgäves, och denna "samling av recept på anpassade frågor" kommer att spara dig några timmar eller ett par nervceller. Så, låt oss börja!

Allmän information om reglerna

Låt oss först titta på några grundläggande begrepp och processen för att arbeta med reglerna, för en bättre förståelse för vad som kommer att hända härnäst. Och även för att dokumentationen inte säger något om detta eller är väldigt spridd i strukturen, vilket inte är särskilt bekvämt.

  1. Reglerna tillämpas under skanning beroende på den förinställning som valdes vid starten (en uppsättning aktiva regler). Du kan skapa ett obegränsat antal förinställningar, och exakt hur du strukturerar dem beror på detaljerna i din process. Du kan gruppera dem efter språk eller välja förinställningar för varje projekt. Antalet aktiva regler påverkar skanningens hastighet och noggrannhet.

    Hur man skriver regler för Checkmarx utan att bli galenStälla in förinställning i Checkmarx-gränssnittet

  2. Reglerna redigeras i ett speciellt verktyg som heter CxAuditor. Detta är ett skrivbordsprogram som ansluter till en server som kör Checkmarx. Det här verktyget har två funktionssätt: redigera regler och analysera resultaten av en redan utförd skanning.

    Hur man skriver regler för Checkmarx utan att bli galenCxAudit gränssnitt

  3. Regler i Checkmarx är uppdelade efter språk, det vill säga varje språk har sin egen uppsättning frågor. Det finns även några generella regler som gäller oavsett språk, det är de så kallade basfrågorna. För det mesta innebär grundläggande frågor att söka efter information som andra regler använder.

    Hur man skriver regler för Checkmarx utan att bli galenDela upp regler efter språk

  4. Reglerna är "Executable" och "Non-Executable" (Executed and Not Executed). Inte riktigt det korrekta namnet, enligt mig, men det är vad det är. Summan av kardemumman är att resultatet av att köra "Körbara"-regler kommer att visas i skanningsresultaten i användargränssnittet, och "Icke-körbara"-regler behövs bara för att använda deras resultat i andra förfrågningar (i själva verket är de bara en funktion ).

    Hur man skriver regler för Checkmarx utan att bli galenBestämmer regeltypen när du skapar

  5. Du kan skapa nya regler eller komplettera/skriva om befintliga. För att skriva om en regel måste du hitta den i trädet, högerklicka och välja "Åsidosätt" från rullgardinsmenyn. Det är viktigt att komma ihåg här att de nya reglerna initialt inte ingår i förinställningarna och inte är aktiva. För att börja använda dem måste du aktivera dem i menyn "Preset Manager" i instrumentet. Omskrivna regler behåller sina inställningar, det vill säga om regeln var aktiv förblir den så och kommer att tillämpas omedelbart.

    Hur man skriver regler för Checkmarx utan att bli galenExempel på en ny regel i Preset Manager-gränssnittet

  6. Under exekveringen byggs ett "träd" av förfrågningar, vilket beror på vad. Reglerna som samlar in information exekveras först, och de som använder den exekveras sedan. Exekveringsresultatet cachelagras, så om det är möjligt att använda resultaten av en befintlig regel, är det bättre att göra det, detta kommer att minska skanningstiden.

  7. Regler kan tillämpas på olika nivåer:

  • För hela systemet - kommer att användas för all skanning av alla projekt

  • På teamnivå - kommer endast att användas för att skanna projekt i det valda teamet.

  • På projektnivå - Kommer att tillämpas i ett specifikt projekt

    Hur man skriver regler för Checkmarx utan att bli galenBestämma nivån på vilken regeln ska tillämpas

"Ordbok" för nybörjare

Och jag börjar med några saker som orsakade mig frågor, och jag kommer också att visa ett antal tekniker som avsevärt kommer att förenkla livet.

Operationer med listor

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

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

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

Alla hittade föremål

Inom det skannade språket kan du få en lista över absolut alla element som Checkmarx har identifierat (strängar, funktioner, klasser, metoder etc.). Detta är ett utrymme med objekt som kan nås genom All. Det vill säga att söka efter ett objekt med ett specifikt namn searchMe, kan du till exempel söka efter namn över alla hittade objekt:

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

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

Men om du behöver söka på ett annat språk som av någon anledning inte ingick i skanningen (till exempel groovy i ett Android-projekt), kan du utöka vårt objektutrymme genom en variabel:

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

Funktioner för flödesanalys

Dessa funktioner används i många regler och här är ett litet fuskblad om vad de betyder:

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

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

Hämtar filnamn/sökväg

Det finns flera attribut som kan erhållas från resultaten av en fråga (namnet på filen där posten hittades, sträng, etc.), men dokumentationen säger inte hur man skaffar och använder dem. Så för att göra detta måste du komma åt LinePragma-egenskapen och de objekt vi behöver kommer att finnas inuti den:

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

Det är värt att komma ihåg det FileName innehåller faktiskt sökvägen till filen, eftersom vi använde metoden GetFirstGraph.

Utförande resultat

Det finns en speciell variabel inuti CxQL result, som returnerar resultatet av exekvering av din skrivna regel. Det initieras omedelbart och du kan skriva mellanresultat i det, ändra och förfina dem medan du arbetar. Men om det inte finns någon tilldelning till denna variabel eller funktion i regeln return— exekveringsresultatet kommer alltid att vara noll.

Följande fråga kommer inte att returnera något till oss som ett resultat av exekvering och kommer alltid att vara tom:

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

Men efter att ha tilldelat exekveringsresultatet till det magiska variabelresultatet, kommer vi att se vad detta anrop returnerar till oss:

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

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

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

Använder resultaten av andra regler

Regler i Checkmarx kan kallas analoga med funktioner i ett vanligt programmeringsspråk. När du skriver en regel kan du mycket väl använda resultaten av andra frågor. Till exempel, det finns inget behov av att söka efter alla metodanrop i koden varje gång, anrop bara den önskade regeln:

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

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

Detta tillvägagångssätt låter dig förkorta koden och avsevärt minska regelexekveringstiden.

Problemlösning

Skogsavverkning

När du arbetar med verktyget är det ibland inte möjligt att omedelbart skriva den önskade frågan och du måste experimentera och prova olika alternativ. I ett sådant fall tillhandahåller verktyget loggning, som kallas enligt följande:

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

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

Men det är värt att komma ihåg att den här metoden bara accepteras som input sträng, så det kommer inte att vara möjligt att visa en komplett lista över hittade element som ett resultat av den första operationen. Det andra alternativet, som används för felsökning, är att tilldela en magisk variabel då och då result resultatet av frågan och se vad som händer. Detta tillvägagångssätt är inte särskilt bekvämt; du måste vara säker på att det inte finns några åsidosättningar eller operationer med detta i koden efter result eller bara kommentera koden nedan. Eller så kan man som jag glömma bort att ta bort flera sådana samtal från en färdig regel och undra varför inget fungerar.

Ett bekvämare sätt är att anropa metoden return med önskad parameter. I det här fallet kommer exekveringen av regeln att sluta och vi kommer att kunna se vad som hände som ett resultat av det vi skrev:

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

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

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

Inloggningsproblem

Det finns situationer när du inte kan komma åt CxAudit-verktyget (som används för att skriva regler). Det kan finnas många orsaker till detta, inklusive krascher, plötsliga Windows-uppdateringar, BSOD och andra oförutsedda situationer som ligger utanför vår kontroll. I det här fallet finns det ibland en oavslutad session i databasen, vilket hindrar dig från att logga in igen. För att fixa det måste du köra flera frågor:

För Checkmarx före 8.6:

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

För Checkmarx efter 8.6:

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

Skrivregler

Nu kommer vi till det mest intressanta. När du börjar skriva regler i CxQL är det du ofta saknar inte så mycket dokumentation som några levande exempel på att lösa vissa problem och beskriva processen för frågeoperation i allmänhet.

Jag ska försöka göra livet lite lättare för de som börjar dyka in i frågespråket och ge flera exempel på hur man använder Custom Queries för att lösa vissa problem. Vissa av dem är ganska generella och kan användas i ditt företag praktiskt taget utan ändringar, andra är mer specifika, men de kan också användas genom att ändra koden så att den passar dina applikationer.

Så här är de problem vi stötte på oftast:

Uppgift: Det finns flera flöden i resultaten av att köra regeln och en av dem är en kapsling av en annan, du måste lämna en av dem.

lösning: Visserligen visar Checkmarx ibland flera dataflöden som kan överlappa varandra och vara en förkortad version av andra. Det finns en speciell metod för sådana fall Minska flödet. Beroende på parametern kommer den att välja det kortaste eller längsta flödet:

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

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

Uppgift: Utöka listan över känsliga data som verktyget reagerar på

lösning: Checkmarx har grundläggande regler, vars resultat används av många andra frågor. Genom att komplettera några av dessa regler med data som är specifik för din applikation kan du omedelbart förbättra dina skanningsresultat. Nedan följer en exempelregel för att komma igång:

General_privacy_violation_list

Låt oss lägga till flera variabler som används i vår applikation för att lagra känslig information:

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

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

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

Uppgift: Utöka listan över variabler med lösenord

lösning: Jag skulle rekommendera att du omedelbart uppmärksammar grundregeln för att definiera lösenord i kod och lägger till en lista över variabelnamn som vanligtvis används i ditt företag.

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

Uppgift: Lägg till använda ramverk som inte stöds av Checkmarx

lösning: Alla frågor i Checkmarx är uppdelade efter språk, så du måste lägga till regler för varje språk. Nedan följer några exempel på sådana regler.

Om bibliotek används som kompletterar eller ersätter standardfunktionalitet kan de enkelt läggas till grundregeln. Då lär alla som använder det omedelbart om de nya introduktionerna. Som ett exempel är bibliotek för inloggning i Android Timber och Loggi. I grundpaketet finns det inga regler för att identifiera icke-systemsamtal, så om ett lösenord eller sessionsidentifierare kommer in i loggen kommer vi inte att veta om det. Låt oss försöka lägga till definitioner av sådana metoder till Checkmarx-reglerna.

Testkodexempel som använder Timber-biblioteket för loggning:

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

Och här är ett exempel på en begäran om Checkmarx, som låter dig lägga till en definition av anrop av Timber-metoder som en utgångspunkt för data från applikationen:

FindAndroidOutputs

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

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

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

Och du kan också lägga till angränsande regel, men den här relaterar direkt till inloggning på Android:

FindAndroidLog_Outputs

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

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

Även om Android-applikationer använder WorkManager för asynkront arbete är det en bra idé att dessutom informera Checkmarx om detta genom att lägga till en metod för att hämta data från uppgiften getInputData:

FindAndroidRead

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

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

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

Uppgift: Söker efter känslig data i plist för iOS-projekt

lösning: iOS använder ofta specialfiler med tillägget .plist för att lagra olika variabler och värden. Att lagra lösenord, tokens, nycklar och annan känslig data i dessa filer rekommenderas inte, eftersom de kan extraheras från enheten utan problem.

Plist-filer har funktioner som inte är uppenbara för blotta ögat, men som är viktiga för Checkmarx. Låt oss skriva en regel som kommer att söka efter den data vi behöver och tala om för oss om lösenord eller tokens nämns någonstans.

Ett exempel på en sådan fil, som innehåller en token för kommunikation med backend-tjänsten:

<?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>

Och en regel för Checkmarx, som har flera nyanser som bör beaktas när du skriver:

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

Uppgift: Hitta information i XML

lösning: Checkmarx har mycket bekväma funktioner för att arbeta med XML och söka efter värden, taggar, attribut med mera. Men tyvärr fanns det ett fel i dokumentationen på grund av att inte ett enda exempel fungerar. Trots att denna defekt har eliminerats i den senaste versionen av dokumentationen, var försiktig om du använder tidigare versioner av dokument.

Här är ett felaktigt exempel från dokumentationen:

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

Som ett resultat av exekveringsförsöket kommer vi att få ett felmeddelande som All det finns ingen sådan metod... Och det är sant, eftersom det finns ett speciellt, separat objektutrymme för att använda funktioner för att arbeta med XML - cxXPath. Så här ser den korrekta frågan ut för att hitta en inställning i Android som tillåter användning av HTTP-trafik:

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

Låt oss titta på det lite mer detaljerat, eftersom syntaxen för alla funktioner är liknande, efter att du har listat ut en, behöver du bara välja den du behöver. Så, sekventiellt enligt parametrarna:

  • "*.xml"— mask av filer som ska genomsökas

  • 8 — ID för det språk som regeln tillämpas på

  • "cleartextTrafficPermitted"— attributnamn i xml

  • "true" — värdet av detta attribut

  • false — användning av reguljärt uttryck vid sökning

  • true — betyder att sökningen kommer att utföras utan att skiftläge ignoreras, det vill säga skiftlägesokänslig

Som exempel använde vi en regel som identifierar felaktiga, ur säkerhetssynpunkt, nätverksanslutningsinställningar i Android som tillåter kommunikation med servern via HTTP-protokollet. Exempel på en inställning som innehåller ett attribut cleartextTrafficPermitted med mening 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>

Uppgift: Begränsa resultat efter filnamn/sökväg

lösning: I ett av de stora projekten relaterade till utvecklingen av en mobilapplikation för Android stötte vi på falska positiva resultat av regeln som bestämmer obfuskationsinställningen. Faktum är att regeln out of the box söker i filen build.gradle en inställning som ansvarar för att tillämpa fördunklingsregler för applikationens releaseversion.

Men i stora projekt finns det ibland underordnade filer build.gradle, som hänvisar till de bibliotek som ingår i projektet. Det speciella är att även om dessa filer inte indikerar behovet av obfuskering, kommer inställningarna för den överordnade sammanställningsfilen att tillämpas under kompileringen.

Uppgiften är alltså att skära av triggers i underordnade filer som tillhör bibliotek. De kan identifieras genom förekomsten av linjen apply 'com.android.library'.

Exempelkod från fil build.gradle, som bestämmer behovet av förvirring:

apply plugin: 'com.android.application'

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

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

dependencies {
  ...
}

Filexempel build.gradle för ett bibliotek som ingår i projektet som inte har denna inställning:

apply plugin: 'android-library'

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

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

Och regeln för 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);
		}
	}
}

Detta tillvägagångssätt kan vara ganska universellt och användbart, inte bara för Android-applikationer, utan också för andra fall när du behöver avgöra om ett resultat tillhör en specifik fil.

Uppgift: Lägg till stöd för ett tredje parts bibliotek om syntaxen inte stöds fullt ut

lösning: Antalet olika ramverk som används i processen att skriva kod är helt enkelt utanför diagrammet. Checkmarx vet naturligtvis inte alltid om deras existens, och vår uppgift är att lära den att förstå att vissa metoder hör specifikt till detta ramverk. Ibland kompliceras detta av det faktum att ramverk använder funktionsnamn som är mycket vanliga och det är omöjligt att entydigt bestämma förhållandet mellan ett visst anrop och ett specifikt bibliotek.

Svårigheten är att syntaxen för sådana bibliotek inte alltid känns igen korrekt och du måste experimentera för att undvika att få ett stort antal falska positiva resultat. Det finns flera alternativ för att förbättra skanningsnoggrannheten och lösa problemet:

  • Det första alternativet, vi vet med säkerhet att biblioteket används i ett specifikt projekt och kan tillämpa regeln på teamnivå. Men om teamet bestämmer sig för att ta ett annat tillvägagångssätt eller använder flera bibliotek där funktionsnamn överlappar varandra, kan vi få en inte särskilt trevlig bild av många falska positiva

  • Det andra alternativet är att söka efter filer där biblioteket är tydligt importerat. Med detta tillvägagångssätt kan vi vara säkra på att biblioteket vi behöver används exakt i den här filen.

  • Och det tredje alternativet är att använda de två ovanstående metoderna tillsammans.

Som ett exempel, låt oss titta på ett bibliotek som är välkänt i snäva kretsar smart för programmeringsspråket Scala, nämligen funktionaliteten Skarva bokstavliga värden. I allmänhet måste du använda operatorn för att skicka parametrar till en SQL-fråga $, som ersätter data med en förformad SQL-fråga. Det vill säga, i själva verket är det en direkt analog till Prepared Statement i Java. Men om du behöver konstruera en SQL-fråga dynamiskt, till exempel om du behöver skicka tabellnamn, kan du använda operatorn #$, som direkt kommer att ersätta data i frågan (nästan som strängsammansättning).

Exempelkod:

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

Checkmarx vet ännu inte hur man upptäcker användningen av Splicing Literal Values ​​och hoppar över operatorer #$, så låt oss försöka lära det att identifiera potentiella SQL-injektioner och markera de rätta platserna i koden:

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

Uppgift: Sök efter använda sårbara funktioner i bibliotek med öppen källkod

lösning: Många företag använder övervakningsverktyg med öppen källkod (OSA-praxis) för att upptäcka användningen av sårbara versioner av bibliotek i utvecklade applikationer. Ibland är det inte möjligt att uppdatera ett sådant bibliotek till en säker version. I vissa fall finns det funktionella begränsningar, i andra finns det ingen säker version alls. I det här fallet kommer en kombination av SAST- och OSA-praxis att hjälpa till att fastställa att de funktioner som leder till att sårbarheten utnyttjas inte används i koden.

Men ibland, särskilt när man överväger JavaScript, kanske detta inte är en helt trivial uppgift. Nedan är en lösning, kanske inte idealisk, men fungerar ändå, med exemplet med sårbarheter i komponenten lodash i metoder template и *set.

Exempel på test av potentiellt sårbar kod i en JS-fil:

/**
 * 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!'

Och när du ansluter direkt i html:

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

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

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

Vi letar efter alla våra sårbara metoder, som är listade i sårbarheter:

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

Uppgift: Söker efter certifikat inbäddade i applikationen

lösning: Det är inte ovanligt att applikationer, särskilt mobila, använder certifikat eller nycklar för att komma åt olika servrar eller verifiera SSL-pinning. Ur ett säkerhetsperspektiv är det inte den bästa praxis att lagra sådana saker i kod. Låt oss försöka skriva en regel som kommer att söka efter liknande filer i förvaret:

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

Uppgift: Hitta komprometterade tokens i applikationen

lösning: Det är ofta nödvändigt att återkalla komprometterade tokens eller annan viktig information som finns i koden. Naturligtvis är det ingen bra idé att lagra dem i källkoden, men situationerna varierar. Tack vare CxQL-frågor är det ganska enkelt att hitta saker som detta:

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

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

Slutsats

Jag hoppas att den här artikeln kommer att vara användbar för dem som börjar bekanta sig med Checkmarx-verktyget. Kanske kommer de som skrivit sina egna regler länge också att hitta något användbart i den här guiden.

Tyvärr saknas det för närvarande en resurs där nya idéer skulle kunna hämtas under utvecklingen av regler för Checkmarx. Det är därför vi skapade repository på Github, där vi kommer lägga upp vårt arbete så att alla som använder CxQL kan hitta något användbart i det, och även ha möjlighet att dela sitt arbete med communityn. Förvaret håller på att fylla och strukturera innehåll, så bidragsgivare är välkomna!

Tack för din uppmärksamhet!

Källa: will.com

Lägg en kommentar