Wie man Regeln für Checkmarx schreibt, ohne den Verstand zu verlieren

Hallo, Habr!

In unserer Arbeit hat unser Unternehmen häufig mit verschiedenen Tools zur statischen Codeanalyse (SAST) zu tun. Die Standardfunktionen sind oft nur durchschnittlich. Natürlich hängt alles vom Projekt und den eingesetzten Technologien ab und davon, wie gut diese Technologien von den Analyse-Regeln abgedeckt sind. Meiner Meinung nach ist eines der wichtigsten Kriterien bei der Auswahl eines SAST-Tools die Möglichkeit, es an die spezifischen Anforderungen der eigenen Anwendungen anzupassen, insbesondere das Schreiben und Anpassen von Analyse-Regeln, die oft als Custom Queries bezeichnet werden.

Wie man Regeln für Checkmarx schreibt, ohne den Verstand zu verlieren

Wir verwenden am häufigsten Checkmarx – einen sehr interessanten und leistungsstarken Code-Analyzer. In diesem Artikel teile ich meine Erfahrungen beim Schreiben von Analyse-Regeln dafür.

Inhaltsverzeichnis

Einleitung

Zunächst möchte ich einen der wenigen Artikel in deutscher Sprache empfehlen, der sich mit den Besonderheiten des Schreibens von Anfragen für Checkmarx beschäftigt. Dieser wurde Ende 2019 auf der Plattform veröffentlicht, und zwar unter dem Titel: „Hallo, Checkmarx!“. So schreiben Sie eine Anfrage für Checkmarx SAST und finden beeindruckende Schwachstellen..

In diesem Artikel wird ausführlich erklärt, wie Sie die ersten Anfragen in CxQL (Checkmarx Query Language) für eine Beispielanwendung schreiben können, und es werden die grundlegenden Prinzipien der Regelanalyse dargestellt.

Ich werde nicht wiederholen, was dort beschrieben ist, obwohl einige Überschneidungen vorhanden sein werden. In meinem Artikel werde ich versuchen, eine Art "Rezeptesammlung" zusammenzustellen, die eine Liste von Lösungsansätzen für spezifische Herausforderungen enthält, mit denen ich in meiner Arbeit mit Checkmarx konfrontiert war. Bei vielen dieser Herausforderungen habe ich lange nach Lösungen gesucht. Manchmal fehlten die Informationen in der Dokumentation, und manchmal war es einfach schwierig zu verstehen, wie man das Gewünschte umsetzen kann. Ich hoffe, dass meine Erfahrungen und schlaflosen Nächte nicht umsonst waren und dass diese "Rezeptsammlung für Custom Queries" Ihnen einige Stunden oder ein paar Nervenzellen erspart. Also, lassen Sie uns beginnen!

Allgemeine Informationen zu Regeln

