Hoe regels voor Checkmarx te schrijven zonder gek te worden

Hé Habr!

In ons werk heeft ons bedrijf heel vaak te maken met verschillende statische code-analysetools (SAST). Out of the box werken ze allemaal gemiddeld. Het hangt natuurlijk allemaal af van het project en de technologieën die daarin worden gebruikt, en ook van hoe goed deze technologieën onder de analyseregels vallen. Naar mijn mening is een van de belangrijkste criteria bij het kiezen van een SAST-tool de mogelijkheid om deze aan te passen aan de specifieke kenmerken van uw applicaties, namelijk het schrijven en wijzigen van analyseregels of, zoals ze vaker worden genoemd, Custom Query's.

Hoe regels voor Checkmarx te schrijven zonder gek te worden

We gebruiken meestal Checkmarx - een zeer interessante en krachtige code-analysator. In dit artikel zal ik het hebben over mijn ervaring met het schrijven van analyseregels ervoor.

inhoudsopgave

Toegang

Om te beginnen zou ik een van de weinige artikelen in het Russisch willen aanbevelen over de kenmerken van het schrijven van query's voor Checkmarx. Het verscheen eind 2019 op Habré onder de titel: "Hallo, Checkmarx!" Hoe u een Checkmarx SAST-query schrijft en coole kwetsbaarheden vindt.

Het onderzoekt in detail hoe de eerste queries in CxQL (Checkmarx Query Language) voor een bepaalde testapplicatie geschreven moeten worden en toont de basisprincipes van hoe analyseregels werken.

Ik zal niet herhalen wat daarin wordt beschreven, hoewel er nog steeds enkele kruispunten aanwezig zullen zijn. In mijn artikel zal ik proberen een soort “verzameling recepten” samen te stellen, een lijst met oplossingen voor specifieke problemen die ik tegenkwam tijdens mijn werk met Checkmarx. Ik moest mijn hoofd breken over veel van deze problemen. Soms was er niet genoeg informatie in de documentatie, en soms was het zelfs moeilijk om te begrijpen hoe te doen wat nodig was. Ik hoop dat mijn ervaring en slapeloze nachten niet tevergeefs zullen zijn, en dat deze “verzameling recepten voor aangepaste zoekopdrachten” je een paar uur of een paar zenuwcellen zal besparen. Dus laten we beginnen!

Algemene informatie over de regels

