Wie man Regeln für Checkmarx schreibt, ohne verrückt zu werden

Hey Habr!

In unserer Arbeit beschäftigt sich unser Unternehmen sehr häufig mit verschiedenen statischen Code-Analyse-Tools (SAST). Im Auslieferungszustand funktionieren sie alle durchschnittlich. Natürlich hängt alles vom Projekt und den darin verwendeten Technologien ab und auch davon, wie gut diese Technologien von den Analyseregeln abgedeckt werden. Meiner Meinung nach ist eines der wichtigsten Kriterien bei der Auswahl eines SAST-Tools die Möglichkeit, es an die Besonderheiten Ihrer Anwendungen anzupassen, nämlich Analyseregeln zu schreiben und zu ändern oder, wie sie häufiger genannt werden, benutzerdefinierte Abfragen.

Wie man Regeln für Checkmarx schreibt, ohne verrückt zu werden

Am häufigsten verwenden wir Checkmarx – einen sehr interessanten und leistungsstarken Code-Analysator. In diesem Artikel werde ich über meine Erfahrungen beim Schreiben von Analyseregeln dafür sprechen.

Inhaltsverzeichnis

Eintrag

Zunächst möchte ich einen der wenigen Artikel auf Russisch über die Funktionen beim Schreiben von Abfragen für Checkmarx empfehlen. Es erschien Ende 2019 auf Habré unter dem Titel: „Hallo, Checkmarx!“ So schreiben Sie eine Checkmarx-SAST-Abfrage und finden coole Schwachstellen.

Es untersucht im Detail, wie man die ersten Abfragen in CxQL (Checkmarx Query Language) für eine Testanwendung schreibt und zeigt die Grundprinzipien der Funktionsweise von Analyseregeln.

Ich werde nicht wiederholen, was darin beschrieben wird, obwohl einige Überschneidungen noch vorhanden sein werden. In meinem Artikel werde ich versuchen, eine Art „Rezeptsammlung“ zusammenzustellen, eine Liste von Lösungen für spezifische Probleme, auf die ich während meiner Arbeit mit Checkmarx gestoßen bin. Über viele dieser Probleme musste ich mir den Kopf zerbrechen. Manchmal enthielt die Dokumentation nicht genügend Informationen und manchmal war es sogar schwierig zu verstehen, wie das Erforderliche erledigt werden sollte. Ich hoffe, dass meine Erfahrung und meine schlaflosen Nächte nicht umsonst waren und dass diese „Sammlung von Rezepten für benutzerdefinierte Abfragen“ Ihnen ein paar Stunden oder ein paar Nervenzellen ersparen wird. Also, fangen wir an!

Allgemeine Informationen zu den Regeln

