How to write rules for Checkmarx and not go crazy

Hey Habr!

In our work, our company very often deals with various static code analysis (SAST) tools. Out of the box, they all work average. Of course, it all depends on the project and the technologies used in it, as well as how well these technologies are covered by the rules of analysis. In my opinion, one of the most important criteria when choosing a SAST tool is the ability to customize it for the specifics of your applications, namely, write and modify analysis rules or, as they are more commonly called, Custom Queries.

How to write rules for Checkmarx and not go crazy

We most often use Checkmarx, a very interesting and powerful code analyzer. In this article, I will talk about my experience in writing analysis rules for it.

Table of contents

Entry

To begin with, I would like to recommend one of the few articles in Russian about the features of writing queries for Checkmarx. It was published on Habré at the end of 2019 under the heading: Hello, Checkmarx! How to write a Checkmarx SAST query and find cool vulnerabilities.

It details how to write the first queries in CxQL (Checkmarx Query Language) for some test application and shows the basic principles of the analysis rules.

I will not repeat what is described in it, although some intersections will still be present. In my article, I will try to compile some “recipe collection”, a list of solutions to specific problems that I have encountered during my work with Checkmarx. Over many of these tasks I had to rack my brains pretty much. Sometimes there was not enough data in the documentation, and sometimes it was generally difficult to understand how to do what was required. I hope my experience and sleepless nights will not go to waste, and this “collection of Custom Queries recipes” will save you a few hours or a couple of nerve cells. So, let's begin!

General information on the rules

First, let's look at a few basic concepts and the process of working with the rules, for a better understanding of what will happen next. And also because the documentation does not say about it or is very smeared over the structure, which is not very convenient.

  1. The rules are applied during scanning depending on the preset selected at the start (a set of active rules). You can create an unlimited number of presets and how exactly to structure them depends on the characteristics of your process. You can group them by language or select presets for each project. The number of active rules affects the speed and accuracy of the scan.

    How to write rules for Checkmarx and not go crazyPreset setting in Checkmarx interface

  2. Rules are edited in a special tool called CxAuditor. This is a desktop application that connects to the server with Checkmarx. This tool has two modes of operation: editing rules and analyzing the results of an already performed scan.

    How to write rules for Checkmarx and not go crazyCxAudit Interface

  3. The rules in Checkmarx are divided by language, that is, each language has its own set of queries. There are also some general rules that apply regardless of the language, these are the so-called basic queries. For the most part, basic queries contain a search for information that other rules use.

    How to write rules for Checkmarx and not go crazySeparation of rules by language

  4. Rules are "Executable" and "Non-Executable" (Executed and Not executed). Not quite the correct name, in my opinion, but there it is. The bottom line is that the result of executing “Executable” rules will be displayed in the scan results in the UI, and “Non-Executable” rules are needed only to use their results in other queries (in fact, just a function).

    How to write rules for Checkmarx and not go crazyDetermining the rule type at creation

  5. You can create new rules or add/rewrite existing ones. In order to rewrite a rule, you need to find it in the tree, right-click and select “Override” from the drop-down menu. It is important to remember here that the new rules are not initially included in the presets and are not active. To start using them, you need to activate them in the “Preset Manager” menu in the instrument. The rewritten rules retain their settings, that is, if the rule was active, it will remain so and will be applied immediately.

    How to write rules for Checkmarx and not go crazyAn example of a new rule in the Preset Manager interface

  6. During execution, a “tree” of requests is built, which depends on what. Rules that collect information are executed first, those who use it are second. The result of the execution is cached, so if it is possible to use the results of an existing rule, then it is better to do so, this will reduce the scan time.

  7. Rules can be applied at various levels:

  • System wide - will be used for any scan of any project

  • At the team level (Team) - will only be used to scan projects in the selected team.

  • At the project level - Will be applied in a specific project

    How to write rules for Checkmarx and not go crazyDetermine the level at which the rule will apply

"Vocabulary" for beginners

And I will start with a few things that caused me questions, and I will also show a number of tricks that will greatly simplify life.

List operations

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

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

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

All found items

Within the scanned language, you can get a list of absolutely all the elements that Checkmarx has defined (strings, functions, classes, methods, etc.). This is some space of objects, which can be accessed through All. That is, to search for an object with a specific name searchMe, you can search, for example, by name for all found objects:

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

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

But, if you need to search in another language that for some reason was not included in the scan (for example, groovy in an Android project), you can expand our object space through a variable:

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