Laten we eerst eens kijken naar enkele basisconcepten en het proces van het werken met de regels, zodat we beter kunnen begrijpen wat er daarna gaat gebeuren. En ook omdat de documentatie hier niets over zegt of erg verspreid is in de structuur, wat niet erg handig is.

  1. De regels worden tijdens het scannen toegepast, afhankelijk van de bij het starten geselecteerde voorinstelling (een reeks actieve regels). U kunt een onbeperkt aantal voorinstellingen maken, en hoe u deze precies structureert, hangt af van de specifieke kenmerken van uw proces. U kunt ze groeperen op taal of voorinstellingen voor elk project selecteren. Het aantal actieve regels heeft invloed op de snelheid en nauwkeurigheid van het scannen.

    Hoe regels voor Checkmarx te schrijven zonder gek te wordenVoorinstelling instellen in de Checkmarx-interface

  2. De regels worden bewerkt in een speciale tool genaamd CxAuditor. Dit is een desktopapplicatie die verbinding maakt met een server waarop Checkmarx draait. Deze tool heeft twee werkingsmodi: regels bewerken en de resultaten van een reeds uitgevoerde scan analyseren.

    Hoe regels voor Checkmarx te schrijven zonder gek te wordenCxAudit-interface

  3. Regels in Checkmarx zijn onderverdeeld per taal, dat wil zeggen dat elke taal zijn eigen reeks zoekopdrachten heeft. Er zijn ook enkele algemene regels die ongeacht de taal gelden, dit zijn de zogenaamde basisquery's. Basisquery's omvatten voor het grootste deel het zoeken naar informatie die andere regels gebruiken.

    Hoe regels voor Checkmarx te schrijven zonder gek te wordenRegels verdelen per taal

  4. Regels zijn "Uitvoerbaar" en "Niet-uitvoerbaar" (uitgevoerd en niet uitgevoerd). Niet helemaal de juiste naam, naar mijn mening, maar zo is het wel. Het komt erop neer dat het resultaat van het uitvoeren van ‘uitvoerbare’ regels wordt weergegeven in de scanresultaten in de gebruikersinterface, en dat ‘niet-uitvoerbare’ regels alleen nodig zijn om hun resultaten in andere verzoeken te gebruiken (in wezen slechts een functie).

    Hoe regels voor Checkmarx te schrijven zonder gek te wordenHet bepalen van het regeltype bij het maken

  5. U kunt nieuwe regels maken of bestaande aanvullen/herschrijven. Om een ​​regel te herschrijven, moet u deze in de boomstructuur vinden, met de rechtermuisknop klikken en 'Overschrijven' selecteren in het vervolgkeuzemenu. Het is belangrijk om hierbij te onthouden dat de nieuwe regels in eerste instantie niet in de presets zijn opgenomen en niet actief zijn. Om ze te kunnen gebruiken, moet u ze activeren in het menu “Preset Manager” van het instrument. Herschreven regels behouden hun instellingen, dat wil zeggen dat als de regel actief was, deze zo blijft en onmiddellijk wordt toegepast.

    Hoe regels voor Checkmarx te schrijven zonder gek te wordenVoorbeeld van een nieuwe regel in de Preset Manager-interface

  6. Tijdens de uitvoering wordt een ‘boom’ met verzoeken gebouwd, afhankelijk van wat. De regels die informatie verzamelen, worden als eerste uitgevoerd, en degenen die deze gebruiken als tweede. Het uitvoeringsresultaat wordt in de cache opgeslagen, dus als het mogelijk is om de resultaten van een bestaande regel te gebruiken, is het beter om dit te doen, dit zal de scantijd verkorten.

  7. Regels kunnen op verschillende niveaus worden toegepast:

  • Voor het hele systeem: wordt gebruikt voor elke scan van elk project

  • Op teamniveau (Team): wordt alleen gebruikt om projecten in het geselecteerde team te scannen.

  • Op projectniveau - Wordt toegepast in een specifiek project

    Hoe regels voor Checkmarx te schrijven zonder gek te wordenHet bepalen van het niveau waarop de regel wordt toegepast

"Woordenboek" voor beginners

En ik zal beginnen met een paar dingen die bij mij vragen opriepen, en ik zal ook een aantal technieken laten zien die het leven aanzienlijk zullen vereenvoudigen.

Bewerkingen met lijsten

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

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

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

Alle gevonden voorwerpen

Binnen de gescande taal kunt u een lijst krijgen van absoluut alle elementen die Checkmarx heeft geïdentificeerd (strings, functies, klassen, methoden, enz.). Dit is een ruimte met objecten waartoe toegang kan worden verkregen All. Dat wil zeggen: zoeken naar een object met een specifieke naam searchMekunt u bijvoorbeeld op naam zoeken in alle gevonden objecten:

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

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

Maar als u in een andere taal moet zoeken die om een ​​of andere reden niet in de scan is opgenomen (bijvoorbeeld groovy in een Android-project), kunt u onze objectruimte uitbreiden via een variabele:

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

Functies voor stroomanalyse

Deze functies worden in veel regels gebruikt en hier is een klein spiekbriefje van wat ze betekenen:

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

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

Bestandsnaam/pad ophalen

Er zijn verschillende attributen die kunnen worden verkregen uit de resultaten van een zoekopdracht (de naam van het bestand waarin het item is gevonden, een tekenreeks, enz.), maar de documentatie geeft niet aan hoe u deze kunt verkrijgen en gebruiken. Om dit te doen, heb je dus toegang nodig tot de eigenschap LinePragma en de objecten die we nodig hebben, zullen zich daarin bevinden:

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

Het is de moeite waard om dat in gedachten te houden FileName bevat eigenlijk het pad naar het bestand, aangezien we de methode hebben gebruikt GetFirstGraph.

Uitvoering resultaat

Er is een speciale variabele in CxQL result, wat het resultaat oplevert van het uitvoeren van uw geschreven regel. Het wordt onmiddellijk geïnitialiseerd en u kunt er tussentijdse resultaten in schrijven, die u tijdens het werken kunt wijzigen en verfijnen. Maar als er geen toewijzing is aan deze variabele of functie binnen de regel return— het uitvoeringsresultaat zal altijd nul zijn.

