Comment écrire des règles pour Checkmarx sans devenir fou

Hé Habr !

Dans notre travail, notre entreprise utilise très souvent divers outils d'analyse de code statique (SAST). Hors de la boîte, ils fonctionnent tous en moyenne. Bien entendu, tout dépend du projet et des technologies utilisées, ainsi que de la manière dont ces technologies sont couvertes par les règles d'analyse. À mon avis, l'un des critères les plus importants lors du choix d'un outil SAST est la possibilité de l'adapter aux spécificités de vos applications, à savoir écrire et modifier des règles d'analyse ou, comme on les appelle plus souvent, des requêtes personnalisées.

Comment écrire des règles pour Checkmarx sans devenir fou

Nous utilisons le plus souvent Checkmarx - un analyseur de code très intéressant et puissant. Dans cet article, je parlerai de mon expérience dans l'écriture de règles d'analyse pour celui-ci.

table des matières

Entrée

Pour commencer, je voudrais recommander l'un des rares articles en russe sur les fonctionnalités d'écriture de requêtes pour Checkmarx. Elle a été publiée sur Habré fin 2019 sous le titre : "Bonjour Checkmarx !" Comment écrire une requête Checkmarx SAST et trouver des vulnérabilités intéressantes.

Il examine en détail comment écrire les premières requêtes en CxQL (Checkmarx Query Language) pour certaines applications de test et montre les principes de base du fonctionnement des règles d'analyse.

Je ne répéterai pas ce qui y est décrit, même si certaines intersections seront toujours présentes. Dans mon article, je vais essayer de compiler une sorte de « recueil de recettes », une liste de solutions à des problèmes spécifiques que j'ai rencontrés lors de mon travail avec Checkmarx. J'ai dû me creuser la tête sur bon nombre de ces problèmes. Parfois, il n'y avait pas suffisamment d'informations dans la documentation, et parfois il était même difficile de comprendre comment faire ce qui était demandé. J'espère que mon expérience et mes nuits blanches ne seront pas vaines, et cette « collection de recettes de requêtes personnalisées » vous fera gagner quelques heures ou quelques cellules nerveuses. Alors commençons !

Informations générales sur les règles