Zunächst betrachten wir einige grundlegende Begriffe und den Prozess der Arbeit mit Regeln, um besser zu verstehen, was als Nächstes passieren wird. Außerdem, weil dies in der Dokumentation nicht gut behandelt wird oder stark über die Struktur verteilt ist, was nicht sehr praktisch ist.

  1. Die Regeln werden beim Scannen abhängig von dem beim Start ausgewählten Preset (einer Sammlung aktiver Regeln) angewendet. Es können unbegrenzt viele Presets erstellt werden, und wie genau diese strukturiert sind, hängt von den Besonderheiten Ihres Prozesses ab. Sie können sie nach Sprachen gruppieren oder Presets für jedes Projekt erstellen. Die Anzahl der aktiven Regeln beeinflusst sowohl die Geschwindigkeit als auch die Genauigkeit des Scannens.

    Wie man Regeln für Checkmarx schreibt, ohne den Verstand zu verlierenPreset-Einstellungen im Checkmarx-Interface

  2. Die Regeln werden mit einem speziellen Tool namens CxAuditor bearbeitet. Dies ist eine Desktopanwendung, die sich mit dem Server von Checkmarx verbindet. Dieses Tool hat zwei Betriebsmodi: Regelbearbeitung und Analyse der Ergebnisse bereits durchgeführter Scans.

    Wie man Regeln für Checkmarx schreibt, ohne den Verstand zu verlierenCxAudit-Interface

  3. Die Regeln in Checkmarx sind nach Sprachen untergliedert, das heißt, für jede Sprache gibt es ein eigenes Set an Abfragen. Zudem gibt es einige allgemeine Regeln, die unabhängig von der Sprache gelten, die sogenannten Basisabfragen. Im Wesentlichen beinhalten die Basisabfragen die Suche nach Informationen, die von anderen Regeln verwendet werden.

    Wie man Regeln für Checkmarx schreibt, ohne den Verstand zu verlierenRegeln nach Sprachen untergliedern

  4. Regeln sind entweder „Executable“ oder „Non-Executable“ (ausführbar und nicht ausführbar). Diese Bezeichnung ist nicht ganz korrekt, meiner Meinung nach, aber das ist nun mal so. Der Kernpunkt ist, dass die Ergebnisse der Ausführung von „Executable“-Regeln im UI der Scanning-Ergebnisse angezeigt werden, während „Non-Executable“-Regeln lediglich benötigt werden, um ihre Ergebnisse in anderen Abfragen zu verwenden (im Grunde – nur eine Funktion).

    Wie man Regeln für Checkmarx schreibt, ohne den Verstand zu verlierenBestimmung des Regeltyps bei der Erstellung

  5. Neue Regeln können erstellt oder bestehende Regeln ergänzt/überschrieben werden. Um eine Regel zu überschreiben, müssen Sie sie im Baum finden, mit der rechten Maustaste klicken und im Dropdown-Menü die Option "Override" wählen. Dabei ist es wichtig, zu beachten, dass neue Regeln standardmäßig nicht in den Presets enthalten und nicht aktiv sind. Um sie nutzen zu können, müssen sie im Menü "Preset Manager" im Tool aktiviert werden. Die überschriebenen Regeln behalten ihre Einstellungen, d.h. wenn eine Regel aktiv war, bleibt sie aktiv und wird sofort angewendet.

    Wie man Regeln für Checkmarx schreibt, ohne den Verstand zu verlierenBeispiel einer neuen Regel im Interface des Preset Managers

  6. Während der Ausführung wird ein "Baum" von Anfragen erstellt, wobei festgelegt wird, was von was abhängt. Zuerst werden die Regeln ausgeführt, die Informationen sammeln, gefolgt von denen, die diese nutzen. Die Ergebnisse der Ausführung werden zwischengespeichert, sodass, wenn möglich, die Ergebnisse einer bestehenden Regel verwendet werden können. Dies verkürzt die Scanzeit.

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

  • Für das gesamte System — es wird für jedes Scanning jedes Projekts verwendet.

  • Auf Teamebene (Team) – wird nur für das Scannen von Projekten im gewählten Team angewendet.

  • Auf Projektebene – wird in einem bestimmten Projekt angewendet.

    Wie man Regeln für Checkmarx schreibt, ohne den Verstand zu verlierenBestimmung des Niveaus, auf dem die Regel angewendet wird.

Ein "Wörterbuch" für Einsteiger

Ich werde mit einigen Punkten beginnen, die mir Fragen aufgeworfen haben, und auch eine Reihe von Methoden zeigen, die das Leben erheblich erleichtern.

Operationen mit Listen

- Subtraktion (list2 - list1)
* Schnittmengen von Listen (list1 * list2)
+ Addition von Listen (list1 + list2)

& (logisches UND) - vereint Listen bei Übereinstimmungen (list1 & list2), ähnlich wie bei Schnittmengen (list1 * list2)
| (logisches ODER) - vereint Listen bei breiter Suche (list1 | list2)

Funktioniert nicht mit Listen:  ^  &&  ||  %  \/ 

Alle gefundenen Elemente

Im Rahmen der gescannten Sprache kann eine Liste aller Elemente abgerufen werden, die Checkmarx definiert hat (Strings, Funktionen, Klassen, Methoden usw.). Dies ist ein gewisser Raum von Objekten, auf den man zugreifen kann über Alle. Das heißt, um ein Objekt mit einem bestimmten Namen zu finden, searchMe, kann man beispielsweise nach dem Namen in allen gefundenen Objekten suchen:

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

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