De volgende zoekopdracht levert ons niets op als gevolg van de uitvoering en is altijd leeg:

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

Maar nadat we het uitvoeringsresultaat hebben toegewezen aan het resultaat van de magische variabele, zullen we zien wat deze oproep naar ons terugkeert:

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

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

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

Gebruik maken van de resultaten van andere regels

Regels in Checkmarx kunnen analoog worden opgeroepen aan functies in een reguliere programmeertaal. Bij het schrijven van een regel kunt u mogelijk de resultaten van andere zoekopdrachten gebruiken. Het is bijvoorbeeld niet nodig om elke keer naar alle methodeaanroepen in de code te zoeken, maar roep gewoon de gewenste regel aan:

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

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

Met deze aanpak kunt u de code inkorten en de uitvoeringstijd van de regels aanzienlijk verkorten.

Probleem oplossen

Loggen

Wanneer u met de tool werkt, is het soms niet mogelijk om meteen de gewenste vraag te schrijven en moet u experimenteren en verschillende opties uitproberen. Voor een dergelijk geval biedt de tool logboekregistratie, die als volgt wordt aangeroepen:

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

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

Maar het is de moeite waard eraan te denken dat deze methode alleen als invoer accepteert snaar, dus het zal niet mogelijk zijn om een ​​volledige lijst met gevonden elementen weer te geven als resultaat van de eerste bewerking. De tweede optie, die wordt gebruikt voor het debuggen, is om van tijd tot tijd een magische variabele toe te wijzen result het resultaat van de zoekopdracht en kijk wat er gebeurt. Deze aanpak is niet erg handig; je moet er zeker van zijn dat er geen overschrijvingen of bewerkingen hiermee in de code daarna voorkomen result of geef gewoon commentaar op de onderstaande code. Of je kunt, net als ik, vergeten een aantal van dergelijke oproepen uit een kant-en-klare regel te verwijderen en je afvragen waarom niets werkt.

Een handiger manier is om de methode aan te roepen return met de vereiste parameter. In dit geval eindigt de uitvoering van de regel en kunnen we zien wat er is gebeurd als gevolg van wat we hebben geschreven:

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

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

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

Inloggen probleem

Er zijn situaties waarin u geen toegang heeft tot de CxAudit-tool (die wordt gebruikt om regels te schrijven). Hier kunnen veel redenen voor zijn, waaronder crashes, plotselinge Windows-updates, BSOD en andere onvoorziene situaties die buiten onze controle liggen. In dit geval is er soms sprake van een onvoltooide sessie in de database, waardoor u niet opnieuw kunt inloggen. Om dit probleem op te lossen, moet u verschillende query's uitvoeren:

Voor Checkmarx vóór 8.6:

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

Voor Checkmarx na 8.6:

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

Regels schrijven

Nu komen we bij het meest interessante deel. Wanneer u begint met het schrijven van regels in CxQL, ontbreekt het u vaak niet zozeer aan documentatie, maar aan enkele levende voorbeelden van het oplossen van bepaalde problemen en het beschrijven van het proces van hoe query's in het algemeen werken.

Ik zal proberen het leven een beetje gemakkelijker te maken voor degenen die zich beginnen te verdiepen in de querytaal en verschillende voorbeelden geven van het gebruik van aangepaste query's om bepaalde problemen op te lossen. Sommige zijn vrij algemeen en kunnen vrijwel zonder wijzigingen in uw bedrijf worden gebruikt, andere zijn specifieker, maar ze kunnen ook worden gebruikt door de code aan te passen aan de specifieke kenmerken van uw toepassingen.

Dit zijn dus de problemen die we het vaakst tegenkwamen:

doelstelling: Er zijn verschillende stromen in de resultaten van het uitvoeren van de regel en een daarvan is een nesting van een andere, u moet er een verlaten.

oplossing: Soms toont Checkmarx inderdaad meerdere gegevensstromen die elkaar kunnen overlappen en een verkorte versie van andere kunnen zijn. Er is een speciale methode voor dergelijke gevallen ReduceFlow. Afhankelijk van de parameter selecteert het de kortste of langste stroom:

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

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

doelstelling: Vouw de lijst met gevoelige gegevens uit waarop de tool reageert