Tout d'abord, examinons quelques concepts de base et le processus d'utilisation des règles, pour une meilleure compréhension de ce qui va se passer ensuite. Et aussi parce que la documentation ne dit rien à ce sujet ou est très dispersée dans la structure, ce qui n’est pas très pratique.

  1. Les règles sont appliquées lors de l'analyse en fonction du préréglage sélectionné au démarrage (un ensemble de règles actives). Vous pouvez créer un nombre illimité de préréglages, et la manière exacte de les structurer dépend des spécificités de votre processus. Vous pouvez les regrouper par langue ou sélectionner des préréglages pour chaque projet. Le nombre de règles actives affecte la vitesse et la précision de l'analyse.

    Comment écrire des règles pour Checkmarx sans devenir fouConfiguration du préréglage dans l'interface Checkmarx

  2. Les règles sont éditées dans un outil spécial appelé CxAuditor. Il s'agit d'une application de bureau qui se connecte à un serveur exécutant Checkmarx. Cet outil dispose de deux modes de fonctionnement : l'édition des règles et l'analyse des résultats d'une analyse déjà effectuée.

    Comment écrire des règles pour Checkmarx sans devenir fouInterface CxAudit

  3. Les règles de Checkmarx sont divisées par langue, c'est-à-dire que chaque langue possède son propre ensemble de requêtes. Il existe également quelques règles générales qui s'appliquent quelle que soit la langue, ce sont les requêtes dites de base. Pour la plupart, les requêtes de base impliquent la recherche d’informations utilisées par d’autres règles.

    Comment écrire des règles pour Checkmarx sans devenir fouDiviser les règles par langue

  4. Les règles sont « exécutables » et « non-exécutables » (exécutées et non exécutées). Ce n’est pas tout à fait le bon nom, à mon avis, mais c’est ça. L'essentiel est que le résultat de l'exécution des règles « exécutables » sera affiché dans les résultats de l'analyse dans l'interface utilisateur, et que les règles « non exécutables » ne sont nécessaires que pour utiliser leurs résultats dans d'autres requêtes (en substance, juste une fonction).

    Comment écrire des règles pour Checkmarx sans devenir fouDétermination du type de règle lors de la création

  5. Vous pouvez créer de nouvelles règles ou compléter/réécrire celles existantes. Pour réécrire une règle, vous devez la trouver dans l'arborescence, faire un clic droit et sélectionner « Remplacer » dans le menu déroulant. Il est important de rappeler ici que les nouvelles règles ne sont pas initialement incluses dans les préréglages et ne sont pas actives. Pour commencer à les utiliser, vous devez les activer dans le menu « Preset Manager » de l'instrument. Les règles réécrites conservent leurs paramètres, c'est-à-dire que si la règle était active, elle le restera et sera appliquée immédiatement.

    Comment écrire des règles pour Checkmarx sans devenir fouExemple de nouvelle règle dans l'interface Preset Manager

  6. Lors de l'exécution, un « arbre » de requêtes est construit, qui dépend de quoi. Les règles qui collectent les informations sont exécutées en premier, et celles qui les utilisent en second. Le résultat de l'exécution est mis en cache, donc s'il est possible d'utiliser les résultats d'une règle existante, alors il est préférable de le faire, cela réduira le temps d'analyse.

  7. Les règles peuvent être appliquées à différents niveaux :

  • Pour l'ensemble du système - sera utilisé pour toute analyse de n'importe quel projet

  • Au niveau de l'équipe (Équipe) - sera utilisé uniquement pour analyser les projets de l'équipe sélectionnée.

  • Au niveau du projet - Sera appliqué dans un projet spécifique

    Comment écrire des règles pour Checkmarx sans devenir fouDéterminer le niveau auquel la règle sera appliquée

« Dictionnaire » pour les débutants

Et je commencerai par quelques choses qui m'ont posé des questions, et je montrerai également un certain nombre de techniques qui simplifieront considérablement la vie.

Opérations avec des listes

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

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

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

Tous les objets trouvés

Dans le langage numérisé, vous pouvez obtenir une liste d'absolument tous les éléments identifiés par Checkmarx (chaînes, fonctions, classes, méthodes, etc.). Il s'agit d'un espace d'objets accessible via All. Autrement dit, rechercher un objet avec un nom spécifique searchMe, vous pouvez effectuer une recherche, par exemple, par nom parmi tous les objets trouvés :

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

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

Mais, si vous devez effectuer une recherche dans une autre langue qui, pour une raison quelconque, n'a pas été incluse dans l'analyse (par exemple, groovy dans un projet Android), vous pouvez étendre notre espace objet via une variable :

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

Fonctions d'analyse de flux

Ces fonctions sont utilisées dans de nombreuses règles et voici un petit aide-mémoire de ce qu'elles signifient :

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

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

Obtenir le nom/chemin du fichier

Plusieurs attributs peuvent être obtenus à partir des résultats d'une requête (le nom du fichier dans lequel l'entrée a été trouvée, la chaîne, etc.), mais la documentation ne précise pas comment les obtenir et les utiliser. Donc, pour ce faire, vous devez accéder à la propriété LinePragma et les objets dont nous avons besoin seront situés à l'intérieur :

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

Il convient de garder à l'esprit que FileName contient en fait le chemin du fichier, puisque nous avons utilisé la méthode GetFirstGraph.

Résultat de l'exécution

Il existe une variable spéciale dans CxQL result, qui renvoie le résultat de l'exécution de votre règle écrite. Il est initialisé immédiatement et vous pouvez y écrire des résultats intermédiaires, en les modifiant et en les affinant au fur et à mesure de votre travail. Mais, s'il n'y a aucune affectation à cette variable ou fonction dans la règle return— le résultat de l'exécution sera toujours nul.