Schauen wir uns zunächst einige grundlegende Konzepte und den Prozess der Arbeit mit den Regeln an, um besser zu verstehen, was als nächstes passieren wird. Und auch, weil die Dokumentation dazu nichts sagt oder in der Struktur sehr weit verstreut ist, was nicht sehr praktisch ist.

  1. Die Regeln werden beim Scannen abhängig von der beim Start ausgewählten Voreinstellung (ein Satz aktiver Regeln) angewendet. Sie können eine unbegrenzte Anzahl von Voreinstellungen erstellen. Die genaue Strukturierung dieser Voreinstellungen hängt von den Besonderheiten Ihres Prozesses ab. Sie können sie nach Sprache gruppieren oder Voreinstellungen für jedes Projekt auswählen. Die Anzahl der aktiven Regeln beeinflusst die Geschwindigkeit und Genauigkeit des Scanvorgangs.

    Wie man Regeln für Checkmarx schreibt, ohne verrückt zu werdenPreset in der Checkmarx-Oberfläche einrichten

  2. Die Regeln werden in einem speziellen Tool namens CxAuditor bearbeitet. Dies ist eine Desktop-Anwendung, die eine Verbindung zu einem Server herstellt, auf dem Checkmarx ausgeführt wird. Dieses Tool verfügt über zwei Betriebsmodi: Bearbeiten von Regeln und Analysieren der Ergebnisse eines bereits durchgeführten Scans.

    Wie man Regeln für Checkmarx schreibt, ohne verrückt zu werdenCxAudit-Schnittstelle

  3. Die Regeln in Checkmarx sind nach Sprachen unterteilt, d. h. jede Sprache hat ihren eigenen Satz von Abfragen. Es gibt auch einige allgemeine Regeln, die unabhängig von der Sprache gelten, die sogenannten Basisabfragen. In den meisten Fällen geht es bei einfachen Abfragen um die Suche nach Informationen, die von anderen Regeln verwendet werden.

    Wie man Regeln für Checkmarx schreibt, ohne verrückt zu werdenAufteilung der Regeln nach Sprache

  4. Regeln sind „Executable“ und „Non-Executable“ (ausgeführt und nicht ausgeführt). Meiner Meinung nach nicht ganz der richtige Name, aber so ist es. Die Quintessenz ist, dass das Ergebnis der Ausführung von „ausführbaren“ Regeln in den Scan-Ergebnissen in der Benutzeroberfläche angezeigt wird und „nicht ausführbare“ Regeln nur benötigt werden, um ihre Ergebnisse in anderen Anforderungen zu verwenden (im Wesentlichen nur eine Funktion).

    Wie man Regeln für Checkmarx schreibt, ohne verrückt zu werdenBestimmen des Regeltyps beim Erstellen

  5. Sie können neue Regeln erstellen oder bestehende ergänzen/umschreiben. Um eine Regel neu zu schreiben, müssen Sie sie in der Baumstruktur suchen, mit der rechten Maustaste klicken und im Dropdown-Menü „Überschreiben“ auswählen. Hierbei ist zu beachten, dass die neuen Regeln zunächst nicht in den Voreinstellungen enthalten und nicht aktiv sind. Um sie verwenden zu können, müssen Sie sie im Menü „Preset Manager“ des Instruments aktivieren. Umgeschriebene Regeln behalten ihre Einstellungen bei, d. h. wenn die Regel aktiv war, bleibt sie dies und wird sofort angewendet.

    Wie man Regeln für Checkmarx schreibt, ohne verrückt zu werdenBeispiel einer neuen Regel in der Preset Manager-Oberfläche

  6. Während der Ausführung wird ein „Baum“ von Anfragen erstellt, der davon abhängt, was. Die Regeln, die Informationen sammeln, werden zuerst ausgeführt, und diejenigen, die sie verwenden, zweitens. Das Ausführungsergebnis wird zwischengespeichert. Wenn es also möglich ist, die Ergebnisse einer vorhandenen Regel zu verwenden, ist es besser, dies zu tun, da dies die Scanzeit verkürzt.

  7. Regeln können auf verschiedenen Ebenen angewendet werden:

  • Für das gesamte System – wird zum Scannen eines beliebigen Projekts verwendet

  • Auf Teamebene (Team) – wird nur zum Scannen von Projekten im ausgewählten Team verwendet.

  • Auf Projektebene – Wird in einem bestimmten Projekt angewendet

    Wie man Regeln für Checkmarx schreibt, ohne verrückt zu werdenBestimmen der Ebene, auf der die Regel angewendet wird

„Wörterbuch“ für Anfänger

Und ich werde mit ein paar Dingen beginnen, die bei mir Fragen aufgeworfen haben, und ich werde auch eine Reihe von Techniken zeigen, die das Leben erheblich vereinfachen werden.

Operationen mit Listen

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

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

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

Alle gefundenen Gegenstände

Innerhalb der gescannten Sprache können Sie eine Liste absolut aller Elemente erhalten, die Checkmarx identifiziert hat (Strings, Funktionen, Klassen, Methoden usw.). Dies ist ein Raum mit Objekten, über den zugegriffen werden kann All. Das heißt, nach einem Objekt mit einem bestimmten Namen zu suchen searchMekönnen Sie beispielsweise nach Namen über alle gefundenen Objekte hinweg suchen:

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

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