Functions for Flow Analysis

These functions are used in many rules and here is a little cheat sheet what they mean:

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

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

Getting file name/path

There are several attributes that can be obtained from the results of the query (the filename in which the entry was found, the string, etc.), but how to get and use them in the documentation is not said. So, in order to do this, you need to refer to the LinePragma property and the objects we need will already be inside it:

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

It is worth bearing in mind that FileName actually contains the path to the file since we used the method GetFirstGraph.

Execution result

There is a special variable inside CxQL result, which returns the result of executing your written rule. It is initialized immediately and you can write intermediate results into it, changing and refining them in the process. But, if there is no assignment to this variable or function inside the rule return- the result of the execution will always be zero.

The following query will not return anything to us as a result of execution and will always be empty:

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

But, having assigned the result of execution to the magic variable result, we will see what this call returns to us:

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

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

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

Using the Results of Executing Other Rules

Rules in Checkmarx can be called an analogue of functions in a conventional programming language. When writing a rule, you may well use the results of other queries. For example, there is no need to search for all method calls in the code each time, just call the desired rule:

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

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

This approach allows you to shorten the code and significantly reduce the execution time of the rule.

Solving Problems

Logging

When working with the tool, sometimes it is not possible to immediately write the desired query and you have to experiment, trying different options. For such a case, the tool provides logging, which is called as follows:

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

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

But it is worth remembering that this method accepts only string, so it will not be possible to display the full list of found elements as a result of the first operation. The second option, which is used for debugging, is to assign the magic variable from time to time result the result of the query and see what happens. This approach is not very convenient, you need to be sure that there are no redefinitions or operations with this in the code after result or just comment the code below. Or, like me, you can forget to remove several such calls from the finished rule and wonder why nothing works.

A more convenient way is to call the method return with the desired setting. In this case, the execution of the rule will end and we will be able to see what happened as a result of what we wrote:

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

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

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

Login problem

There are situations when it is not possible to enter the CxAudit tool (which is used to write rules). There can be many reasons for this, a crash, a sudden Windows update, BSOD and other unforeseen situations that are beyond our control. In this case, sometimes there is an incomplete session in the database, which does not allow you to log in again. To fix it, you need to run several queries:

For Checkmarx up to 8.6:

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

For Checkmarx after 8.6:

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

Writing the rules

So we got to the most interesting. When you start writing rules in CxQL, more often than not there is not enough documentation, but some living examples of solving certain problems and a description of the query workflow in general.

I will try to make life a little easier for those who are starting to dive into the query language and give some examples of using Custom Queries to solve certain problems. Some of them are quite general and can be applied in your company with almost no changes, others are more specific, but they can also be used by changing the code to suit the specifics of your applications.

So, here are the tasks we had to meet most often:

Problem: There are several Flows in the results of the rule execution and one of them is an attachment of another, you must leave one of them.

Decision: Indeed, sometimes Checkmarx shows multiple Flows of data movement, which may overlap and be a shortened version of others. For such cases, there is a special method ReduceFlow. Depending on the parameter, it will select the shortest or longest Flow:

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

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

Problem: Expand the list of sensitive data that the tool reacts to

Decision: Checkmarx has basic rules, the result of which is used by many other queries. By supplementing some of these rules with data specific to your application, you can immediately improve scan results. Below is an example rule to start with:

General_privacy_violation_list

Let's add a few variables that are used in our application to store sensitive information:

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

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

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

Problem: Expand the list of variables with passwords

Decision: I would recommend that you immediately pay attention to the basic rule for determining passwords in the code and add to it a list of variable names that are commonly used in your company.

Password_privacy_violation_list

CxList allStrings = All.FindByType("String"); 
allStrings.Add(All.FindByType(typeof(StringLiteral))); 
allStrings.Add(Find_UnknownReference());
allStrings.Add(All.FindByType(typeof (Declarator)));
allStrings.Add(All.FindByType(typeof (MemberAccess)));
allStrings.Add(All.FindByType(typeof(EnumMemberDecl))); 
allStrings.Add(Find_Methods().FindByShortName("get*"));