La requête suivante ne nous retournera rien suite à son exécution et sera toujours vide :

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

Mais, après avoir affecté le résultat de l'exécution à la variable magique result, nous verrons ce que nous renvoie cet appel :

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

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

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

Utiliser les résultats d'autres règles

Les règles de Checkmarx peuvent être qualifiées d’analogues aux fonctions d’un langage de programmation classique. Lorsque vous rédigez une règle, vous pouvez très bien utiliser les résultats d’autres requêtes. Par exemple, il n'est pas nécessaire de rechercher à chaque fois tous les appels de méthode dans le code, il suffit d'appeler la règle souhaitée :

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

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

Cette approche permet de raccourcir le code et de réduire considérablement le temps d'exécution des règles.

Résolution de problèmes

Enregistrement

Lorsque vous travaillez avec l'outil, il n'est parfois pas possible d'écrire immédiatement la requête souhaitée et vous devez expérimenter en essayant différentes options. Dans un tel cas, l'outil fournit une journalisation, appelée comme suit :

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

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

Mais il convient de rappeler que cette méthode n'accepte qu'en entrée la ficelle, il ne sera donc pas possible d'afficher une liste complète des éléments trouvés suite à la première opération. La deuxième option, utilisée pour le débogage, consiste à attribuer une variable magique de temps en temps. result le résultat de la requête et voir ce qui se passe. Cette approche n'est pas très pratique ; vous devez être sûr qu'il n'y a pas de substitutions ou d'opérations avec cela dans le code après result ou commentez simplement le code ci-dessous. Ou vous pouvez, comme moi, oublier de supprimer plusieurs de ces appels d'une règle toute faite et vous demander pourquoi rien ne fonctionne.

Un moyen plus pratique consiste à appeler la méthode return avec le paramètre requis. Dans ce cas, l'exécution de la règle prendra fin et nous pourrons voir ce qui s'est passé à la suite de ce que nous avons écrit :

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

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

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

Problème de connexion

Il existe des situations dans lesquelles vous ne pouvez pas accéder à l'outil CxAudit (qui est utilisé pour écrire des règles). Il peut y avoir de nombreuses raisons à cela, notamment des plantages, des mises à jour soudaines de Windows, des BSOD et d'autres situations imprévues qui échappent à notre contrôle. Dans ce cas, il y a parfois une session inachevée dans la base de données, ce qui vous empêche de vous reconnecter. Pour résoudre ce problème, vous devez exécuter plusieurs requêtes :

Pour Checkmarx avant 8.6 :

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

Pour Checkmarx après 8.6 :

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

Règles d'écriture

Passons maintenant à la partie la plus intéressante. Lorsque vous commencez à écrire des règles dans CxQL, ce qui vous manque souvent, ce n'est pas tant de documentation que des exemples concrets de résolution de certains problèmes et de description du processus de fonctionnement des requêtes en général.

Je vais essayer de rendre la vie un peu plus facile à ceux qui commencent à se plonger dans le langage de requête et donner plusieurs exemples d'utilisation de requêtes personnalisées pour résoudre certains problèmes. Certains d'entre eux sont assez généraux et peuvent être utilisés dans votre entreprise pratiquement sans modifications, d'autres sont plus spécifiques, mais ils peuvent également être utilisés en modifiant le code pour l'adapter aux spécificités de vos applications.

Voici donc les problèmes que nous avons rencontrés le plus souvent :

Tâche Il y a plusieurs Flux dans les résultats de l'exécution de la règle et l'un d'eux est une imbrication d'un autre, vous devez en laisser un.

solution: En effet, Checkmarx affiche parfois plusieurs flux de données qui peuvent se chevaucher et être une version abrégée des autres. Il existe une méthode spéciale pour de tels cas Réduire le flux. En fonction du paramètre, il sélectionnera le Débit le plus court ou le plus long :

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

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