Wenn Sie jedoch in einer anderen Sprache suchen müssen, die aus bestimmten Gründen nicht im Scannen enthalten war (zum Beispiel groovy im Projekt für Android), können Sie unseren Objektbereich über eine Variable erweitern:

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

Funktionen zur Flow-Analyse

Diese Funktionen kommen in vielen Regeln vor, und hier ist eine kleine Übersicht, was sie bedeuten:

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

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

Abrufen des Namens/Pfads der Datei

Es gibt mehrere Attribute, die aus den Ergebnissen der Abfrage abgerufen werden können (den Dateinamen, in dem der Treffer gefunden wurde, die Zeile usw.), aber es wird nicht erklärt, wie man sie abruft und verwendet. Um dies zu tun, müssen Sie auf die Eigenschaft LinePragma zugreifen, in der sich die benötigten Objekte 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 ist zu beachten, dass FileName tatsächlich den Pfad zur Datei enthält, da wir die Methode GetFirstGraph.

Ausführungsergebnis

verwendet haben. Innerhalb von CxQL gibt es eine spezielle Variable result, die das Ergebnis der Ausführung Ihrer geschriebenen Regel zurückgibt. Sie wird sofort initialisiert und Sie können Zwischenresultate dort speichern, indem Sie diese im Verlauf der Arbeit ändern und präzisieren. Wenn es jedoch innerhalb der Regel keine Zuweisung für diese Variable oder Funktion gibt, return— Das Ergebnis der Ausführung wird immer null sein.

Die folgende Abfrage wird uns nichts als Ergebnis zurückgeben und wird immer leer sein:

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

Aber wenn wir das Ergebnis der Ausführung der magischen Variable result zuweisen, sehen wir, was dieser Aufruf zurückgibt:

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

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

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

Verwendung der Ausführungsergebnisse anderer Regeln

Die Regeln in Checkmarx können als Äquivalent zu Funktionen in einer herkömmlichen Programmiersprache betrachtet werden. Bei der Erstellung einer Regel können Sie problemlos die Ergebnisse anderer Abfragen verwenden. Zum Beispiel ist es nicht notwendig, jedes Mal alle Methodenaufrufe im Code zu suchen; es genügt, die benötigte Regel aufzurufen:

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

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

Ein solcher Ansatz ermöglicht es, den Code zu verkürzen und die Ausführungszeit der Regel erheblich zu reduzieren.

Problemlösung

Protokollierung

Bei der Arbeit mit dem Tool gelingt es manchmal nicht, sofort die benötigte Abfrage zu schreiben, und es ist notwendig, zu experimentieren, indem verschiedene Varianten ausprobiert werden. Für diesen Fall gibt es im Tool ein Logging, das 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 eine Zeichenkette, sodass die vollständige Liste der gefundenen Elemente nach der ersten Operation nicht angezeigt werden kann. Eine zweite Option, die für das Debugging verwendet wird, besteht darin, der magischen Variable von Zeit zu Zeit einen Wert zuzuweisen. result das Ergebnis der Abfrage und zu beobachten, was passiert. Dieser Ansatz ist nicht besonders praktisch; man muss sicherstellen, dass es im Code keine Überschreibungen oder Operationen mit diesem Ergebnis gibt. result oder einfach den nachfolgenden Code kommentieren. Alternativ kann man, wie ich, vergessen, bei der endgültigen Regel einige dieser Aufrufe zu entfernen und sich wundern, warum nichts funktioniert.

Ein bequemerer Weg besteht darin, die Methode return mit dem gewünschten Parameter aufzurufen. In diesem Fall wird die Ausführung der Regel abgeschlossen und wir können sehen, was das Ergebnis unseres Codes ist:

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

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

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

Anmeldeprobleme

Es gibt Situationen, in denen es nicht möglich ist, auf das CxAudit-Tool zuzugreifen (das zur Erstellung von Regeln verwendet wird). Die Gründe dafür können vielfältig sein, wie unerwartete Programmabstürze, plötzliche Windows-Updates, BSOD und andere unvorhergesehene Ereignisse, die außerhalb unseres Einflussbereichs liegen. In solchen Fällen kann manchmal eine unvollständige Sitzung in der Datenbank verbleiben, die einen erneuten Zugriff verhindert. Um dies zu beheben, müssen einige Abfragen durchgeführt werden:

Für Checkmarx bis Version 8.6:

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

Für Checkmarx ab Version 8.6:

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

Regeln schreiben

Jetzt kommen wir zum Interessantesten. Wenn man beginnt, Regeln für CxQL zu schreiben, fehlt oft nicht nur die Dokumentation, sondern auch lebendige Beispiele zur Lösung bestimmter Aufgaben und eine Beschreibung des gesamten Arbeitsprozesses der Abfragen.

Ich werde versuchen, das Leben für diejenigen, die sich in die Abfragesprache einarbeiten, etwas zu erleichtern und einige Beispiele für die Verwendung von benutzerdefinierten Abfragen zur Lösung bestimmter Aufgaben anzuführen. Einige davon sind recht allgemein und können in Ihrem Unternehmen praktisch unverändert angewendet werden, während andere spezifischer sind, aber ebenfalls verwendet werden können, indem der Code an die Spezifikationen Ihrer Anwendungen angepasst wird.

Hier sind die häufigsten Herausforderungen, mit denen wir konfrontiert sind:

Aufgabe: Bei der Ausführung der Regel gibt es mehrere Flows, wobei einer in einen anderen eingebettet ist. Es muss einer von ihnen beibehalten werden.

Die Lösung: Tatsächlich zeigt Checkmarx manchmal mehrere Datenfluss-Elemente, die sich überschneiden und eine verkürzte Version anderer sein können. Für solche Fälle gibt es eine spezielle Methode. ReduceFlow. Je nach Parameter wählt er den kürzesten oder den längsten Flow aus:

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

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

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

Die Lösung: In Checkmarx gibt es grundlegende Regeln, deren Ausführung von vielen anderen Anfragen verwendet wird. Durch das Hinzufügen spezifischer Daten für Ihre Anwendung zu einigen dieser Regeln können die Scanning-Ergebnisse sofort verbessert werden. Hier ein Beispiel für eine Regel, mit der Sie beginnen können:

General_privacy_violation_list

Fügen wir einige Variablen hinzu, die in unserer Anwendung zur Speicherung sensibler Informationen verwendet werden:

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

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

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

Aufgabe: Erweitern Sie die Liste der Variablen mit Passwörtern.

Die Lösung: Ich würde empfehlen, sofort auf die grundlegende Regel zur Festlegung von Passwörtern im Code zu achten und eine Liste der Variablennamen hinzuzufügen, die in Ihrem Unternehmen üblich sind.

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

// Ergänzen der Standardvariablenliste
List  pswdIncludeList = new List{"*password*", "*psw", "psw*", "pwd*", "*pwd", "*authKey*", "pass*", "cipher*", "*cipher", "pass", "adgangskode", "benutzerkennwort", "chiffre", "clave", "codewort", "contrasena", "contrasenya", "geheimcode", "geslo", "heslo", "jelszo", "kennwort", "losenord", "losung", "losungswort", "lozinka", "modpas", "motdepasse", "parol", "parola", "parole", "pasahitza", "pasfhocal", "passe", "passord", "passwort", "pasvorto", "paswoord", "salasana", "schluessel", "schluesselwort", "senha", "sifre", "wachtwoord", "wagwoord", "watchword", "zugangswort", "PAROLACHIAVE", "PAROLA CHIAVE", "PAROLECHIAVI", "PAROLE CHIAVI", "paroladordine", "verschluesselt", "sisma",
                "pincode",
								"pin"};
								
List  pswdExcludeList = new List{"*pass", "*passable*", "*passage*", "*passenger*", "*passer*", "*passing*", "*passion*", "*passive*", "*passover*", "*passport*", "*passed*", "*compass*", "*bypass*", "pass-through", "passthru", "passthrough", "passbytes", "passcount", "passratio"};

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

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

Aufgabe: Fügen Sie unterstützte Frameworks hinzu, die von Checkmarx nicht unterstützt werden

Die Lösung: Alle Anfragen in Checkmarx sind nach Sprachen unterteilt, sodass die Regeln für jede Sprache ergänzt werden müssen. Im Folgenden finden Sie einige Beispiele für solche Regeln.