oplossing: Checkmarx heeft basisregels waarvan de resultaten door veel andere zoekopdrachten worden gebruikt. Door een aantal van deze regels aan te vullen met gegevens die specifiek zijn voor uw toepassing, kunt u uw scanresultaten direct verbeteren. Hieronder vindt u een voorbeeldregel om u op weg te helpen:

Algemene_privacyschending_lijst

Laten we verschillende variabelen toevoegen die in onze applicatie worden gebruikt om gevoelige informatie op te slaan:

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

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

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

doelstelling: Vouw de lijst met variabelen uit met wachtwoorden

oplossing: Ik zou aanraden om onmiddellijk aandacht te besteden aan de basisregel voor het definiëren van wachtwoorden in code en daaraan een lijst met variabelenamen toe te voegen die vaak in uw bedrijf worden gebruikt.

Wachtwoord_privacy_schending_lijst

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

doelstelling: Voeg gebruikte frameworks toe die niet door Checkmarx worden ondersteund

oplossing: Alle zoekopdrachten in Checkmarx zijn per taal onderverdeeld, dus u moet voor elke taal regels toevoegen. Hieronder vindt u enkele voorbeelden van dergelijke regels.

Als er bibliotheken worden gebruikt die de standaardfunctionaliteit aanvullen of vervangen, kunnen deze eenvoudig aan de basisregel worden toegevoegd. Dan maakt iedereen die er gebruik van maakt direct kennis met de nieuwe introducties. Bibliotheken voor inloggen op Android zijn bijvoorbeeld Timber en Loggi. In het basispakket zijn er geen regels voor het identificeren van niet-systeemoproepen, dus als een wachtwoord of sessie-ID in het logboek terechtkomt, weten we daar niets van. Laten we proberen definities van dergelijke methoden aan de Checkmarx-regels toe te voegen.

Testcodevoorbeeld dat de Timber-bibliotheek gebruikt voor logboekregistratie:

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

En hier is een voorbeeld van een verzoek voor Checkmarx, waarmee u een definitie kunt toevoegen van het aanroepen van Timber-methoden als uitgangspunt voor gegevens uit de toepassing:

Zoek Android-uitvoer

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

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

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

En je kunt ook iets toevoegen aan de aangrenzende regel, maar deze heeft rechtstreeks betrekking op het inloggen op Android:

ZoekAndroidLog_Outputs

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

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

Ook als Android-applicaties gebruiken WerkManager voor asynchroon werk is het een goed idee om Checkmarx hierover aanvullend te informeren door een methode toe te voegen om gegevens uit de taak te halen getInputData:

ZoekAndroidRead

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

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

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

doelstelling: Zoeken naar gevoelige gegevens in plist voor iOS-projecten

oplossing: iOS gebruikt vaak speciale bestanden met de extensie .plist om verschillende variabelen en waarden op te slaan. Het opslaan van wachtwoorden, tokens, sleutels en andere gevoelige gegevens in deze bestanden wordt afgeraden, aangezien deze zonder problemen van het apparaat kunnen worden geëxtraheerd.

Plist-bestanden hebben functies die met het blote oog niet duidelijk zijn, maar die belangrijk zijn voor Checkmarx. Laten we een regel schrijven die zoekt naar de gegevens die we nodig hebben en ons vertelt of er ergens wachtwoorden of tokens worden vermeld.

Een voorbeeld van zo'n bestand, dat een token bevat voor communicatie met de backend-service:

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

En een regel voor Checkmarx, die verschillende nuances heeft waarmee bij het schrijven rekening moet worden gehouden:

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

doelstelling: Informatie zoeken in XML

oplossing: Checkmarx heeft zeer handige functies voor het werken met XML en het zoeken naar waarden, tags, attributen en meer. Maar helaas was er een fout in de documentatie waardoor geen enkel voorbeeld werkt. Ondanks het feit dat dit defect in de nieuwste versie van de documentatie is geëlimineerd, moet u voorzichtig zijn als u eerdere versies van documenten gebruikt.

Hier is een onjuist voorbeeld uit de documentatie:

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

Als gevolg van de uitvoeringspoging krijgen we een foutmelding dat All zo'n methode bestaat niet... En dit is waar, aangezien er een speciale, aparte objectruimte is voor het gebruik van functies voor het werken met XML - cxXPath. Zo ziet de juiste zoekopdracht eruit om een ​​instelling in Android te vinden die het gebruik van HTTP-verkeer toestaat:

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