Tâche Développez la liste des données sensibles auxquelles l'outil réagit

solution: Checkmarx a des règles de base dont les résultats sont utilisés par de nombreuses autres requêtes. En complétant certaines de ces règles avec des données spécifiques à votre application, vous pouvez immédiatement améliorer les résultats de votre analyse. Vous trouverez ci-dessous un exemple de règle pour vous aider à démarrer :

Liste_générale_de_violation_privacy

Ajoutons plusieurs variables utilisées dans notre application pour stocker des informations sensibles :

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

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

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

Tâche Développez la liste des variables avec des mots de passe

solution: Je recommanderais immédiatement de prêter attention à la règle de base pour définir les mots de passe dans le code et d'y ajouter une liste de noms de variables couramment utilisés dans votre entreprise.

Liste_de_violations_de_privacy_de_motsdepasse

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

Tâche Ajouter des frameworks utilisés qui ne sont pas pris en charge par Checkmarx

solution: Toutes les requêtes dans Checkmarx sont divisées par langue, vous devez donc ajouter des règles pour chaque langue. Vous trouverez ci-dessous quelques exemples de telles règles.

Si des bibliothèques sont utilisées pour compléter ou remplacer les fonctionnalités standard, elles peuvent être facilement ajoutées à la règle de base. Ainsi, tous ceux qui l'utilisent seront immédiatement informés des nouvelles introductions. À titre d'exemple, les bibliothèques de connexion sous Android sont Timber et Loggi. Dans le package de base, il n'y a pas de règles pour identifier les appels non système, donc si un mot de passe ou un identifiant de session entre dans le journal, nous ne le saurons pas. Essayons d'ajouter des définitions de ces méthodes aux règles Checkmarx.

Exemple de code de test qui utilise la bibliothèque Timber pour la journalisation :

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

Et voici un exemple de requête pour Checkmarx, qui permettra d'ajouter une définition d'appel aux méthodes Timber comme point de sortie des données de l'application :

Rechercher des sorties Android

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

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

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

Et vous pouvez aussi ajouter à la règle voisine, mais celle-ci concerne directement la connexion sous Android :

TrouverAndroidLog_Outputs

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

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

De plus, si les applications Android utilisent Gestionnaire de travaux pour le travail asynchrone, c'est une bonne idée d'en informer également Checkmarx en ajoutant une méthode pour obtenir des données de la tâche getInputData:

RechercherAndroidLire

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

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

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

Tâche Recherche de données sensibles dans plist pour les projets iOS

solution: iOS utilise souvent des fichiers spéciaux avec l'extension .plist pour stocker diverses variables et valeurs. Il n'est pas recommandé de stocker des mots de passe, des jetons, des clés et d'autres données sensibles dans ces fichiers, car ils peuvent être extraits de l'appareil sans aucun problème.

Les fichiers Plist ont des fonctionnalités qui ne sont pas évidentes à l'œil nu, mais qui sont importantes pour Checkmarx. Écrivons une règle qui recherchera les données dont nous avons besoin et nous dira si des mots de passe ou des jetons sont mentionnés quelque part.

Un exemple d'un tel fichier, qui contient un jeton pour la communication avec le service backend :

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

Et une règle pour Checkmarx, qui comporte plusieurs nuances à prendre en compte lors de la rédaction :

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

Tâche Recherche d'informations en XML

solution: Checkmarx possède des fonctions très pratiques pour travailler avec XML et rechercher des valeurs, des balises, des attributs et plus encore. Mais malheureusement, il y a eu une erreur dans la documentation à cause de laquelle aucun exemple ne fonctionne. Malgré le fait que ce défaut ait été éliminé dans la dernière version de la documentation, soyez prudent si vous utilisez des versions antérieures des documents.

Voici un exemple incorrect tiré de la documentation :

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