// Дополняем дефолтный список переменных
List < string > pswdIncludeList = new List<string>{"*password*", "*psw", "psw*", "pwd*", "*pwd", "*authKey*", "pass*", "cipher*", "*cipher", "pass", "adgangskode", "benutzerkennwort", "chiffre", "clave", "codewort", "contrasena", "contrasenya", "geheimcode", "geslo", "heslo", "jelszo", "kennwort", "losenord", "losung", "losungswort", "lozinka", "modpas", "motdepasse", "parol", "parola", "parole", "pasahitza", "pasfhocal", "passe", "passord", "passwort", "pasvorto", "paswoord", "salasana", "schluessel", "schluesselwort", "senha", "sifre", "wachtwoord", "wagwoord", "watchword", "zugangswort", "PAROLACHIAVE", "PAROLA CHIAVE", "PAROLECHIAVI", "PAROLE CHIAVI", "paroladordine", "verschluesselt", "sisma",
                "pincode",
								"pin"};
								
List < string > pswdExcludeList = new List<string>{"*pass", "*passable*", "*passage*", "*passenger*", "*passer*", "*passing*", "*passion*", "*passive*", "*passover*", "*passport*", "*passed*", "*compass*", "*bypass*", "pass-through", "passthru", "passthrough", "passbytes", "passcount", "passratio"};

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

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

Problem: Add used frameworks that are not supported by Checkmarx

Decision: All requests in Checkmarx are divided by language, so you need to supplement the rules for each language. Below are some examples of such rules.

If libraries are used that supplement or replace the standard functionality, they can be easily added to the base rule. Then everyone who uses it will immediately find out about the new input. As an example, Android logging libraries are Timber and Loggi. There are no rules for determining non-system calls in the base distribution, so if the password or session ID gets into the log, we will not know about it. Let's try to add definitions of such methods to the Checkmarx rules.

A test code example that uses the Timber library for logging:

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

And here is an example query for Checkmarx, which will allow you to add a definition for calling Timber methods as an exit point for data from the application:

FindAndroidOutputs

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

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

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

And you can also add a neighboring rule, but already related directly to logging in Android:

FindAndroidLog_Outputs

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

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

Also, if Android applications use Work Manager for asynchronous work, it's a good idea to additionally inform Checkmarx about this by adding a method for obtaining data from the task getInputData:

FindAndroidRead

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

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

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

Problem: Finding sensitive data in plist for iOS projects

Decision: Often, special files with the .plist extension are used to store various variables and values ​​in iOS. Storing passwords, tokens, keys and other sensitive data in these files is not recommended, as they can be retrieved from the device without any problems.

plist files have features that are not obvious to the naked eye but are important to Checkmarx. Let's write a rule that will look for the data we need and tell us if passwords or tokens are mentioned somewhere.

An example of such a file, in which a token is hardwired for communicating with the backend service:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>DeviceDictionary</key>
	<dict>
		<key>phone</key>
		<string>iPhone 6s</string>
	</dict>
	<key>privatekey</key>
	<string>MIICXAIBAAKBgQCqGKukO1De7zhZj6+</string>
</dict>
</plist>

And a rule for Checkmarx, which has several nuances to consider when writing:

// Используем результат выполнения правила по поиску файлов plist, чтобы уменьшить время работы правила и 
CxList plist = Find_Plist_Elements();

// Инициализируем новую переменную
CxList dictionarySettings = All.NewCxList();

// Теперь добавим поиск всех интересующих нас значений. В дальнейшем можно расширять этот список.
// Для поиска значений, как ни странно, используется FindByMemberAccess - поиск обращений к методам. Второй параметр внутри функции, false, означает, что поиск нечувствителен к регистру
dictionarySettings.Add(plist.FindByMemberAccess("privatekey", false));
dictionarySettings.Add(plist.FindByMemberAccess("privatetoken", false));

// Для корректного поиска из-за особенностей структуры plist - нужно искать по типу "If statement"
CxList ifStatements = plist.FindByType(typeof(IfStmt));

// Добавляем в результат, перед этим получив родительский узел - для правильного отображения
result = dictionarySettings.FindByFathers(ifStatements);

Problem: Finding information in XML

Decision: Checkmarx has very handy functions for working with XML and searching for values, tags, attributes, and more. But in the documentation, unfortunately, a mistake was made due to which not a single example works. Despite the fact that the latest version of the documentation has eliminated this defect, be careful if you are using earlier versions of the documents.

Here is an incorrect example from the documentation:

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

As a result of an attempt to execute, we will receive an error that All there is no such method ... And this is true, since there is a special, separate object space for using functions to work with XML - cxXPath. Here is what the correct request looks like to find the setting in Android that allows HTTP traffic:

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