Wenn Sie jedoch in einer anderen Sprache suchen müssen, die aus irgendeinem Grund nicht im Scan enthalten war (z. B. groovy in einem Android-Projekt), können Sie unseren Objektraum durch eine Variable erweitern:

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

Funktionen zur Strömungsanalyse

Diese Funktionen werden in vielen Regeln verwendet und hier ist ein kleiner Spickzettel, was sie bedeuten:

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

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

Dateiname/Pfad abrufen

Es gibt mehrere Attribute, die aus den Ergebnissen einer Abfrage abgerufen werden können (Name der Datei, in der der Eintrag gefunden wurde, Zeichenfolge usw.), in der Dokumentation wird jedoch nicht angegeben, wie diese abgerufen und verwendet werden. Um dies zu tun, müssen Sie auf die LinePragma-Eigenschaft zugreifen und die von uns benötigten Objekte werden sich darin befinden:

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

Es lohnt sich, das im Hinterkopf zu behalten FileName enthält tatsächlich den Pfad zur Datei, da wir die Methode verwendet haben GetFirstGraph.

Ausführungsergebnis

In CxQL gibt es eine spezielle Variable result, das das Ergebnis der Ausführung Ihrer geschriebenen Regel zurückgibt. Es wird sofort initialisiert und Sie können Zwischenergebnisse hineinschreiben und diese während der Arbeit ändern und verfeinern. Wenn jedoch innerhalb der Regel keine Zuweisung zu dieser Variablen oder Funktion vorhanden ist return— Das Ausführungsergebnis wird immer Null sein.

Die folgende Abfrage gibt uns bei der Ausführung nichts zurück und ist immer leer:

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

Aber nachdem wir das Ausführungsergebnis der magischen Variablen result zugewiesen haben, werden wir sehen, was dieser Aufruf für uns zurückgibt:

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

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

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

Verwendung der Ergebnisse anderer Regeln

Regeln in Checkmarx können analog zu Funktionen in einer regulären Programmiersprache aufgerufen werden. Beim Schreiben einer Regel können Sie durchaus die Ergebnisse anderer Abfragen verwenden. Es ist beispielsweise nicht erforderlich, jedes Mal nach allen Methodenaufrufen im Code zu suchen, sondern rufen Sie einfach die gewünschte Regel auf:

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

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

Mit diesem Ansatz können Sie den Code kürzen und die Regelausführungszeit erheblich verkürzen.

Fehlerbehebung

Protokollierung

Bei der Arbeit mit dem Tool ist es manchmal nicht möglich, die gewünschte Abfrage sofort zu schreiben und Sie müssen experimentieren und verschiedene Optionen ausprobieren. Für einen solchen Fall stellt das Tool eine Protokollierung zur Verfügung, die wie folgt aufgerufen wird:

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

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

Es ist jedoch zu beachten, dass diese Methode nur Eingaben akzeptiert SchnurDaher ist es nicht möglich, als Ergebnis des ersten Vorgangs eine vollständige Liste der gefundenen Elemente anzuzeigen. Die zweite Option, die zum Debuggen verwendet wird, besteht darin, von Zeit zu Zeit eine magische Variable zuzuweisen result Sehen Sie sich das Ergebnis der Abfrage an und sehen Sie, was passiert. Dieser Ansatz ist nicht sehr praktisch; Sie müssen sicherstellen, dass es im folgenden Code keine Überschreibungen oder Vorgänge dazu gibt result oder kommentieren Sie einfach den Code unten. Oder Sie vergessen, wie ich, mehrere solcher Aufrufe aus einer vorgefertigten Regel zu entfernen und fragen sich, warum nichts funktioniert.

Eine bequemere Möglichkeit besteht darin, die Methode aufzurufen return mit dem erforderlichen Parameter. In diesem Fall wird die Ausführung der Regel beendet und wir können sehen, was aufgrund dessen, was wir geschrieben haben, passiert ist:

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

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

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

Login-Problem