Laten we het wat gedetailleerder bekijken, aangezien de syntaxis voor alle functies vergelijkbaar is. Nadat je er een hebt bedacht, hoef je alleen maar degene te selecteren die je nodig hebt. Dus opeenvolgend volgens de parameters:

  • "*.xml"— masker van bestanden die moeten worden doorzocht

  • 8 — ID van de taal waarvoor de regel wordt toegepast

  • "cleartextTrafficPermitted"— attribuutnaam in xml

  • "true" — de waarde van dit attribuut

  • false — gebruik van reguliere expressies bij het zoeken

  • true — betekent dat de zoekopdracht wordt uitgevoerd zonder hoofdlettergebruik, dat wil zeggen hoofdletterongevoelig

We hebben bijvoorbeeld een regel gebruikt die, vanuit veiligheidsoogpunt, onjuiste netwerkverbindingsinstellingen in Android identificeert die communicatie met de server via het HTTP-protocol mogelijk maken. Voorbeeld van een instelling die een attribuut bevat cleartextTrafficPermitted met betekenis 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>

doelstelling: Beperk de resultaten op bestandsnaam/pad

oplossing: In een van de grote projecten gerelateerd aan de ontwikkeling van een mobiele applicatie voor Android kwamen we valse positieven tegen over de regel die de verduisteringsinstelling bepaalt. Feit is dat de regel out-of-the-box in het bestand zoekt build.gradle een instelling die verantwoordelijk is voor het toepassen van verduisteringsregels voor de releaseversie van de applicatie.

Maar bij grote projecten zijn er soms onderliggende bestanden build.gradle, die verwijzen naar de bibliotheken die in het project zijn opgenomen. Het bijzondere is dat zelfs als deze bestanden niet aangeven dat verduistering nodig is, de instellingen van het bovenliggende assemblagebestand tijdens de compilatie zullen worden toegepast.

Het is dus de taak om triggers af te sluiten in onderliggende bestanden die tot bibliotheken behoren. Ze zijn te herkennen aan de aanwezigheid van de lijn apply 'com.android.library'.

Voorbeeldcode uit bestand build.gradle, die de noodzaak van verduistering bepaalt:

apply plugin: 'com.android.application'

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

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

dependencies {
  ...
}

Bestand voorbeeld build.gradle voor een bibliotheek die deel uitmaakt van het project en die deze instelling niet heeft:

apply plugin: 'android-library'

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

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

En de regel voor 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);
		}
	}
}

Deze aanpak kan vrij universeel en nuttig zijn, niet alleen voor Android-applicaties, maar ook voor andere gevallen waarin u moet bepalen of een resultaat bij een specifiek bestand hoort.

doelstelling: Voeg ondersteuning toe voor een bibliotheek van derden als de syntaxis niet volledig wordt ondersteund

oplossing: Het aantal verschillende raamwerken dat wordt gebruikt bij het schrijven van code is simpelweg buiten de hitlijsten. Natuurlijk is Checkmarx niet altijd op de hoogte van hun bestaan, en het is onze taak om het te leren begrijpen dat bepaalde methoden specifiek tot dit raamwerk behoren. Soms wordt dit gecompliceerd door het feit dat frameworks functienamen gebruiken die heel gebruikelijk zijn en het onmogelijk is om op ondubbelzinnige wijze de relatie van een bepaalde aanroep met een specifieke bibliotheek te bepalen.

De moeilijkheid is dat de syntaxis van dergelijke bibliotheken niet altijd correct wordt herkend en dat u moet experimenteren om te voorkomen dat u een groot aantal valse positieven krijgt. Er zijn verschillende opties om de scannauwkeurigheid te verbeteren en het probleem op te lossen:

  • Bij de eerste optie weten we zeker dat de bibliotheek in een specifiek project wordt gebruikt en kunnen we de regel op teamniveau toepassen. Maar als het team besluit een andere aanpak te kiezen of meerdere bibliotheken gebruikt waarin functienamen elkaar overlappen, kunnen we een niet erg prettig beeld krijgen van talloze valse positieven

  • De tweede optie is om te zoeken naar bestanden waarin de bibliotheek duidelijk geïmporteerd is. Met deze aanpak kunnen we er zeker van zijn dat de bibliotheek die we nodig hebben precies in dit bestand wordt gebruikt.

  • En de derde optie is om de twee bovenstaande benaderingen samen te gebruiken.

