¡Hola, Habr!
En nuestro trabajo, nuestra empresa se enfrenta muy a menudo a diversas herramientas de análisis estático de código (SAST). Por defecto, todas funcionan de manera promedio. Por supuesto, todo depende del proyecto y de las tecnologías utilizadas, así como de qué tan bien estas tecnologías están cubiertas por las reglas de análisis. En mi opinión, uno de los criterios más importantes al elegir una herramienta SAST es la posibilidad de ajustarla a las características de nuestras aplicaciones, es decir, poder escribir y modificar las reglas de análisis o, como se les llama con más frecuencia, Consultas Personalizadas.

Nosotros solemos utilizar Checkmarx, un analizador de código muy interesante y potente. En este artículo, compartiré mi experiencia en la redacción de reglas de análisis para esta herramienta.
Tabla de contenido
Introducción
Para comenzar, me gustaría recomendar uno de los pocos artículos en ruso sobre las particularidades de escribir consultas para Checkmarx. Se publicó en Habr a finales de 2019 con el título: .
En él se detalla cómo redactar las primeras consultas en el lenguaje CxQL (Checkmarx Query Language) para una aplicación de prueba y se muestran los principios básicos de funcionamiento de las reglas de análisis.
No repetiré lo que se describe en él, aunque algunas coincidencias estarán presentes. En mi artículo, intentaré elaborar un “recetario” con una lista de soluciones a problemas específicos que he enfrentado durante mi trabajo con Checkmarx. Para muchas de estas tareas tuve que romperme la cabeza. A veces faltaba información en la documentación, y otras veces era difícil entender cómo hacer lo que se requería. Espero que mi experiencia y noches sin dormir no sean en vano y que este “recetario de Consultas Personalizadas” te ahorre algunas horas o un par de neuronas. ¡Así que comencemos!
Información general sobre las reglas
Comencemos revisando algunos conceptos básicos y el proceso de trabajo con las reglas, para tener una mejor comprensión de lo que sucederá a continuación. También porque en la documentación no se menciona esto o está muy disperso en la estructura, lo que no es muy conveniente.
Las reglas se aplican durante el escaneo según el preset seleccionado al inicio (un conjunto de reglas activas). Se pueden crear un número ilimitado de presets y cómo estructurarlos depende de las características de su proceso. Se pueden agrupar por idiomas o destacar presets para cada proyecto. La cantidad de reglas activas afecta la velocidad y precisión del escaneo.
Configuración del Preset en la interfaz de CheckmarxLas reglas se editan en una herramienta especial llamada CxAuditor. Esta es una aplicación de escritorio que se conecta al servidor de Checkmarx. Esta herramienta tiene dos modos de trabajo: edición de reglas y análisis de los resultados de un escaneo ya realizado.
Interfaz de CxAuditLas reglas en Checkmarx se dividen por idiomas, es decir, para cada idioma existe su propio conjunto de consultas. También hay algunas reglas generales que se aplican independientemente del idioma, estas son las llamadas consultas básicas. En su mayoría, las consultas básicas contienen la búsqueda de información que utilizan otras reglas.
División de las reglas por idiomasLas reglas pueden ser "Executable" y "Non-Executable" (Ejecutables y No Ejecutables). No es un nombre del todo correcto, en mi opinión, pero así es. La esencia es que el resultado de la ejecución de las reglas "Executable" se mostrará en los resultados del escaneo en la interfaz, mientras que las reglas "Non-Executable" solo son necesarias para usar sus resultados en otras consultas (en esencia, solo una función).
Definición del tipo de regla al crearSe pueden crear nuevas reglas o complementar/reescribir las existentes. Para reescribir una regla, es necesario encontrarla en el árbol, hacer clic derecho y en el menú desplegable seleccionar el elemento “Override”. Aquí es importante recordar que las nuevas reglas inicialmente no están incluidas en los presets y no están activas. Para comenzar a usarlas, es necesario activarlas en el menú “Preset Manager” de la herramienta. Las reglas reescritas mantienen sus configuraciones, es decir, si la regla estaba activa, así permanecerá y se aplicará de inmediato.
Ejemplo de una nueva regla en la interfaz del Administrador de PreajustesDurante la ejecución, se construye un “árbol” de consultas, donde cada una depende de otra. Primero se ejecutan las reglas que recogen información, y luego las que la utilizan. El resultado de la ejecución se almacena en caché, por lo que si es posible utilizar los resultados de una regla existente, es mejor hacerlo, ya que esto reducirá el tiempo de escaneo.
Las reglas se pueden aplicar a diferentes niveles:
Para todo el sistema — se utilizará para cualquier escaneo de cualquier proyecto
A nivel de equipo — se aplicará solo para el escaneo de proyectos en el equipo seleccionado.
A nivel de proyecto — se aplicará en un proyecto específico
Definición del nivel en el que se aplicará la regla
“Diccionario“ para principiantes
Y comenzaré con algunas cosas que me han planteado preguntas, así como mostraré una serie de trucos que simplificarán notablemente la vida.
Operaciones con listas
- resta de uno a otro (list2 - list1)
* intersección de listas (list1 * list2)
+ suma de listas (list1 + list2)
& (lógico Y) - combina listas por coincidencia (list1 & list2), similar a la intersección (list1 * list2)
| (lógico O) - combina listas por búsqueda amplia (list1 | list2)
No funciona con listas: ^ && || % \/ Todos los elementos encontrados
Dentro del idioma escaneado, se puede obtener una lista de todos los elementos que Checkmarx ha definido (cadenas, funciones, clases, métodos, etc.). Este es un espacio de objetos, al que se puede acceder a través de All. Es decir, para buscar un objeto con un nombre específico searchMe, se puede buscar, por ejemplo, por nombre en todos los objetos encontrados:
// Такой запрос выдаст все элементы
result = All;
// Такой запрос выдаст все элементы, в имени которых присутствует “searchMe“
result = All.FindByName("searchMe");Sin embargo, si se necesita buscar en otro idioma que por alguna razón no se incluyó en el escaneo (por ejemplo, groovy en un proyecto de Android), se puede expandir nuestro espacio de objetos a través de la variable:
result = AllMembers.All.FindByName("searchMe");Funciones para analizar Flow
Estas funciones se utilizan en muchas reglas y aquí hay una pequeña guía sobre lo que significan:
// Какие данные second влияют на first.
// Другими словами - ТО (second) что влияет на МЕНЯ (first).
result = first.DataInfluencedBy(second);
// Какие данные first влияют на second.
// Другими словами - Я (first) влияю на ТО (second).
result = first.DataInfluencingOn(second);Obtener el nombre/ruta del archivo
Hay varios atributos que se pueden obtener de los resultados de la ejecución de la consulta (nombre del archivo en el que se encontró la coincidencia, línea, etc.), pero cómo obtenerlos y usarlos no se menciona en la documentación. Así que, para hacer esto, es necesario acceder a la propiedad LinePragma y dentro de ella estarán los objetos que necesitamos:
// Для примера найдем все методы
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 importante tener en cuenta que FileName realmente contiene la ruta al archivo, ya que utilizamos el método GetFirstGraph.
Resultado de la ejecución
Dentro de CxQL hay una variable especial resultado, que devuelve el resultado de la ejecución de la regla que escribiste. Se inicializa de inmediato y se puede escribir en ella resultados intermedios, modificándolos y aclarándolos en el transcurso del trabajo. Sin embargo, si dentro de la regla no hay asignación a esta variable o función return— el resultado de la ejecución siempre será cero.
La siguiente consulta no nos devolverá nada en el resultado de la ejecución y siempre estará vacía:
// Находим элементы foo
CxList libraries = All.FindByName("foo");Pero, al asignar el resultado de la ejecución a la variable mágica result, veremos qué nos devuelve esta llamada:
// Находим элементы foo
CxList libraries = All.FindByName("foo");
// Выводим, как результат выполнения правила
result = libraries
// Или еще короче
result = All.FindByName("foo");Uso de los resultados de la ejecución de otras reglas
Las reglas en Checkmarx pueden considerarse análogas a las funciones en un lenguaje de programación convencional. Al escribir una regla, puedes utilizar los resultados de otras consultas. Por ejemplo, no es necesario buscar cada vez todas las llamadas a métodos en el código, sino que basta con invocar la regla necesaria:
// Получаем результат выполнения другого правила
CxList methods = Find_Methods();
// Ищем внутри метод foo.
// Второй параметр false означает, что ищем без чувствительности к регистру
result = methods.FindByShortName("foo", false);Este enfoque permite reducir el código y disminuir significativamente el tiempo de ejecución de la regla.
Resolución de problemas
Registro
Al trabajar con la herramienta, a veces no se puede escribir inmediatamente la consulta necesaria y hay que experimentar, probando diferentes variantes. Para este caso, la herramienta ofrece un registro que se invoca de la siguiente manera:
// Находим что-то
CxList toLog = All.FindByShortName("log");
// Формируем строку и отправляем в лог
cxLog.WriteDebugMessage (“number of DOM elements =” + All.Count);Pero hay que recordar que este método solo acepta una cadena, por lo que no será posible mostrar la lista completa de elementos encontrados como resultado de la primera operación. La segunda opción, que se utiliza para depuración, es asignar de vez en cuando a la variable mágica resultado el resultado de la consulta y ver qué se obtiene. Este enfoque no es muy conveniente, hay que asegurarse de que no haya una redefinición o operaciones con esto en el código después. resultado o simplemente comentar el código que se encuentra más abajo. O, como yo, olvidar eliminar varias de estas llamadas de la regla final y preguntarse por qué nada funciona.
Una forma más cómoda es invocar el método return con el parámetro necesario. En ese caso, la ejecución de la regla terminará y podremos ver qué hemos obtenido como resultado de lo que escribimos:
// Находим что-то
CxList toLog = All.FindByShortName("log");
// Выводим результат выполнения
return toLog
//Все, что написано дальше не будет выполнено
result = All.DataInfluencedBy(toLog)Problema con el inicio de sesión
Hay situaciones en las que no se puede acceder a la herramienta CxAudit (que se utiliza para escribir reglas). Las razones pueden ser muchas: un cierre inesperado, una actualización repentina de Windows, BSOD y otras situaciones imprevistas que no podemos controlar. En tal caso, a veces queda una sesión no cerrada en la base de datos, lo que impide volver a ingresar. Para solucionar esto, es necesario realizar varias consultas:
Para Checkmarx hasta la 8.6:
// Проверяем, что есть залогиненые пользователи, выполнив запрос в БД
SELECT COUNT(*) FROM [CxDB].[dbo].LoggedinUser WHERE [ClientType] = 6;
// Если что-то есть, а на самом деле даже если и нет, попробовать выполнить запрос
DELETE FROM [CxDB].[dbo].LoggedinUser WHERE [ClientType] = 6;
Para Checkmarx después de la 8.6:
// Проверяем, что есть залогиненые пользователи, выполнив запрос в БД
SELECT COUNT(*) FROM LoggedinUser WHERE (ClientType = 'Audit');
// Если что-то есть, а на самом деле даже если и нет, попробовать выполнить запрос
DELETE FROM [CxDB].[dbo].LoggedinUser WHERE (ClientType = 'Audit');Redacción de reglas
Aquí llegamos a lo más interesante. Cuando comienzas a escribir reglas en CxQL, a menudo careces no solo de la documentación, sino también de ejemplos prácticos de cómo resolver ciertas tareas y de una descripción del proceso de trabajo de las consultas en general.
Intentaré simplificar un poco la vida a quienes comienzan a adentrarse en el lenguaje de consultas y proporcionaré algunos ejemplos de uso de Custom Queries para resolver tareas específicas. Algunos de ellos son bastante generales y pueden aplicarse en tu empresa prácticamente sin cambios, otros son más específicos, pero también se pueden utilizar adaptando el código a las particularidades de tus aplicaciones.
Así que aquí están las tareas con las que nos hemos encontrado con mayor frecuencia:
Tarea: En los resultados de ejecución de la regla hay varios Flow y uno de ellos es una inclusión del otro, es necesario dejar solo uno.
Solución: En efecto, a veces Checkmarx muestra varios Flows de movimiento de datos que pueden cruzarse y ser una versión abreviada de otros. Para tales casos existe un método especial. ReduceFlow. Dependiendo del parámetro, elegirá el Flow más corto o el más largo:
// Оставить только длинные Flow
result = result.ReduceFlow(CxList.ReduceFlowType.ReduceSmallFlow);
// Оставить только короткие Flow
result = result.ReduceFlow(CxList.ReduceFlowType.ReduceBigFlow);Tarea: Ampliar la lista de datos sensibles a los que responde la herramienta.
Solución: En Checkmarx existen reglas básicas, cuyo resultado es utilizado por muchas otras consultas. Al complementar algunas de estas reglas con datos específicos para tu aplicación, se pueden mejorar inmediatamente los resultados del escaneo. A continuación, un ejemplo de regla con la que se puede comenzar:
General_privacy_violation_list
Agregaremos algunas variables que se utilizan en nuestra aplicación para almacenar información sensible:
// Получаем результат выполнения базового правила
result = base.General_privacy_violation_list();
// Ищем элементы, которые попадают под простые регулярные выражения. Можно дополнить характерными для вас паттернами.
CxList personalList = All.FindByShortNames(new List<string> {
"*securityToken*", "*sessionId*"}, false);
// Добавляем к конечному результату
result.Add(personalList);Tarea: Ampliar la lista de variables con contraseñas
Solución: Recomendaría prestar atención a la regla básica para definir contraseñas en el código y agregarle una lista de nombres de variables que se utilizan comúnmente en su empresa.
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*"));
// Complementamos la lista predeterminada de variables
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);
}
}Tarea: Agregar los frameworks utilizados que no son compatibles con Checkmarx
Solución: Todas las consultas en Checkmarx se dividen por lenguajes, así que es necesario completar las reglas para cada uno. A continuación, algunos ejemplos de dichas reglas.
Si se utilizan bibliotecas que complementan o reemplazan la funcionalidad estándar, es fácil agregarlas a la regla básica. De esta manera, todos los que las utilicen se enterarán de las nuevas consideraciones de inmediato. Por ejemplo, bibliotecas para el registro en Android, como Timber y Loggi. En la entrega básica, no hay reglas para detectar llamadas no sistemáticas, así que si la contraseña o el identificador de sesión se registra, no nos enteraremos de ello. Intentaremos agregar a las reglas de Checkmarx la detección de tales métodos.
Ejemplo de código de prueba que utiliza la biblioteca Timber para el registro:
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");
}
}Aquí hay un ejemplo de una solicitud para Checkmarx que permitirá agregar la definición de las llamadas a los métodos Timber como un punto de salida de datos desde la aplicación:
FindAndroidOutputs
// Получаем результат выполнения базового правила
result = base.Find_Android_Outputs();
// Дополняем вызовами, которые приходят из библиотеки Timber
CxList timber = All.FindByExactMemberAccess("Timber.*") +
All.FindByShortName("Timber").GetMembersOfTarget();
// Добавляем к конечному результату
result.Add(timber);Además, se puede complementar la regla vecina, pero ya relacionada directamente con el registro en Android:
FindAndroidLog_Outputs
// Получаем результат выполнения базового правила
result = base.Find_Android_Log_Outputs();
// Дополняем вызовами, которые приходят из библиотеки Timber
result.Add(
All.FindByExactMemberAccess("Timber.*") +
All.FindByShortName("Timber").GetMembersOfTarget()
);También, si en aplicaciones Android se utiliza para trabajo asíncrono, sería bueno informar adicionalmente a Checkmarx agregando el método para obtener datos de la tarea getInputData:
FindAndroidRead
// Получаем результат выполнения базового правила
result = base.Find_Android_Read();
// Дополняем вызовом функции getInputData, которая используется в WorkManager
CxList getInputData = All.FindByShortName("getInputData");
// Добавляем к конечному результату
result.Add(getInputData.GetMembersOfTarget());Tarea: Búsqueda de datos sensibles en plist para proyectos de iOS
Solución: A menudo, para almacenar diversas variables y valores en iOS se utilizan archivos especiales con la extensión .plist. No se recomienda almacenar contraseñas, tokens, claves y otros datos sensibles en estos archivos, ya que pueden ser extraídos fácilmente del dispositivo.
Los archivos plist tienen características que no son obvias a simple vista, pero son importantes para Checkmarx. Escribiremos una regla que buscará los datos que necesitamos y nos informará si se mencionan contraseñas o tokens en alguna parte.
Ejemplo de tal archivo, en el que está incrustado un token para comunicarse con el servicio backend:
DeviceDictionary
phone
iPhone 6s
privatekey
MIICXAIBAAKBgQCqGKukO1De7zhZj6+Y la regla para Checkmarx que tiene varios matices que se deben tener en cuenta al escribir:
// Используем результат выполнения правила по поиску файлов 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);Tarea: Búsqueda de información en XML
Solución: En Checkmarx hay funciones muy convenientes para trabajar con XML y buscar valores, etiquetas, atributos y más. Pero en la documentación, desafortunadamente, hay un error que hace que ningún ejemplo funcione. A pesar de que en la última versión de la documentación se ha corregido este error, tengan cuidado si utilizan versiones anteriores de los documentos.
Aquí hay un ejemplo incorrecto de la documentación:
// Код работать не будет
result = All.FindXmlAttributesByNameAndValue("*.app", 8, “id”, "error- section", false, true);Como resultado del intento de ejecución, obtendremos un error que nos All no existe tal método… Y esto es cierto, ya que para usar funciones que trabajen con XML hay un espacio de objetos especial y separado — cxXPath. Así es como se ve la consulta correcta para buscar la configuración en Android que permite el uso de tráfico HTTP:
// Правильный вариант с использованием cxXPath
result = cxXPath.FindXmlAttributesByNameAndValue("*.xml", 8, "cleartextTrafficPermitted", "true", false, true);Analicemos un poco más, ya que la sintaxis de todas las funciones es similar; después de entender una, solo hay que elegir la adecuada. Así que, paso a paso por los parámetros:
"*.xml"— la máscara de archivos sobre la que se debe realizar la búsqueda8— id del idioma al que se aplica la regla"cleartextTrafficPermitted"— el nombre del atributo en xml"true"— el valor de este atributofalse— el uso de expresiones regulares durante la búsquedatrue— significa que la búsqueda se realizará sin distinción de mayúsculas y minúsculas, es decir, case-insensitive
Por ejemplo se usó una regla que define configuraciones de conexión de red en Android que son inseguras, permitiendo la comunicación con el servidor mediante el protocolo HTTP. Un ejemplo de configuración que contiene el atributo cleartextTrafficPermitted con el valor true:
example.com
secure.example.comTarea: Limitar resultados por nombre/ruta de archivo
Solución: En uno de los grandes proyectos relacionados con el desarrollo de una aplicación móvil para Android, nos enfrentamos a falsos positivos de la regla que define la configuración de ofuscación. La cuestión es que la regla por defecto busca en el archivo build.gradle la configuración que se encarga de aplicar las reglas de ofuscación para la versión de lanzamiento de la aplicación.
Pero en proyectos grandes, a veces hay archivos secundarios build.gradle, que pertenecen a bibliotecas incluidas en el proyecto. La particularidad es que, incluso si estos archivos no indican la necesidad de ofuscación, al compilar se aplicarán las configuraciones del archivo de construcción principal.
Así, la tarea es filtrar las activaciones en los archivos secundarios que pertenecen a bibliotecas. Se puede identificar por la presencia de la línea apply 'com.android.library'.
Ejemplo de código del archivo build.gradle, que define la necesidad de ofuscación:
aplicar el complemento: 'com.android.application'
android {
compileSdkVersion 24
buildToolsVersion "24.0.2"
defaultConfig {
...
}
buildTypes {
release {
minifyEnabled true
...
}
}
}
dependencias {
...
}Ejemplo de archivo build.gradle para una biblioteca incluida en el proyecto y que no posee tal configuración:
aplicar el complemento: 'android-library'
dependencias {
compile 'com.android.support:support-v4:18.0.+'
}
android {
compileSdkVersion 14
buildToolsVersion '17.0.0'
...
}Y la regla para 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);
}
}
}Este enfoque puede ser lo suficientemente universal y útil no solo para aplicaciones de Android, sino también para otros casos en los que es necesario determinar la pertenencia del resultado a un archivo específico.
Tarea: Agregar soporte para una biblioteca externa si la sintaxis no está soportada completamente
Solución: La cantidad de diversos frameworks que se utilizan en el proceso de escritura de código es abrumadora. Por supuesto, Checkmarx no siempre conoce su existencia y nuestra tarea es enseñarle a entender que ciertos métodos pertenecen a este framework específico. A veces, esto se complica porque los frameworks utilizan nombres de funciones que son muy comunes y no se puede determinar de manera inequívoca la relación de una llamada particular con una biblioteca específica.
La dificultad radica en que la sintaxis de tales bibliotecas no siempre se reconoce correctamente y hay que experimentar para evitar una gran cantidad de falsos positivos. Existen varias opciones para mejorar la precisión del escaneo y resolver el desafío planteado:
La primera opción, sabemos con certeza que la biblioteca se usa en un proyecto específico y podemos aplicar la regla a nivel de equipo. Pero si el equipo decide usar otro enfoque o utiliza múltiples bibliotecas con nombres de funciones que se cruzan, podemos enfrentar un panorama poco agradable debido a numerosas falsas alarmas.
La segunda opción, aplicar búsquedas en los archivos donde se importa claramente la biblioteca. Con este enfoque, podemos estar seguros de que en dicho archivo se aplica la biblioteca que necesitamos.
Y la tercera opción es combinar los dos enfoques anteriores.
Como ejemplo, analizaremos una biblioteca conocida en círculos reducidos para el lenguaje de programación Scala, específicamente, la funcionalidad . En general, para pasar parámetros a una consulta SQL, es necesario utilizar el operador $, que inserta datos en una consulta SQL predefinida. Es decir, es prácticamente un análogo directo a Prepared Statement en Java. Sin embargo, en caso de necesitar construir dinámicamente una consulta SQL, por ejemplo, si es necesario pasar los nombres de las tablas, se puede usar el operador #$, que insertará directamente los datos en la consulta (prácticamente como la concatenación de cadenas).
Ejemplo de código:
// В общем случае - значения, контролируемые пользователем
val table = "coffees"
sql"select * from #$table where name = $name".as[Coffee].headOptionCheckmarx aún no sabe detectar el uso de Splicing Literal Values y pasa por alto los operadores #$, así que intentaremos enseñarle a identificar potenciales inyecciones SQL y resaltar las partes relevantes en el código:
// Находим все импорты
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));
}Tarea: Búsqueda de funciones vulnerables utilizadas en bibliotecas de código abierto
Solución: Muchas empresas utilizan herramientas para controlar el código abierto (práctica OSA), que permiten detectar el uso de versiones vulnerables de bibliotecas en las aplicaciones en desarrollo. A veces, no es posible actualizar una biblioteca a una versión segura. En algunos casos hay limitaciones funcionales, y en otros, simplemente no existe una versión segura. En tales situaciones, puede ayudar la combinación de prácticas SAST y OSA, que permite determinar que las funciones que conducen a la explotación de vulnerabilidades no se utilizan en el código.
Pero a veces, especialmente si consideramos JavaScript, esto puede no ser una tarea trivial. A continuación se presenta una solución, quizás no ideal, pero que funciona, a partir de las vulnerabilidades en el componente lodash en los métodos template y *set.
Ejemplos de código potencialmente vulnerable en un archivo 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!'Y al conectarse directamente en HTML:
<!DOCTYPE html>
<html>
<head>
<title>Tutorial de Lodash</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>Buscamos todos nuestros métodos vulnerables, que están enumerados en las vulnerabilidades:
// Ищем все строки: в которых встречается строка 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));Tarea: Búsqueda de certificados incrustados en la aplicación
Solución: A menudo, las aplicaciones, especialmente las móviles, utilizan certificados o claves para acceder a diversos servidores o para verificar SSL-Pinning. Desde el punto de vista de la seguridad, almacenar estas cosas en el código no es la mejor práctica. Intentaremos escribir una regla que busque archivos similares en el repositorio:
// Найдем все сертификаты по маске файла
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;Tarea: Búsqueda de tokens comprometidos en la aplicación
Solución: A menudo es necesario revocar tokens comprometidos u otra información importante que esté presente en el código. Por supuesto, almacenarlos dentro del código fuente no es la mejor idea, pero hay diferentes situaciones. Gracias a las consultas CxQL, encontrar tales elementos es bastante sencillo:
// Получаем все строки, которые содержатся в коде
CxList strings = base.Find_Strings();
// Ищем среди всех строк нужное нам значение. В примере токен в виде строки "qwerty12345"
result = strings.FindByShortName("qwerty12345");Conclusión
Espero que este artículo sea útil para aquellos que comienzan a familiarizarse con la herramienta Checkmarx. Puede que también aquellos que ya llevan tiempo escribiendo sus propias reglas encuentren algo valioso en esta guía.
Desafortunadamente, en este momento falta un recurso donde se puedan obtener nuevas ideas durante el desarrollo de reglas para Checkmarx. Por eso creamos , donde publicaremos nuestros avances para que cada persona que utilice CxQL pueda encontrar algo útil en él, así como tener la oportunidad de compartir sus trabajos con la comunidad. El repositorio está en proceso de llenado y estructuración de contenido, ¡así que los colaboradores son bienvenidos!
¡Gracias por su atención!
Fuente: habr.com

Configuración del Preset en la interfaz de Checkmarx
Interfaz de CxAudit
División de las reglas por idiomas
Definición del tipo de regla al crear
Ejemplo de una nueva regla en la interfaz del Administrador de Preajustes
Definición del nivel en el que se aplicará la regla