Es gibt Situationen, in denen Sie nicht auf das CxAudit-Tool (das zum Schreiben von Regeln verwendet wird) zugreifen können. Dafür kann es viele Gründe geben, darunter Abstürze, plötzliche Windows-Updates, BSOD und andere unvorhergesehene Situationen, die außerhalb unserer Kontrolle liegen. In diesem Fall gibt es manchmal eine nicht abgeschlossene Sitzung in der Datenbank, die Sie daran hindert, sich erneut anzumelden. Um das Problem zu beheben, müssen Sie mehrere Abfragen ausführen:

Für Checkmarx vor 8.6:

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

Für Checkmarx nach 8.6:

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

Schreibregeln

Jetzt kommen wir zum interessantesten Teil. Wenn Sie anfangen, Regeln in CxQL zu schreiben, fehlt Ihnen oft nicht so viel Dokumentation, sondern vielmehr einige lebendige Beispiele für die Lösung bestimmter Probleme und die Beschreibung des Prozesses, wie Abfragen im Allgemeinen funktionieren.

Ich werde versuchen, denjenigen, die anfangen, in die Abfragesprache einzutauchen, das Leben ein wenig zu erleichtern und einige Beispiele für die Verwendung benutzerdefinierter Abfragen zur Lösung bestimmter Probleme zu geben. Einige davon sind recht allgemein gehalten und können praktisch ohne Änderungen in Ihrem Unternehmen verwendet werden, andere sind spezifischer, können aber auch durch Änderung des Codes an die Besonderheiten Ihrer Anwendungen angepasst werden.

Hier sind also die Probleme, auf die wir am häufigsten gestoßen sind:

Problem: Es gibt mehrere Flows in den Ergebnissen der Regelausführung und einer davon ist eine Verschachtelung eines anderen. Sie müssen einen davon verlassen.

Lösung: Tatsächlich zeigt Checkmarx manchmal mehrere Datenflüsse an, die sich überschneiden und eine verkürzte Version anderer sein können. Für solche Fälle gibt es eine spezielle Methode ReduceFlow. Abhängig vom Parameter wird der kürzeste oder längste Fluss ausgewählt:

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

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

Problem: Erweitern Sie die Liste der sensiblen Daten, auf die das Tool reagiert

Lösung: Checkmarx verfügt über Grundregeln, deren Ergebnisse von vielen anderen Abfragen verwendet werden. Indem Sie einige dieser Regeln durch anwendungsspezifische Daten ergänzen, können Sie Ihre Scanergebnisse sofort verbessern. Nachfolgend finden Sie eine Beispielregel, um Ihnen den Einstieg zu erleichtern:

General_privacy_violation_list

Fügen wir mehrere Variablen hinzu, die in unserer Anwendung zum Speichern vertraulicher Informationen verwendet werden:

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

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

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

Problem: Erweitern Sie die Liste der Variablen mit Passwörtern

Lösung: Ich würde empfehlen, sofort auf die Grundregel für die Definition von Passwörtern im Code zu achten und eine Liste mit Variablennamen hinzuzufügen, die in Ihrem Unternehmen häufig verwendet werden.

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

Problem: Fügen Sie verwendete Frameworks hinzu, die von Checkmarx nicht unterstützt werden

Lösung: Alle Abfragen in Checkmarx sind nach Sprachen unterteilt, daher müssen Sie für jede Sprache Regeln hinzufügen. Nachfolgend finden Sie einige Beispiele für solche Regeln.

Werden Bibliotheken eingesetzt, die die Standardfunktionalität ergänzen oder ersetzen, können diese problemlos zur Grundregel hinzugefügt werden. Dann erfährt jeder, der es nutzt, sofort von den Neueinführungen. Bibliotheken für die Protokollierung in Android sind beispielsweise Timber und Loggi. Im Basispaket gibt es keine Regeln zur Identifizierung von Nicht-Systemaufrufen. Wenn also ein Passwort oder eine Sitzungskennung in das Protokoll gelangt, erfahren wir nichts davon. Versuchen wir, Definitionen solcher Methoden zu den Checkmarx-Regeln hinzuzufügen.