À la suite de la tentative d'exécution, nous recevrons une erreur qui All il n'existe pas une telle méthode... Et cela est vrai, car il existe un espace objet spécial et séparé pour utiliser les fonctions permettant de travailler avec XML - cxXPath. Voici à quoi ressemble la requête correcte pour trouver un paramètre dans Android qui permet l'utilisation du trafic HTTP :

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

Examinons-le un peu plus en détail, puisque la syntaxe de toutes les fonctions est similaire, après en avoir trouvé une, il vous suffit alors de sélectionner celle dont vous avez besoin. Ainsi, séquentiellement selon les paramètres :

  • "*.xml"— masque des fichiers à rechercher

  • 8 — identifiant de la langue pour laquelle la règle est appliquée

  • "cleartextTrafficPermitted"— nom de l'attribut en XML

  • "true" — la valeur de cet attribut

  • false — utilisation d'une expression régulière lors de la recherche

  • true — signifie que la recherche sera effectuée en ignorant la casse, c'est-à-dire sans tenir compte de la casse

A titre d'exemple, nous avons utilisé une règle qui identifie les paramètres de connexion réseau incorrects, du point de vue de la sécurité, dans Android qui permettent la communication avec le serveur via le protocole HTTP. Exemple de paramètre contenant un attribut cleartextTrafficPermitted avec un sens 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>

Tâche Limiter les résultats par nom/chemin de fichier

solution: Dans l'un des grands projets liés au développement d'une application mobile pour Android, nous avons rencontré des faux positifs de la règle qui détermine le paramètre d'obscurcissement. Le fait est que la règle des recherches prêtes à l'emploi dans le fichier build.gradle un paramètre responsable de l’application des règles d’obscurcissement pour la version commerciale de l’application.

Mais dans les grands projets, il y a parfois des fichiers enfants build.gradle, qui font référence aux bibliothèques incluses dans le projet. La particularité est que même si ces fichiers n'indiquent pas la nécessité d'un obscurcissement, les paramètres du fichier assembly parent seront appliqués lors de la compilation.

Ainsi, la tâche consiste à supprimer les déclencheurs dans les fichiers enfants appartenant aux bibliothèques. Ils peuvent être identifiés par la présence de la ligne apply 'com.android.library'.

Exemple de code à partir d'un fichier build.gradle, qui détermine le besoin d'obscurcissement :

apply plugin: 'com.android.application'

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

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

dependencies {
  ...
}

Exemple de fichier build.gradle pour une bibliothèque incluse dans le projet qui n'a pas ce paramètre :

apply plugin: 'android-library'

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

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

Et la règle pour 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);
		}
	}
}

Cette approche peut être assez universelle et utile non seulement pour les applications Android, mais également dans d'autres cas où vous devez déterminer si un résultat appartient à un fichier spécifique.

Tâche Ajouter la prise en charge d'une bibliothèque tierce si la syntaxe n'est pas entièrement prise en charge

solution: Le nombre de frameworks différents utilisés dans le processus d’écriture de code est tout simplement hors du commun. Bien entendu, Checkmarx n'est pas toujours au courant de leur existence, et notre tâche est de lui apprendre à comprendre que certaines méthodes appartiennent spécifiquement à ce cadre. Parfois, cela est compliqué par le fait que les frameworks utilisent des noms de fonctions très courants et qu'il est impossible de déterminer sans ambiguïté la relation entre un appel particulier et une bibliothèque spécifique.

La difficulté est que la syntaxe de ces bibliothèques n'est pas toujours reconnue correctement et qu'il faut expérimenter pour éviter d'obtenir un grand nombre de faux positifs. Il existe plusieurs options pour améliorer la précision de la numérisation et résoudre le problème :

  • Dans la première option, nous savons avec certitude que la bibliothèque est utilisée dans un projet spécifique et pouvons appliquer la règle au niveau de l'équipe. Mais si l'équipe décide d'adopter une approche différente ou utilise plusieurs bibliothèques dans lesquelles les noms de fonctions se chevauchent, nous pouvons avoir une image peu agréable de nombreux faux positifs.

  • La deuxième option consiste à rechercher des fichiers dans lesquels la bibliothèque est clairement importée. Avec cette approche, nous pouvons être sûrs que la bibliothèque dont nous avons besoin est exactement utilisée dans ce fichier.

  • Et la troisième option consiste à utiliser ensemble les deux approches ci-dessus.