Laten we als voorbeeld eens kijken naar een bibliotheek die in kleine kringen bekend is glad voor de programmeertaal Scala, namelijk de functionaliteit Letterlijke waarden verbinden. Als u parameters wilt doorgeven aan een SQL-query, moet u over het algemeen de operator gebruiken $, waarmee gegevens worden vervangen door een vooraf gevormde SQL-query. Dat wil zeggen dat het in feite een directe analogie is van Prepared Statement in Java. Maar als u dynamisch een SQL-query moet samenstellen, bijvoorbeeld als u tabelnamen moet doorgeven, kunt u de operator gebruiken #$, waarmee de gegevens rechtstreeks in de query worden vervangen (bijna zoals tekenreeksaaneenschakeling).

Voorbeeldcode:

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

Checkmarx weet nog niet hoe hij het gebruik van Splicing Literal Values ​​moet detecteren en slaat operators over #$, dus laten we proberen het te leren potentiële SQL-injecties te identificeren en de juiste plaatsen in de code te markeren:

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

doelstelling: Zoek naar gebruikte kwetsbare functies in Open-Source-bibliotheken

oplossing: Veel bedrijven gebruiken Open-Source monitoring tools (OSA-praktijk) om het gebruik van kwetsbare versies van bibliotheken in ontwikkelde applicaties te detecteren. Soms is het niet mogelijk om zo’n bibliotheek te updaten naar een beveiligde versie. In sommige gevallen zijn er functionele beperkingen, in andere gevallen is er helemaal geen veilige versie. In dit geval zal een combinatie van SAST- en OSA-praktijken helpen vaststellen dat de functies die tot misbruik van de kwetsbaarheid leiden, niet in de code worden gebruikt.

Maar soms, vooral als het om JavaScript gaat, is dit misschien geen volkomen triviale taak. Hieronder staat een oplossing, misschien niet ideaal, maar toch werkend, aan de hand van het voorbeeld van kwetsbaarheden in de component lodash in methoden template и *set.

Voorbeelden van het testen van potentieel kwetsbare code in een JS-bestand:

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

En wanneer u rechtstreeks verbinding maakt in 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>

We zijn op zoek naar al onze kwetsbare methoden, die staan ​​vermeld bij kwetsbaarheden:

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

doelstelling: Zoeken naar certificaten die in de applicatie zijn ingebed

oplossing: Het is niet ongebruikelijk dat applicaties, vooral mobiele applicaties, certificaten of sleutels gebruiken om toegang te krijgen tot verschillende servers of om SSL-pinning te verifiëren. Vanuit veiligheidsperspectief is het opslaan van dergelijke zaken in code niet de beste praktijk. Laten we proberen een regel te schrijven die naar vergelijkbare bestanden in de repository zoekt:

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

doelstelling: Gecompromitteerde tokens vinden in de toepassing

oplossing: Het is vaak nodig om gecompromitteerde tokens of andere belangrijke informatie in de code in te trekken. Het is natuurlijk geen goed idee om ze in de broncode op te slaan, maar de situatie kan variëren. Dankzij CxQL-query's is het vinden van dit soort dingen vrij eenvoudig:

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

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

Conclusie

Ik hoop dat dit artikel nuttig zal zijn voor degenen die kennis maken met de Checkmarx-tool. Misschien zullen degenen die al heel lang hun eigen regels schrijven, ook iets nuttigs in deze gids vinden.

Helaas is er momenteel een gebrek aan een bron waar nieuwe ideeën kunnen worden verzameld tijdens de ontwikkeling van regels voor Checkmarx. Daarom hebben wij gecreëerd repository op Github, waar we ons werk zullen posten, zodat iedereen die CxQL gebruikt er iets nuttigs in kan vinden, en ook de mogelijkheid heeft om zijn werk met de gemeenschap te delen. De repository is bezig met het vullen en structureren van inhoud, dus bijdragers zijn welkom!

Dank je wel!

Bron: www.habr.com

Voeg een reactie