Testcodebeispiel, das die Timber-Bibliothek für die Protokollierung verwendet:

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

Und hier ist ein Beispiel für eine Anfrage für Checkmarx, mit der Sie eine Definition für den Aufruf von Timber-Methoden als Ausgangspunkt für Daten aus der Anwendung hinzufügen können:

Finden Sie AndroidOutputs

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

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

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

Und Sie können auch die Nachbarregel ergänzen, diese bezieht sich jedoch direkt auf die Anmeldung bei Android:

Finden Sie AndroidLog_Outputs

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

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

Auch, wenn Android-Anwendungen genutzt werden WorkManager Für asynchrones Arbeiten bietet es sich an, Checkmarx zusätzlich darüber zu informieren, indem eine Methode zum Abrufen von Daten aus der Aufgabe hinzugefügt wird getInputData:

Finden Sie AndroidRead

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

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

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

Problem: Suche nach sensiblen Daten in plist für iOS-Projekte

Lösung: iOS verwendet häufig spezielle Dateien mit der Erweiterung .plist, um verschiedene Variablen und Werte zu speichern. Das Speichern von Passwörtern, Tokens, Schlüsseln und anderen sensiblen Daten in diesen Dateien wird nicht empfohlen, da diese problemlos aus dem Gerät extrahiert werden können.

Plist-Dateien verfügen über Funktionen, die mit bloßem Auge nicht erkennbar sind, für Checkmarx jedoch wichtig sind. Schreiben wir eine Regel, die nach den von uns benötigten Daten sucht und uns mitteilt, ob irgendwo Passwörter oder Token erwähnt werden.

Ein Beispiel für eine solche Datei, die ein Token für die Kommunikation mit dem Backend-Dienst enthält:

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

Und eine Regel für Checkmarx, die mehrere Nuancen aufweist, die beim Schreiben berücksichtigt werden sollten:

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

Problem: Informationen in XML finden

Lösung: Checkmarx verfügt über sehr komfortable Funktionen zum Arbeiten mit XML und zur Suche nach Werten, Tags, Attributen und mehr. Doch leider gab es einen Fehler in der Dokumentation, aufgrund dessen kein einziges Beispiel funktionierte. Obwohl dieser Fehler in der neuesten Version der Dokumentation behoben wurde, sollten Sie vorsichtig sein, wenn Sie frühere Versionen von Dokumenten verwenden.

Hier ist ein falsches Beispiel aus der Dokumentation:

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

Als Ergebnis des Ausführungsversuchs erhalten wir eine Fehlermeldung All es gibt keine solche Methode... Und das stimmt, denn es gibt einen speziellen, separaten Objektraum für die Verwendung von Funktionen für die Arbeit mit XML - cxXPath. So sieht die richtige Abfrage aus, um eine Einstellung in Android zu finden, die die Verwendung von HTTP-Verkehr ermöglicht:

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

Schauen wir es uns etwas genauer an, da die Syntax für alle Funktionen ähnlich ist. Nachdem Sie eine herausgefunden haben, müssen Sie nur noch die gewünschte auswählen. Also der Reihe nach nach den Parametern:

  • "*.xml"— Maske der zu durchsuchenden Dateien

  • 8 — ID der Sprache, für die die Regel angewendet wird

  • "cleartextTrafficPermitted"– Attributname in XML

  • "true" – der Wert dieses Attributs

  • false — Verwendung regulärer Ausdrücke bei der Suche

  • true – bedeutet, dass die Suche ohne Beachtung der Groß-/Kleinschreibung durchgeführt wird

Als Beispiel haben wir eine Regel verwendet, die aus Sicherheitsgründen falsche Netzwerkverbindungseinstellungen in Android identifiziert, die die Kommunikation mit dem Server über das HTTP-Protokoll ermöglichen. Beispiel für eine Einstellung, die ein Attribut enthält cleartextTrafficPermitted mit Bedeutung 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>

