Hallo, Habra!
In unserer Arbeit hat unser Unternehmen sehr häufig mit verschiedenen Werkzeugen zur statischen Codeanalyse (SAST) zu tun. Out-of-the-Box arbeiten sie alle durchschnittlich. Natürlich hängt alles vom Projekt und den darin verwendeten Technologien ab, sowie davon, wie gut diese Technologien durch Analyse-Regeln abgedeckt sind. Meiner Meinung nach ist eines der wichtigsten Kriterien bei der Auswahl eines SAST-Tools die Möglichkeit, es an die Besonderheiten seiner Anwendungen anzupassen, nämlich Regeln für die Analyse zu schreiben und zu ändern oder, wie sie oft genannt werden, benutzerdefinierte Abfragen.

Wir verwenden meist Checkmarx – einen sehr interessanten und leistungsstarken Code-Analyzer. In diesem Artikel werde ich über meine Erfahrungen beim Schreiben von Analyse-Regeln für ihn berichten.
Inhaltsverzeichnis
Einleitung
Zunächst möchte ich einen der wenigen Artikel auf Russisch über die Besonderheiten des Schreibens von Abfragen für Checkmarx empfehlen. Er wurde Ende 2019 auf Habr veröffentlicht unter dem Titel: .
In ihm wird ausführlich beschrieben, wie man die ersten Abfragen in CxQL (Checkmarx Query Language) für eine bestimmte Testanwendung schreibt, und es werden die grundlegenden Arbeitsprinzipien der Analyse-Regeln gezeigt.
Ich werde nicht wiederholen, was dort beschrieben ist, obwohl es einige Überschneidungen geben wird. In meinem Artikel werde ich versuchen, eine Art „Rezeptsammlung“ zu erstellen, eine Liste von Lösungen für bestimmte Aufgaben, denen ich während meiner Arbeit mit Checkmarx begegnet bin. Bei vielen dieser Aufgaben musste ich meine Gedanken ordentlich zerbrechen. Manchmal fehlten Informationen in der Dokumentation, und manchmal war es überhaupt schwierig zu verstehen, wie das, was erforderlich ist, umgesetzt werden kann. Ich hoffe, dass meine Erfahrungen und schlaflosen Nächte nicht umsonst waren und dass diese „Rezeptsammlung für benutzerdefinierte Abfragen“ Ihnen einige Stunden oder ein paar Nerven erspart. Also, lassen Sie uns anfangen!
Allgemeine Informationen zu den Regeln
Zu Beginn betrachten wir einige grundlegende Begriffe und den Prozess der Arbeit mit Regeln, um ein besseres Verständnis dafür zu bekommen, was als Nächstes passieren wird. Und auch, weil dies in der Dokumentation nicht erwähnt wird oder stark über die Struktur verteilt ist, was nicht sehr praktisch ist.
Die Regeln werden beim Scannen abhängig von dem beim Start ausgewählten Preset (Set aktiver Regeln) angewendet. Es können unbegrenzt viele Presets erstellt werden, und wie genau diese strukturiert werden, hängt von den Besonderheiten Ihres Prozesses ab. Sie können sie nach Sprachen gruppieren oder Presets für jedes Projekt erstellen. Die Anzahl aktiver Regeln beeinflusst die Geschwindigkeit und Genauigkeit des Scans.
Preset-Konfiguration im Checkmarx-InterfaceDie Regeln werden mit einem speziellen Tool namens CxAuditor bearbeitet. Dies ist eine Desktop-Anwendung, die sich mit dem Server von Checkmarx verbindet. Dieses Tool hat zwei Betriebsmodi: Bearbeiten von Regeln und Analysieren von Ergebnissen bereits durchgeführter Scans.
CxAudit-InterfaceDie Regeln in Checkmarx sind nach Sprachen unterteilt, d.h. für jede Sprache gibt es ein eigenes Set an Anfragen. Es gibt auch einige allgemeine Regeln, die unabhängig von der Sprache angewendet werden, die sogenannten Basisanfragen. Die Basisanfragen enthalten größtenteils Suchabfragen nach Informationen, die von anderen Regeln verwendet werden.
Aufteilung der Regeln nach SprachenEs gibt „Executable“ und „Non-Executable“ Regeln. Das ist meiner Meinung nach nicht ganz korrekt, aber das ist, wie es ist. Der Unterschied besteht darin, dass die Ergebnisse der Ausführung von „Executable“ Regeln in den Scanresultaten in der Benutzeroberfläche angezeigt werden, während „Non-Executable“ Regeln nur dazu dienen, deren Ergebnisse in anderen Anfragen zu verwenden (im Grunde genommen – eine Funktion).
Bestimmung des Regeltyps bei der ErstellungNeue Regeln können erstellt oder bestehende ergänzt/neu geschrieben werden. Um eine Regel neu zu schreiben, muss man sie im Baum finden, mit der rechten Maustaste klicken und im Dropdown-Menü den Punkt „Override“ auswählen. Hier ist es wichtig zu beachten, dass neue Regeln anfangs nicht in den Presets aktiviert sind. Um sie zu verwenden, müssen sie im Menü „Preset Manager“ im Tool aktiviert werden. Überarbeitete Regeln behalten ihre Einstellungen, d.h. wenn eine Regel aktiv war, bleibt sie aktiv und wird sofort angewendet.
Beispiel einer neuen Regel im Preset-Manager-InterfaceWährend der Ausführung wird ein „Baum“ von Anfragen erstellt, der die Abhängigkeiten zwischen ihnen zeigt. Zunächst werden die Regeln ausgeführt, die Informationen sammeln, gefolgt von denen, die diese Informationen verwenden. Das Ergebnis der Ausführung wird zwischengespeichert, sodass, wenn es möglich ist, die Ergebnisse einer bestehenden Regel zu verwenden, dies bevorzugt wird, da es die Scanzeit verkürzt.
Regeln können auf verschiedenen Ebenen angewendet werden:
Für das gesamte System – wird bei jedem Scan eines beliebigen Projekts verwendet
Auf Team-Ebene – wird nur für das Scannen von Projekten im ausgewählten Team angewendet.
Auf Projektebene – wird in einem bestimmten Projekt angewendet
Definition der Ebene, auf der die Regel angewendet wird
Ein 'Wörterbuch' für Anfänger
Ich beginne mit einigen Punkten, die mir Fragen aufgeworfen haben, und zeige auch eine Reihe von Techniken, die das Leben erheblich erleichtern werden.
Operationen mit Listen
- Subtraktion (list2 - list1)
* Schnittmengen (list1 * list2)
+ Addition von Listen (list1 + list2)
& (logisches UND) – kombiniert Listen nach Übereinstimmung (list1 & list2), ähnlich wie bei der Schnittmenge (list1 * list2)
| (logisches ODER) – kombiniert Listen nach breiterer Suche (list1 | list2)
Funktioniert nicht mit Listen: ^ && || % / Alle gefundenen Elemente
Innerhalb der gescannten Sprache kann eine Liste aller Elemente, die von Checkmarx definiert wurden (Zeilen, Funktionen, Klassen, Methoden usw.), generiert werden. Dies ist ein gewisser Raum von Objekten, auf den über All. Das heißt, um ein Objekt mit einem bestimmten Namen zu finden searchMe, kann eine Suche, beispielsweise nach dem Namen in allen gefundenen Objekten, durchgeführt werden:
// Такой запрос выдаст все элементы
result = All;
// Такой запрос выдаст все элементы, в имени которых присутствует “searchMe“
result = All.FindByName("searchMe");Wenn jedoch eine Suche in einer anderen Sprache erforderlich ist, die aus bestimmten Gründen nicht im Scan enthalten ist (wie groovy in einem Android-Projekt), kann unser Objektbereich über eine Variable erweitert werden:
result = AllMembers.All.FindByName("searchMe");Funktionen zur Analyse des Flows
Diese Funktionen werden in vielen Regeln verwendet, und hier ist eine kleine Zusammenfassung, was sie bedeuten:
// Какие данные second влияют на first.
// Другими словами - ТО (second) что влияет на МЕНЯ (first).
result = first.DataInfluencedBy(second);
// Какие данные first влияют на second.
// Другими словами - Я (first) влияю на ТО (second).
result = first.DataInfluencingOn(second);Abrufen des Dateinamens/Pfads
Es gibt mehrere Attribute, die aus den Ergebnissen der Anfrage (Dateiname, in dem das Vorkommen gefunden wurde, Zeile usw.) abgerufen werden können, aber es wurde nicht erläutert, wie man sie abruft und verwendet. Um dies zu tun, muss auf die Eigenschaft LinePragma zugegriffen werden, und darin befinden sich die benötigten Objekte:
// Для примера найдем все методы
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 sollte beachtet werden, dass FileName enthält tatsächlich den Pfad zur Datei, da wir die Methode verwendet haben GetFirstGraph.
Ergebnis der Ausführung
Innerhalb von CxQL gibt es eine spezielle Variable result, die das Ergebnis der Ausführung Ihrer geschriebenen Regel zurückgibt. Sie ist sofort initialisiert und Sie können darin Zwischen Ergebnisse speichern, indem Sie sie im Laufe der Arbeit ändern und verfeinern. Wenn es jedoch in der Regel keine Zuweisung dieser Variablen oder Funktion gibt, returnwird das Ergebnis der Ausführung immer null sein.
Die folgende Abfrage wird uns bei der Ausführung nichts zurückgeben und immer leer sein:
// Находим элементы foo
CxList libraries = All.FindByName("foo");Aber wenn Sie das Ergebnis der Ausführung der magischen Variablen result zuweisen, werden wir sehen, was dieser Aufruf zurückgibt:
// Находим элементы foo
CxList libraries = All.FindByName("foo");
// Выводим, как результат выполнения правила
result = libraries
// Или еще короче
result = All.FindByName("foo");Verwendung der Ergebnisse der Ausführung anderer Regeln
Regeln in Checkmarx können als das Äquivalent von Funktionen in einer üblichen Programmiersprache bezeichnet werden. Beim Schreiben einer Regel können Sie durchaus die Ergebnisse anderer Abfragen verwenden. Zum Beispiel ist es nicht notwendig, jedes Mal alle Aufrufe von Methoden im Code zu suchen, es genügt, die benötigte Regel aufzurufen:
// Получаем результат выполнения другого правила
CxList methods = Find_Methods();
// Ищем внутри метод foo.
// Второй параметр false означает, что ищем без чувствительности к регистру
result = methods.FindByShortName("foo", false);Dieser 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 man muss experimentieren, verschiedene Varianten ausprobieren. Für solche Fälle ist im Tool das Logging vorgesehen, das wie folgt aufgerufen wird:
// Находим что-то
CxList toLog = All.FindByShortName("log");
// Формируем строку и отправляем в лог
cxLog.WriteDebugMessage (“number of DOM elements =” + All.Count);Aber man sollte bedenken, dass diese Methode nur eine Zeichenketteentgegennimmt, sodass es nicht möglich ist, die gesamte Liste der gefundenen Elemente als Ergebnis der ersten Operation auszugeben. Eine zweite Methode, die für Debugging verwendet wird, besteht darin, von Zeit zu Zeit der magischen Variablen result das Ergebnis der Abfrage zuzuweisen und zu sehen, was dabei herauskommt. Dieser Ansatz ist nicht sehr bequem, man muss sicher sein, dass im nachfolgenden Code keine Umstellungen oder Operationen damit vorgenommen werden, result oder einfach den darunter liegenden Code kommentieren. Manchmal kann man, so wie ich, vergessen, mehrere solcher Aufrufe aus der fertigen Regel zu entfernen und sich fragen, warum nichts funktioniert.
Eine bequemere Methode ist es, die Methode return mit dem entsprechenden Parameter aufzurufen. In diesem Fall wird die Ausführung der Regel beendet und wir können sehen, was wir als Ergebnis unseres Schreibens erhalten haben:
// Находим что-то
CxList toLog = All.FindByShortName("log");
// Выводим результат выполнения
return toLog
//Все, что написано дальше не будет выполнено
result = All.DataInfluencedBy(toLog)Problem mit dem Login
Es gibt Situationen, in denen man nicht auf das Tool CxAudit (das zur Erstellung von Regeln verwendet wird) zugreifen kann. Es kann dafür viele Gründe geben, wie ein unerwarteter Programmabsturz, plötzliche Windows-Updates, BSOD und andere unvorhergesehene Ereignisse, die außerhalb unserer Kontrolle liegen. In einem solchen Fall bleibt manchmal eine unvollendete Sitzung in der Datenbank, die einen erneuten Zugriff verhindert. Um das zu beheben, müssen einige Abfragen ausgeführt werden:
Für Checkmarx bis 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');Schreiben von Regeln
Nun kommen wir zum Interessantesten. Wenn man beginnt, Regeln in CxQL zu schreiben, fehlt es oft nicht nur an Dokumentation, sondern vor allem an lebendigen Beispielen zur Lösung bestimmter Aufgaben und einer Beschreibung des Abfrageprozesses im Allgemeinen.
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 zu zeigen. Einige davon sind recht allgemein und können in Ihrem Unternehmen praktisch unverändert angewendet werden, andere sind spezifischer, doch auch sie können verwendet werden, indem man den Code an die Besonderheiten Ihrer Anwendungen anpasst.
Hier sind die Aufgaben, mit denen wir am häufigsten konfrontiert wurden:
Die Aufgabe ist: In den Ergebnissen der Regelausführung gibt es mehrere Flows, wobei einer den anderen einschließt; es ist notwendig, einen von ihnen beizubehalten.
Lösung: Tatsächlich zeigt Checkmarx manchmal mehrere Flows der Datenbewegung, 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 es den kürzesten oder längsten Flow:
// Оставить только длинные Flow
result = result.ReduceFlow(CxList.ReduceFlowType.ReduceSmallFlow);
// Оставить только короткие Flow
result = result.ReduceFlow(CxList.ReduceFlowType.ReduceBigFlow);Die Aufgabe ist: Erweitern Sie die Liste der sensitiven Daten, auf die das Tool reagiert.
Lösung: In Checkmarx gibt es grundlegende Regeln, deren Ausführungsresultate von vielen anderen Abfragen genutzt werden. Wenn Sie einige dieser Regeln mit für Ihre Anwendung spezifischen Daten ergänzen, können Sie die Scanergebnisse sofort verbessern. Hier ist 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);Die Aufgabe ist: Die Liste der Variablen mit Passwörtern erweitern.
Lösung: Ich würde empfehlen, sofort auf die grundlegende Regel zur Definition von Passwörtern im Code zu achten und eine Liste von 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 Sie die Standardliste der Variablen
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);
}
}Die Aufgabe ist: Fügen Sie die verwendeten Frameworks hinzu, die von Checkmarx nicht unterstützt werden.
Lösung: Alle Anfragen in Checkmarx sind nach Sprachen unterteilt, sodass die Regeln für jede Sprache ergänzt werden müssen. Nachfolgend einige Beispiele für solche Regeln.
Wenn Bibliotheken verwendet werden, die die Standardfunktionalität erweitern oder ersetzen, können sie leicht in die grundlegende Regel aufgenommen werden. Dann erfahren alle, die sie verwenden, sofort von neuen Eingaben. Zum Beispiel die Bibliotheken für das Logging in Android — Timber und Loggi. In der Basisausstattung der Regeln zur Definition nicht-systemischer Aufrufe sind solche Regeln nicht enthalten, sodass wir nicht erfahren, wenn ein Passwort oder eine Sitzungs-ID ins Log gelangt. Wir werden versuchen, solche Methoden in den Regeln von Checkmarx zu definieren.
Beispielcode, der die Bibliothek Timber für das 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("Error Message");
Timber.d("Debug Message");
Timber.tag("Some Different tag").e("And error message");
}
}Hier ist ein Beispiel für eine Anfrage an Checkmarx, die es ermöglicht, die Definition des Aufrufs von Timber-Methoden als Punkt für den Datenexport aus der Anwendung hinzuzufügen:
FindAndroidOutputs
// Получаем результат выполнения базового правила
result = base.Find_Android_Outputs();
// Дополняем вызовами, которые приходят из библиотеки Timber
CxList timber = All.FindByExactMemberAccess("Timber.*") +
All.FindByShortName("Timber").GetMembersOfTarget();
// Добавляем к конечному результату
result.Add(timber);Es kann auch die benachbarte Regel ergänzt werden, die sich konkret auf das Logging in Android bezieht:
FindAndroidLog_Outputs
// Получаем результат выполнения базового правила
result = base.Find_Android_Log_Outputs();
// Дополняем вызовами, которые приходят из библиотеки Timber
result.Add(
All.FindByExactMemberAccess("Timber.*") +
All.FindByShortName("Timber").GetMembersOfTarget()
);Wenn in Android-Anwendungen für asynchrone Aufgaben verwendet wird, wäre es gut, Checkmarx zusätzlich darüber zu informieren, indem man die Methode zum Abrufen von Daten aus der Aufgabe getInputData:
FindAndroidRead
// Получаем результат выполнения базового правила
result = base.Find_Android_Read();
// Дополняем вызовом функции getInputData, которая используется в WorkManager
CxList getInputData = All.FindByShortName("getInputData");
// Добавляем к конечному результату
result.Add(getInputData.GetMembersOfTarget());Die Aufgabe ist: Suche nach sensiblen Daten in plist für iOS-Projekte
Lösung: Häufig werden in iOS spezielle Dateien mit der Endung .plist verwendet, um verschiedene Variablen und Werte zu speichern. Das Speichern von Passwörtern, Tokens, Schlüsseln und anderen sensiblen Daten in diesen Dateien ist nicht empfehlenswert, da sie ohne größere Probleme vom Gerät extrahiert werden können.
Plist-Dateien haben Eigenschaften, die dem ungeschulten Auge nicht offensichtlich sind, aber für Checkmarx wichtig sind. Wir werden eine Regel schreiben, die nach den von uns benötigten Daten sucht und uns informiert, wenn irgendwo Passwörter oder Tokens erwähnt werden.
Ein Beispiel für eine solche Datei, die ein Token für die Kommunikation mit dem Backend-Service enthält:
DeviceDictionary
phone
iPhone 6s
privatekey
MIICXAIBAAKBgQCqGKukO1De7zhZj6+Und eine Regel für Checkmarx, die mehrere Nuancen enthält, die bei der Erstellung zu berücksichtigen sind:
// Используем результат выполнения правила по поиску файлов 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);Die Aufgabe ist: Suche nach Informationen in XML
Lösung: Checkmarx bietet sehr praktische Funktionen zur Verarbeitung von XML und zur Suche nach Werten, Tags, Attributen und mehr. Leider gibt es einen Fehler in der Dokumentation, aufgrund dessen kein einziges Beispiel funktioniert. Auch wenn in der letzten Version der Dokumentation dieser Mangel 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);Als Ergebnis des Versuchs werden wir eine Fehlermeldung erhalten, dass All Es gibt keine solche Methode... Und das ist korrekt, da es für die Verwendung von Funktionen zur Arbeit mit XML einen speziellen, separaten Objektbereich gibt — cxXPath. So sieht die korrekte Anfrage zum Finden der Einstellung in Android aus, die die Verwendung von HTTP-Verkehr erlaubt:
// Правильный вариант с использованием cxXPath
result = cxXPath.FindXmlAttributesByNameAndValue("*.xml", 8, "cleartextTrafficPermitted", "true", false, true);Lass uns das etwas detaillierter betrachten, da die Syntax aller Funktionen ähnlich ist. Nachdem man mit einer funktioniert hat, muss man nur die benötigte auswählen. Also, schrittweise nach Parametern:
"*.xml"— die Masken für die Dateien, in denen die Suche durchgeführt werden muss8— die id der Sprache, für die die Regel angewendet wird"cleartextTrafficPermitted"— der Name des Attributs in xml"true"— der Wert dieses Attributsfalse— die Verwendung von regulären Ausdrücken bei der Suchetrue— bedeutet, dass die Suche ohne Berücksichtigung der Groß- und Kleinschreibung durchgeführt wird, also case-insensitive
Als Beispiel wurde eine Regel verwendet, die unsichere Einstellungen für Netzwerkverbindungen in Android definiert, die die Kommunikation mit dem Server über das HTTP-Protokoll erlauben. Beispiel für eine Einstellung, die das Attribut enthält cleartextTrafficPermitted mit dem Wert true:
example.com
secure.example.comDie Aufgabe ist: Ergebnisse nach Dateiname/Pfad einschränken
Lösung: In einem der größeren Projekte, die mit der Entwicklung einer mobilen Anwendung für Android verbunden sind, stießen wir auf Fehlalarme der Regel, die die Obfuskationseinstellung definiert. Das Problem ist, dass die Regel standardmäßig die Datei durchsucht build.gradle die für die Anwendung der Obfuskationsregeln in der Release-Version verantwortlich ist.
Aber in großen Projekten gibt es manchmal untergeordnete Dateien build.gradle, die zu Bibliotheken gehören, die im Projekt enthalten sind. Das Besondere ist, dass, auch wenn in diesen Dateien nicht angegeben ist, dass eine Obfuskation notwendig ist, bei der Kompilierung die Einstellungen der übergeordneten Build-Datei angewendet werden.
Somit besteht die Aufgabe darin, auszulösen, wenn es um untergeordnete Dateien geht, die zu Bibliotheken gehören. Diese können durch das Vorhandensein der Zeichenfolge erkannt werden apply 'com.android.library'.
Ein Codebeispiel aus der Datei build.gradle, das die Notwendigkeit der Obfuskation definiert:
apply plugin: 'com.android.application'
android {
compileSdkVersion 24
buildToolsVersion "24.0.2"
defaultConfig {
...
}
buildTypes {
release {
minifyEnabled true
...
}
}
}
dependencies {
...
}Beispieldatei build.gradle für die im Projekt eingebundene Bibliothek, die über keine solche Einstellung verfügt:
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 ausreichend vielseitig sein und ist nicht nur für Android-Anwendungen nützlich, sondern auch in anderen Situationen, in denen es erforderlich ist, das Ergebnis einem bestimmten File zuzuordnen.
Die Aufgabe ist: Fügen Sie Unterstützung für eine Drittanbieterbibliothek 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 überwältigend. Natürlich weiß Checkmarx nicht immer über deren Existenz Bescheid, und unsere Aufgabe besteht darin, ihm beizubringen, dass bestimmte Methoden genau zu diesem Framework gehören. Manchmal wird dies dadurch erschwert, dass Frameworks Funktionsnamen verwenden, die sehr verbreitet sind, und man kann nicht eindeutig bestimmen, zu welcher Bibliothek ein bestimmter Aufruf gehört.
Die Schwierigkeit liegt darin, dass die Syntax solcher Bibliotheken nicht immer korrekt erkannt wird, und man muss experimentieren, um eine hohe Anzahl an Fehlalarmen zu vermeiden. Es gibt mehrere Optionen, um die Scannergebnisse zu verbessern und das Problem zu lösen:
Option eins: Wir wissen genau, dass eine Bibliothek in einem bestimmten Projekt verwendet wird, und können die Regel auf Teamebene anwenden. Wenn das Team jedoch entscheidet, einen anderen Ansatz zu verfolgen oder mehrere Bibliotheken zu verwenden, bei denen sich die Funktionsnamen überschneiden, könnte das zu einer nicht gerade erfreulichen Situation mit zahlreichen Fehlalarmen führen.
Option zwei: Durchsuchen von Dateien, in denen die Bibliothek eindeutig importiert wird. Mit diesem Ansatz können wir sicher sein, dass in dieser Datei die benötigte Bibliothek verwendet wird.
Und Option drei: die Kombination der beiden zuvor genannten Ansätze.
Als Beispiel betrachten wir eine in Fachkreisen bekannte Bibliothek für die Programmiersprache Scala, insbesondere die Funktionalität . Im Allgemeinen müssen zur Übergabe von Parametern in eine SQL-Abfrage der Operator $, der Daten in eine zuvor formuliere SQL-Abfrage einfügt, verwendet werden. Das bedeutet, dass er im Grunde das direkte Pendant zu Prepared Statement in Java ist. Sollte jedoch eine dynamische Konstruktion einer SQL-Abfrage erforderlich sein, zum Beispiel wenn Tabellennamen übergeben werden müssen, kann der Operator #$, der Daten direkt in die Abfrage einfügt (praktisch wie die Verkettung von Strings), verwendet werden.
Beispielcode:
// В общем случае - значения, контролируемые пользователем
val table = "coffees"
sql"select * from #$table where name = $name".as[Coffee].headOptionCheckmarx kann derzeit keine Verwendung von Splicing Literal Values erkennen und überspringt die Operatoren #$, daher versuchen wir, ihn zu lehren, potenzielle SQL-Injektionen zu erkennen 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));
}Die Aufgabe ist: Suche nach verwendeten verwundbaren Funktionen in Open-Source-Bibliotheken
Lösung: In vielen Unternehmen werden Tools zur Überwachung von Open-Source (OSA-Praxis) eingesetzt, die es ermöglichen, die Verwendung verwundbarer Versionen von Bibliotheken in entwickelten Anwendungen zu erkennen. Manchmal ist es nicht möglich, eine solche Bibliothek auf eine sichere Version zu aktualisieren. In manchen Fällen gibt es funktionale Einschränkungen, in anderen sogar überhaupt keine sichere Version. In einem solchen Fall kann eine Kombination aus SAST- und OSA-Praktiken helfen, um festzustellen, dass Funktionen, die zur Ausnutzung von Schwachstellen führen, im Code nicht verwendet werden.
Aber manchmal kann es, insbesondere bei JavaScript, nicht ganz trivial sein. Nachfolgend finden Sie eine Lösung, die möglicherweise nicht ideal, aber dennoch funktionsfähig ist, am Beispiel von Schwachstellen im Komponent 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 Einfügen 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 all unseren verwundbaren Methoden, die in den Schwachstellen 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));Die Aufgabe ist: Suche nach fest im Anwendungscode enthaltenen Zertifikaten
Lösung: Häufig verwenden Anwendungen, insbesondere mobile, Zertifikate oder Schlüssel für den Zugriff auf verschiedene Server oder zur Überprüfung von SSL-Pinning. Aus Sicherheitsgründen ist es jedoch keine gute Praxis, solche Dinge im Code zu speichern. 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;Die Aufgabe ist: Suche nach kompromittierten Tokens in der Anwendung
Lösung: Es kommt häufig vor, dass kompromittierte Tokens oder andere wichtige Informationen, die im Code vorhanden sind, zurückgerufen werden müssen. Natürlich ist es keine gute Idee, sie innerhalb des Quellcodes zu speichern, aber die Situationen können unterschiedlich sein. Dank der CxQL-Abfragen ist es recht 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 ihre Bekanntschaft mit dem Checkmarx-Tool beginnen, hilfreich sein wird. Vielleicht finden auch diejenigen, die schon lange ihre eigenen Regeln schreiben, in diesem Leitfaden etwas Nützliches.
Leider fehlt es derzeit sehr an Ressourcen, wo man neue Ideen für die Entwicklung von Regeln für Checkmarx finden kann. Deshalb haben wir , wo wir unsere Ergebnisse veröffentlichen werden, damit jeder, der CxQL verwendet, darin etwas Nützliches finden kann und auch die Möglichkeit hat, seine Arbeiten mit der Gemeinschaft zu teilen. Das Repository befindet sich in der Phase der Befüllung und Strukturierung des Inhalts, daher sind Beiträge willkommen!
Vielen Dank für Ihre Aufmerksamkeit!
Quelle: habr.com

Preset-Konfiguration im Checkmarx-Interface
CxAudit-Interface
Aufteilung der Regeln nach Sprachen
Bestimmung des Regeltyps bei der Erstellung
Beispiel einer neuen Regel im Preset-Manager-Interface
Definition der Ebene, auf der die Regel angewendet wird