Wenn Bibliotheken verwendet werden, die die Standardfunktionen erweitern oder ersetzen, können sie leicht in die Grundregel aufgenommen werden. Dann erfahren alle, die sie verwenden, sofort von neuen Eingaben. Ein Beispiel sind Logging-Bibliotheken für Android – Timber und Loggi. In der Basisauslieferung gibt es keine Regeln zur Erkennung nicht-systemischer Aufrufe, sodass wir nicht erfahren, wenn ein Passwort oder eine Sitzungs-ID in das Protokoll gelangt. Lassen Sie uns versuchen, in die Regeln von Checkmarx die Erkennung solcher Methoden hinzuzufügen.

Beispielcode, der die Bibliothek Timber zum Logging 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("Fehlermeldung");
        Timber.d("Debugmeldung");

        Timber.tag("Ein anderer Tag").e("Und Fehlermeldung");
    }
}

Hier ist ein Beispiel einer Abfrage für Checkmarx, die es ermöglicht, die Definition von Methodenaufrufen von Timber als Datenausgabepunkt aus der Anwendung hinzuzufügen:

FindAndroidOutputs

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

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

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

Außerdem kann die benachbarte Regel ergänzt werden, die sich direkt auf das Logging in Android bezieht:

FindAndroidLog_Outputs

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

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

Zusätzlich, wenn in Android-Anwendungen WorkManager für asynchrone Aufgaben verwendet wird, wäre es sinnvoll, Checkmarx darüber zu informieren, indem man die Methode zum Abrufen von Daten aus der Aufgabe hinzufügt: getInputData:

FindAndroidRead

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

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

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

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

Die Lösung: Oft werden in iOS spezielle Dateien mit der Erweiterung .plist verwendet, um verschiedene Variablen und Werte zu speichern. Es wird nicht empfohlen, Passwörter, Tokens, Schlüssel und andere sensible Daten in diesen Dateien zu speichern, da sie relativ einfach aus dem Gerät extrahiert werden können.

Plist-Dateien haben Besonderheiten, die mit bloßem Auge nicht sofort ersichtlich sind, jedoch für Checkmarx wichtig sind. Lassen Sie uns eine Regel schreiben, die nach den benötigten Daten sucht und uns benachrichtigt, wenn irgendwo Passwörter oder Tokens erwähnt werden.

Hier ist ein Beispiel für eine solche Datei, in der ein Token zur Kommunikation mit dem Backend-Service eingebettet ist:

DeviceDictionary
	
		phone
		iPhone 6s
		
	privatekey
	MIICXAIBAAKBgQCqGKukO1De7zhZj6+

Und es gibt eine Regel für Checkmarx, bei der es einige Nuancen zu beachten gilt, wenn Sie schreiben:

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

Aufgabe: Informationen in XML suchen

Die Lösung: Checkmarx bietet sehr nützliche Funktionen für die Arbeit mit XML sowie zur Suche nach Werten, Tags, Attributen und mehr. Leider gab es einen Fehler in der Dokumentation, weshalb kein Beispiel funktioniert. Obwohl dieser Mangel in der neuesten Version der Dokumentation behoben wurde, seien Sie vorsichtig, wenn Sie frühere Versionen der Dokumente verwenden.

Hier ist ein falsches Beispiel aus der Dokumentation:

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

Infolgedessen werden wir beim Versuch, die Ausführung durchzuführen, einen Fehler erhalten, dass es Alle keine solche Methode gibt... Und das ist richtig, da es für die Nutzung der Funktionen zur Arbeit mit XML einen speziellen, getrennten Objektbereich gibt - cxXPath. So sieht die richtige Anfrage zur Suche nach einer Einstellung in Android aus, die die Nutzung von HTTP-Traffic erlaubt:

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

Lassen Sie uns etwas detaillierter darauf eingehen, da die Syntax aller Funktionen ähnlich ist. Sobald man eine Funktion verstanden hat, müssen Sie nur die passende auswählen. Lassen Sie uns also die Parameter der Reihe nach durchgehen:

  • "*.xml"— die Dateimuster, nach denen gesucht werden soll

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

  • "cleartextTrafficPermitted"— der Attributname in XML

  • "true" — der Wert dieses Attributs

  • false — die Verwendung von regulären Ausdrücken bei der Suche

  • true — bedeutet, dass die Suche ohne Berücksichtigung der Groß- und Kleinschreibung erfolgt, also fallunabhängig