Problem: Begrenzen Sie die Ergebnisse nach Dateiname/Pfad

Lösung: In einem der großen Projekte im Zusammenhang mit der Entwicklung einer mobilen Anwendung für Android sind wir auf Fehlalarme der Regel gestoßen, die die Verschleierungseinstellung bestimmt. Tatsache ist, dass die Standardregel in der Datei sucht build.gradle Eine Einstellung, die für die Anwendung von Verschleierungsregeln für die Release-Version der Anwendung verantwortlich ist.

Aber in großen Projekten gibt es manchmal untergeordnete Dateien build.gradle, die auf die im Projekt enthaltenen Bibliotheken verweisen. Die Besonderheit besteht darin, dass bei der Kompilierung die Einstellungen der übergeordneten Assemblydatei angewendet werden, auch wenn diese Dateien keinen Bedarf für eine Verschleierung anzeigen.

Die Aufgabe besteht also darin, Trigger in untergeordneten Dateien abzuschneiden, die zu Bibliotheken gehören. Sie sind am Vorhandensein der Linie zu erkennen apply 'com.android.library'.

Beispielcode aus Datei build.gradle, was die Notwendigkeit der Verschleierung bestimmt:

apply plugin: 'com.android.application'

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

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

dependencies {
  ...
}

Dateibeispiel build.gradle für eine im Projekt enthaltene Bibliothek, die diese Einstellung nicht hat:

apply plugin: 'android-library'

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

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

Und die Regel 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);
		}
	}
}

Dieser Ansatz kann nicht nur für Android-Anwendungen recht universell und nützlich sein, sondern auch für andere Fälle, in denen Sie feststellen müssen, ob ein Ergebnis zu einer bestimmten Datei gehört.

Problem: Fügen Sie Unterstützung für eine Bibliothek eines Drittanbieters hinzu, wenn die Syntax nicht vollständig unterstützt wird

Lösung: Die Anzahl der verschiedenen Frameworks, die beim Schreiben von Code verwendet werden, ist einfach unübertroffen. Natürlich weiß Checkmarx nicht immer von ihrer Existenz, und unsere Aufgabe ist es, ihm beizubringen, zu verstehen, dass bestimmte Methoden speziell zu diesem Rahmen gehören. Manchmal wird dies dadurch erschwert, dass Frameworks sehr häufige Funktionsnamen verwenden und es unmöglich ist, die Beziehung eines bestimmten Aufrufs zu einer bestimmten Bibliothek eindeutig zu bestimmen.

Die Schwierigkeit besteht darin, dass die Syntax solcher Bibliotheken nicht immer korrekt erkannt wird und man experimentieren muss, um eine große Anzahl falsch positiver Ergebnisse zu vermeiden. Es gibt mehrere Möglichkeiten, die Scangenauigkeit zu verbessern und das Problem zu lösen:

  • Bei der ersten Option wissen wir sicher, dass die Bibliothek in einem bestimmten Projekt verwendet wird und können die Regel auf Teamebene anwenden. Wenn sich das Team jedoch für einen anderen Ansatz entscheidet oder mehrere Bibliotheken verwendet, in denen sich Funktionsnamen überschneiden, können wir ein nicht sehr erfreuliches Bild zahlreicher falsch positiver Ergebnisse erhalten

  • Die zweite Möglichkeit besteht darin, nach Dateien zu suchen, in denen die Bibliothek eindeutig importiert ist. Mit diesem Ansatz können wir sicher sein, dass die von uns benötigte Bibliothek genau in dieser Datei verwendet wird.

  • Und die dritte Möglichkeit besteht darin, die beiden oben genannten Ansätze zusammen zu verwenden.