A titre d'exemple, regardons une bibliothèque bien connue dans les cercles étroits nappe pour le langage de programmation Scala, à savoir la fonctionnalité Épissage de valeurs littérales. De manière générale, pour passer des paramètres à une requête SQL, vous devez utiliser l'opérateur $, qui remplace les données dans une requête SQL préformée. Autrement dit, il s’agit d’un analogue direct de l’instruction préparée en Java. Mais si vous devez construire dynamiquement une requête SQL, par exemple si vous devez transmettre des noms de tables, vous pouvez utiliser l'opérateur #$, qui remplacera directement les données dans la requête (presque comme une concaténation de chaînes).

Exemple de code:

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

Checkmarx ne sait pas encore détecter l'utilisation des valeurs littérales d'épissage et ignore les opérateurs #$, essayons donc de lui apprendre à identifier les potentielles injections SQL et à mettre en évidence les bons endroits dans le code :

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

Tâche Rechercher les fonctions vulnérables utilisées dans les bibliothèques Open-Source

solution: De nombreuses entreprises utilisent des outils de surveillance Open Source (pratique OSA) pour détecter l'utilisation de versions vulnérables des bibliothèques dans les applications développées. Parfois, il n'est pas possible de mettre à jour une telle bibliothèque vers une version sécurisée. Dans certains cas, il existe des limitations fonctionnelles, dans d’autres, il n’existe aucune version sécurisée. Dans ce cas, une combinaison de pratiques SAST et OSA permettra de déterminer que les fonctions qui conduisent à l'exploitation de la vulnérabilité ne sont pas utilisées dans le code.

Mais parfois, surtout si l’on considère JavaScript, cela peut ne pas être une tâche totalement triviale. Ci-dessous une solution, peut-être pas idéale, mais néanmoins efficace, en prenant l'exemple des vulnérabilités du composant lodash dans les méthodes template и *set.

Exemples de test de code potentiellement vulnérable dans un fichier JS :

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

Et en se connectant directement en 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>

Nous recherchons toutes nos méthodes vulnérables, qui sont répertoriées dans vulnérabilités :

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

Tâche Recherche de certificats intégrés à l'application

solution: Il n'est pas rare que des applications, notamment mobiles, utilisent des certificats ou des clés pour accéder à divers serveurs ou vérifier le SSL-Pinning. Du point de vue de la sécurité, stocker de telles choses dans le code n'est pas la meilleure pratique. Essayons d'écrire une règle qui recherchera des fichiers similaires dans le référentiel :

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

Tâche Trouver des jetons compromis dans l'application

solution: Il est souvent nécessaire de révoquer les jetons compromis ou d'autres informations importantes présentes dans le code. Bien sûr, les stocker dans le code source n’est pas une bonne idée, mais les situations varient. Grâce aux requêtes CxQL, trouver des choses comme celle-ci est assez simple :

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

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

Conclusion

J'espère que cet article sera utile à ceux qui commencent à se familiariser avec l'outil Checkmarx. Peut-être que ceux qui écrivent leurs propres règles depuis longtemps trouveront également quelque chose d'utile dans ce guide.

Malheureusement, il manque actuellement une ressource où de nouvelles idées pourraient être glanées lors du développement des règles pour Checkmarx. C'est pourquoi nous avons créé dépôt sur Github, où nous publierons notre travail afin que tous ceux qui utilisent CxQL puissent y trouver quelque chose d'utile et avoir également la possibilité de partager leur travail avec la communauté. Le référentiel est en train de remplir et de structurer le contenu, les contributeurs sont donc les bienvenus !

Je vous remercie!

Source: habr.com

Ajouter un commentaire