Zum Beispiel wird hier eine Regel verwendet, die unsichere Einstellungen für Netzwerkverbindungen in Android definiert, die die Kommunikation mit dem Server über das HTTP-Protokoll zulassen. Ein Beispiel für eine Einstellung, die das Attribut cleartextTrafficPermitted mit dem Wert true:

example.com
        
            
        
        
            secure.example.com

Aufgabe: Ergebnisse nach Dateiname/Pfad einschränken

Die Lösung: In einem der größeren Projekte, das sich mit der Entwicklung einer mobilen Anwendung für Android befasst, sind wir auf falsche Auslösungen der Regel gestoßen, die die Obfuskationskonfiguration definiert. Das Problem dabei ist, dass die Regel standardmäßig in der Datei build.gradle nach einer Konfiguration sucht, die für die Anwendung von Obfuskationsregeln für die Release-Version der Anwendung verantwortlich ist.

In großen Projekten kommen jedoch manchmal untergeordnete Dateien vor, build.gradle, die zu in das Projekt eingebundenen Bibliotheken gehören. Das Besondere ist, dass, selbst wenn in diesen Dateien keine Notwendigkeit zur Obfuskation vorgeschrieben ist, bei der Kompilierung die Einstellungen der übergeordneten Build-Datei angewendet werden.

Somit besteht die Herausforderung darin, Auslösungen in den untergeordneten Dateien, die zu Bibliotheken gehören, auszuschließen. Diese können an der Zeile erkannt werden, apply 'com.android.library'.

Beispielcode aus der Datei build.gradle, der die Notwendigkeit der Obfuskation definiert:

apply plugin: 'com.android.application'

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

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

dependencies {
  ...
}

Beispiel einer Datei build.gradle für eine im Projekt eingebundene Bibliothek, die keine solche Einstellung 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 recht flexibel sein und eignet sich nicht nur für Android-Anwendungen, sondern auch für andere Fälle, in denen es notwendig ist, die Zuordnung des Ergebnisses zu einer bestimmten Datei zu bestimmen.

Aufgabe: Fügen Sie die Unterstützung für externe Bibliotheken hinzu, wenn die Syntax nicht vollständig unterstützt wird.

Die Lösung: Die Anzahl der verschiedenen Frameworks, die beim Codieren verwendet werden, ist einfach enorm. Natürlich kennt Checkmarx nicht immer deren Existenz, und es ist unsere Aufgabe, es zu schulen, damit bestimmte Methoden genau diesem Framework zugeordnet werden. Manchmal wird dies dadurch kompliziert, dass Frameworks Funktionsnamen verwenden, die sehr verbreitet sind, und man nicht eindeutig bestimmen kann, ob ein bestimmter Aufruf zu einer bestimmten Bibliothek gehört.

Die Herausforderung besteht darin, dass die Syntax solcher Bibliotheken nicht immer korrekt erkannt wird, und daher muss man experimentieren, um keine hohen Raten an Fehlalarmen zu erhalten. Es gibt mehrere Ansätze, um die Scangenauigkeit zu verbessern und die gestellte Aufgabe zu lösen:

  • Die erste Option besteht darin, dass wir genau wissen, dass die Bibliothek in einem bestimmten Projekt verwendet wird und wir eine Regel auf Teamebene anwenden können. Doch falls das Team sich entscheidet, einen anderen Ansatz zu wählen oder mehrere Bibliotheken mit sich überschneidenden Funktionsnamen verwendet, könnten wir ein unerfreuliches Bild mit zahlreichen Fehlalarmen erhalten.

  • Die zweite Option besteht darin, eine Dateisuche durchzuführen, in der die Bibliothek eindeutig importiert wird. Mit diesem Ansatz können wir sicher sein, dass in dieser Datei die benötigte Bibliothek tatsächlich verwendet wird.

  • Die dritte Option ist die gleichzeitige Anwendung der beiden oben genannten Ansätze.