Schauen wir uns als Beispiel eine in engen Kreisen bekannte Bibliothek an glatt für die Programmiersprache Scala, nämlich die Funktionalität Literale Werte verbinden. Um Parameter an eine SQL-Abfrage zu übergeben, müssen Sie im Allgemeinen den Operator verwenden $, das Daten in eine vorgefertigte SQL-Abfrage ersetzt. Das heißt, es ist tatsächlich ein direktes Analogon zur Prepared Statement in Java. Wenn Sie jedoch eine SQL-Abfrage dynamisch erstellen müssen, beispielsweise wenn Sie Tabellennamen übergeben müssen, können Sie den Operator verwenden #$, wodurch die Daten direkt in die Abfrage eingefügt werden (fast wie eine Zeichenfolgenverkettung).

ример кода:

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

Checkmarx weiß noch nicht, wie man die Verwendung von Splicing Literal Values ​​erkennt und überspringt Operatoren #$Versuchen wir also, ihm beizubringen, potenzielle SQL-Injections zu identifizieren und die richtigen Stellen im Code hervorzuheben:

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

Problem: Suchen Sie nach verwendeten anfälligen Funktionen in Open-Source-Bibliotheken

Lösung: Viele Unternehmen nutzen Open-Source-Überwachungstools (OSA-Praxis), um die Verwendung anfälliger Versionen von Bibliotheken in entwickelten Anwendungen zu erkennen. Manchmal ist es nicht möglich, eine solche Bibliothek auf eine sichere Version zu aktualisieren. Teilweise gibt es funktionale Einschränkungen, in anderen Fällen gibt es überhaupt keine sichere Version. In diesem Fall hilft eine Kombination aus SAST- und OSA-Praktiken dabei, festzustellen, dass die Funktionen, die zur Ausnutzung der Schwachstelle führen, im Code nicht verwendet werden.

Aber manchmal, insbesondere wenn man JavaScript in Betracht zieht, ist dies möglicherweise keine völlig triviale Aufgabe. Nachfolgend finden Sie eine vielleicht nicht ideale, aber dennoch funktionierende Lösung am Beispiel von Schwachstellen in der Komponente lodash in Methoden template и *set.

Beispiele für den Test potenziell anfälliger Codes in einer JS-Datei:

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

Und bei direkter Verbindung 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>

Wir suchen nach allen unseren anfälligen Methoden, die unter Schwachstellen aufgeführt sind:

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

Problem: Suche nach in der Anwendung eingebetteten Zertifikaten

Lösung: Es ist nicht ungewöhnlich, dass Anwendungen, insbesondere mobile, Zertifikate oder Schlüssel verwenden, um auf verschiedene Server zuzugreifen oder SSL-Pinning zu überprüfen. Aus Sicherheitsgründen ist das Speichern solcher Dinge im Code nicht die beste Vorgehensweise. Versuchen wir, eine Regel zu schreiben, die nach ähnlichen Dateien im Repository sucht:

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

Problem: Kompromittierte Token in der Anwendung finden

Lösung: Oft ist es notwendig, kompromittierte Token oder andere wichtige Informationen, die im Code vorhanden sind, zu widerrufen. Natürlich ist es keine gute Idee, sie im Quellcode zu speichern, aber die Situationen sind unterschiedlich. Dank CxQL-Abfragen ist es ganz einfach, Dinge wie diese zu finden:

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

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

Abschluss

Ich hoffe, dass dieser Artikel für diejenigen nützlich sein wird, die gerade erst mit dem Checkmarx-Tool vertraut werden. Vielleicht finden auch diejenigen, die schon lange ihre eigenen Regeln schreiben, in diesem Ratgeber etwas Nützliches.

Leider mangelt es derzeit an einer Ressource, aus der bei der Entwicklung von Regeln für Checkmarx neue Ideen gewonnen werden könnten. Deshalb haben wir geschaffen Repository auf Github, wo wir unsere Arbeit veröffentlichen werden, damit jeder, der CxQL verwendet, darin etwas Nützliches finden kann und auch die Möglichkeit hat, seine Arbeit mit der Community zu teilen. Das Repository wird derzeit mit Inhalten gefüllt und strukturiert, daher sind Mitwirkende willkommen!

Danke!

Source: habr.com

Kommentar hinzufügen