Let's take a closer look, since the syntax for all functions is similar, after you figured out one, then you just need to select the one you need. So, sequentially by parameters:

  • "*.xml"— mask of files to be searched

  • 8 — id of the language for which the rule applies

  • "cleartextTrafficPermitted"- attribute name in xml

  • "true" - value of this attribute

  • false - using a regular expression when searching

  • true - means that the search will be performed with case-insensitive, that is, case-insensitive

For example, a rule is used that defines incorrect, from a security point of view, network connection settings in Android, which allow communication with the server via the HTTP protocol. Configuration example containing an attribute cleartextTrafficPermitted with the value of true:

<network-security-config>
    <domain-config>
        <domain includeSubdomains="true">example.com</domain>
        <trust-anchors>
            <certificates src="@raw/my_ca"/>
        </trust-anchors>
        <domain-config cleartextTrafficPermitted="true">
            <domain includeSubdomains="true">secure.example.com</domain>
        </domain-config>
    </domain-config>
</network-security-config>

Problem: Restrict results by filename/path

Decision: In one of the large projects related to the development of a mobile application for Android, we encountered false positives of a rule that determines the obfuscation setting. The point is that the out-of-the-box rule looks in the file build.gradle a setting responsible for applying obfuscation rules for the release version of the application.

But in large projects, sometimes there are child files build.gradle, which refer to the libraries included in the project. The peculiarity is that even if the need for obfuscation is not indicated in these files, the settings of the parent assembly file will be applied during compilation.

Thus, the task is to cut off positives in child files that belong to libraries. It is possible to determine them by the presence of the line apply 'com.android.library'.

Example code from file build.gradle, which determines the need for obfuscation:

apply plugin: 'com.android.application'

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

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

dependencies {
  ...
}

File example build.gradle for a library included in the project and not configured like this:

apply plugin: 'android-library'

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

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

And the rule for 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);
		}
	}
}

This approach can be quite universal and useful not only for Android applications, but also for other cases when you need to determine whether the result belongs to a specific file.

Problem: Add XNUMXrd party library support if syntax is not fully supported

Decision: The number of various frameworks that are used in the process of writing code just rolls over. Of course, Checkmarx does not always know about their existence, and our task is to teach him to understand that certain methods apply specifically to this framework. Sometimes this is complicated by the fact that frameworks use the names of functions that are very common and it is impossible to unambiguously determine the relationship of a particular call to a particular library.

The difficulty lies in the fact that the syntax of such libraries is not always correctly recognized and you have to experiment in order not to get a large number of false positives. There are several options to improve the accuracy of the scan and solve the problem:

  • The first option, we know for sure that the library is used in a particular project and we can apply the rule at the team level. But in case the team decides to use a different approach or uses several libraries in which function names intersect, we can get a not very pleasant picture from numerous false positives.

  • The second option is to apply a search for files in which the library is explicitly imported. With this approach, we can be sure that the library we need is exactly used in this file.

  • And the third option is to use the above two approaches together.

As an example, let's analyze the well-known library in narrow circles slick for the Scala programming language, namely, the functional Splicing Literal Values. In general, to pass parameters to a SQL query, you must use the operator $, which substitutes data into a preformed SQL query. That is, in fact, it is a direct analogue of Prepared Statement in Java. But, if you need to dynamically construct an SQL query, for example, if you need to pass table names, you can use the operator #$, which will directly substitute the data into the query (practically, like string concatenation).

Example code:

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

Checkmarx is not yet able to detect the use of Splicing Literal Values ​​and skips statements #$, so let's try to teach it to identify potential SQL injections and highlight the right places in the 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));
}

Problem: Search for used vulnerable functions in Open-Source libraries

Decision: Many companies use Open-Source control tools (OSA practice) to detect the use of vulnerable versions of libraries in developed applications. Sometimes it is not possible to update such a library to a safe version. In some cases there are functional limitations, in others the safe version is not at all. In this case, a combination of SAST and OSA practices will help, allowing you to determine that the functions that lead to the exploitation of the vulnerability are not used in the code.

But sometimes, especially when considering JavaScript, this may not be a completely trivial task. Below is a solution, perhaps not ideal, but nevertheless working, using the example of vulnerabilities in the component lodash in methods template и *set.

Examples of potentially vulnerable test code in a JS file:

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

And when connecting directly in html:

<!DOCTYPE html>
<html>
<head>
    <title>Lodash Tutorial</title>
    <script src="./node_modules/lodash.js"></script>
    <script type="text/javascript">
  // Lodash chunking array
        nums = [1, 2, 3, 4, 5, 6, 7, 8, 9];

        let c1 = _.template('<% print("hello " + js); %>!');
        console.log(c1);

        let c2 = _.template('<% print("hello " + js); %>!');
        console.log(c2);
    </script>
</head>
<body></body>
</html>

We are looking for all our vulnerable methods, which are listed in the vulnerabilities:

// Ищем все строки: в которых встречается строка lodash (предполагаем, что это объявление импорта библиотеки
CxList lodash_strings = Find_String_Literal().FindByShortName("*lodash*");

// Ищем все данные: которые взаимодействуют с этими строками
CxList data_on_lodash = All.InfluencedBy(lodash_strings);


// Задаем список уязвимых методов
List<string> vulnerable_methods = new List<string> {"template", "*set"};

// Ищем все наши уязвимые методы, которые перечисленны в уязвимостях и отфильтровываем их только там, где они вызывались
CxList vulnerableMethods = All.FindByShortNames(vulnerable_methods).FindByType(typeof(MethodInvokeExpr));

//Находим все данные: которые взаимодействуют с данными методами
CxList vulnFlow = All.InfluencedBy(vulnerableMethods);

// Если есть пересечение по этим данным - кладем в результат
result = vulnFlow * data_on_lodash;

// Формируем список путей по которым мы уже прошли, чтобы фильтровать в дальнейшем дубли
List<string> lodash_result_path = new List<string> {};

foreach(CxList lodash_result in result)
{
    // Очередной раз получаем пути к файлам
	string file_name = lodash_result.GetFirstGraph().LinePragma.FileName;
	lodash_result_path.Add(file_name);
}

// Дальше идет часть относящаяся к html файлам, так как в них мы не можем проследить откуда именно идет вызов
// Формируем массив путей файлов, чтобы быть уверенными, что срабатывания уязвимых методов были именно в тех файлах, в которых объявлен lodash
List<string> lodash_path = new List<string> {};
foreach(CxList string_lodash in lodash_strings)
{
	string file_name = string_lodash.GetFirstGraph().LinePragma.FileName;
	lodash_path.Add(file_name);
}

// Перебираем все уязвимые методы и убеждаемся, что они вызваны в тех же файлах, что и объявление/включение lodash
foreach(CxList method in vulnerableMethods)
{
	string file_name_method = method.GetFirstGraph().LinePragma.FileName;
	if (lodash_path.Contains(file_name_method) == true && lodash_result_path.Contains(file_name_method) == false){
		result.Add(method);
	}
}

// Убираем все UknownReferences и оставляем самый "длинный" из путей, если такие встречаются
result = result.ReduceFlow(CxList.ReduceFlowType.ReduceSmallFlow) - result.FindByType(typeof(UnknownReference));

Problem: Search for certificates embedded in the application

Decision: It is not uncommon for applications, especially mobile ones, to use certificates or keys to access various servers or verify SSL-Pinning. From a security point of view, storing such things in code is not the best practice. Let's try to write a rule that will look for similar files in the repository:

// Найдем все сертификаты по маске файла
CxList find_certs = All.FindByShortNames(new List<string> {"*.der", "*.cer", "*.pem", "*.key"}, false);

// Проверим, где в приложении они используются
CxList data_used_certs = All.DataInfluencedBy(find_certs);

// И для мобильных приложений - можем поискать методы, где вызывается чтение сертификатов
// Для других платформ и приложений могут быть различные методы
CxList methods = All.FindByMemberAccess("*.getAssets");

// Пересечение множеств даст нам результат по использованию локальных сертификатов в приложении
result = methods * data_used_certs;

Problem: Search for compromised tokens in the application

Decision: It is often necessary to revoke compromised tokens or other important information that is present in the code. Of course, storing them inside the sources is not a good idea, but situations are different. Thanks to CxQL queries, finding things like this is easy enough:

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

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

Conclusion

I hope that this article will be useful for those who begin their acquaintance with the Checkmarx tool. Perhaps those who have been writing their own rules for a long time will also find something useful in this guide.

Unfortunately, there is currently a lack of a resource where one could get new ideas during the development of rules for Checkmarx. Therefore we created repository on Github, where we will post our work so that everyone who uses CxQL can find something useful in it, and also have the opportunity to share their work with the community. The repository is in the process of filling and structuring content, so contributors are welcome!

Thank you for attention!

Source: habr.com

Add a comment