Als Beispiel betrachten wir eine in Fachkreisen bekannte Bibliothek slick für die Programmiersprache Scala, insbesondere die Funktionalität Splicing Literal Values. Im Allgemeinen müssen zur Übergabe von Parametern in einer SQL-Abfrage Operatoren verwendet werden, $, die Daten in die zuvor formatierte SQL-Abfrage einfügen. Das ist im Grunde genommen das direkte Pendant zu Prepared Statements in Java. Wenn jedoch eine dynamische Konstruktion der SQL-Abfrage erforderlich ist, zum Beispiel wenn Tabellennamen übergeben werden müssen, kann man den Operator verwenden, #$, der Daten direkt in die Abfrage einfügt (praktisch wie die Verkettung von Zeichenfolgen).

Beispielcode:

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

Checkmarx kann bisher die Verwendung von Splicing Literal Values nicht erkennen und überspringt die Operatoren #$, also versuchen wir, es zu trainieren, um potenzielle SQL-Injektionen zu identifizieren und die relevanten 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));
}

Aufgabe: Suche nach verwendeten verwundbaren Funktionen in Open-Source-Bibliotheken

Die Lösung: In vielen Unternehmen kommen Open-Source-Überwachungstools (OSA-Praktiken) zum Einsatz, die helfen, die Verwendung von verwundbaren Versionen von Bibliotheken in den entwickelten Anwendungen zu erkennen. Manchmal ist es jedoch nicht möglich, eine solche Bibliothek auf eine sichere Version zu aktualisieren. In einigen Fällen gibt es funktionale Beschränkungen, in anderen ist eine sichere Version überhaupt nicht erhältlich. In solchen Fällen kann eine Kombination aus SAST- und OSA-Praktiken helfen, um festzustellen, dass die Funktionen, die zur Ausnutzung der Verwundbarkeit führen, im Code nicht verwendet werden.

Doch manchmal, besonders im Falle von JavaScript, kann das eine alles andere als triviale Aufgabe sein. Hier ist eine Lösung präsentiert, die vielleicht nicht perfekt, aber auf jeden Fall funktional ist, am Beispiel von Verwundbarkeiten in der Komponente lodash in den Methoden template und *set.

Beispiele für potenziell verwundbaren Testcode 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 beim direkten Einbinden 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>

Suchen wir alle unsere verwundbaren Methoden, die in den Verwundbarkeiten aufgelistet 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));

Aufgabe: Suche nach im Code hinterlegten Zertifikaten

Die Lösung: Es kommt häufig vor, dass Anwendungen, insbesondere mobile, Zertifikate oder Schlüssel verwenden, um auf verschiedene Server zuzugreifen oder SSL-Pinning zu überprüfen. Aus Sicherheitsperspektive ist das Speichern solcher Daten im Code jedoch nicht die beste Praxis. Lassen Sie uns eine Regel schreiben, die nach solchen 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;

Aufgabe: Suche nach kompromittierten Tokens in der Anwendung

Die Lösung: Es kommt oft vor, dass kompromittierte Tokens oder andere wichtige Informationen, die im Code vorhanden sind, widerrufen werden müssen. Natürlich ist es keine gute Idee, sie im Quellcode zu speichern, aber die Situationen sind unterschiedlich. Dank der CxQL-Abfragen ist es relativ einfach, solche Dinge zu finden:

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

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

Fazit

Ich hoffe, dass dieser Artikel für diejenigen, die ihr erstes Wissen über das Tool Checkmarx erwerben, nützlich ist. Vielleicht finden auch die, die schon lange ihre eigenen Regeln schreiben, hier etwas Hilfreiches in diesem Leitfaden.

Leider fehlt derzeit eine Ressource, wo man neue Ideen zur Entwicklung von Regeln für Checkmarx finden kann. Deshalb haben wir ein Repository auf Github erstellt, wo wir unsere Entwicklungen veröffentlichen, damit jeder, der CxQL verwendet, etwas Nützliches darin finden kann und auch die Möglichkeit hat, seine Arbeiten mit der Gemeinschaft zu teilen. Das Repository ist im Prozess der Befüllung und Strukturierung von Inhalten, daher sind Mitwirkende herzlich willkommen!

Vielen Dank für Ihre Aufmerksamkeit!

Quelle: habr.com

Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen 🔥 Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen | ProHoster