Cómo escribir reglas para Checkmarx sin volverse loco

¡Hola, Habr!

En nuestro trabajo, nuestra empresa utiliza muy a menudo varias herramientas de análisis de código estático (SAST). Fuera de la caja, todos funcionan en promedio. Por supuesto, todo depende del proyecto y de las tecnologías utilizadas en él, 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 capacidad de personalizarla según las características específicas de sus aplicaciones, es decir, escribir y cambiar reglas de análisis o, como se les llama más a menudo, consultas personalizadas.

Cómo escribir reglas para Checkmarx sin volverse loco

La mayoría de las veces utilizamos Checkmarx, un analizador de código muy interesante y potente. En este artículo hablaré sobre mi experiencia al escribir reglas de análisis para ello.

tabla de contenidos

Entrada

Para empezar, me gustaría recomendar uno de los pocos artículos en ruso sobre las características de escribir consultas para Checkmarx. Fue publicado en Habré a finales de 2019 con el título: "¡Hola, Checkmarx!" Cómo escribir una consulta SAST de Checkmarx y encontrar vulnerabilidades interesantes.

Examina en detalle cómo escribir las primeras consultas en CxQL (Checkmarx Query Language) para algunas aplicaciones de prueba y muestra los principios básicos de cómo funcionan las reglas de análisis.

No repetiré lo que en él se describe, aunque todavía habrá algunas intersecciones. En mi artículo intentaré compilar una especie de "colección de recetas", una lista de soluciones a problemas específicos que encontré durante mi trabajo con Checkmarx. Tuve que devanarme los sesos sobre muchos de estos problemas. A veces no había suficiente información en la documentación y, a veces, incluso era difícil entender cómo hacer lo que se requería. Espero que mi experiencia y mis noches de insomnio no sean en vano, y que esta “colección de recetas de Consultas Personalizadas” te ahorre unas horas o un par de células nerviosas. ¡Vamos a empezar!

Información general sobre las reglas.

Primero, veamos algunos conceptos básicos y el proceso de trabajar con las reglas, para comprender mejor lo que sucederá a continuación. Y también porque la documentación no dice nada al respecto o está muy dispersa en la estructura, lo cual no es muy conveniente.

  1. Las reglas se aplican durante el escaneo dependiendo del ajuste preestablecido seleccionado al inicio (un conjunto de reglas activas). Puede crear una cantidad ilimitada de ajustes preestablecidos y exactamente cómo estructurarlos depende de las características específicas de su proceso. Puedes agruparlos por idioma o seleccionar ajustes preestablecidos para cada proyecto. La cantidad de reglas activas afecta la velocidad y precisión del escaneo.

    Cómo escribir reglas para Checkmarx sin volverse locoConfiguración de ajustes preestablecidos en la interfaz Checkmarx

  2. Las reglas se editan en una herramienta especial llamada CxAuditor. Esta es una aplicación de escritorio que se conecta a un servidor que ejecuta Checkmarx. Esta herramienta tiene dos modos de funcionamiento: editar reglas y analizar los resultados de un escaneo ya realizado.

    Cómo escribir reglas para Checkmarx sin volverse locoInterfaz CxAudit

  3. Las reglas en Checkmarx están divididas por idioma, es decir, cada idioma tiene su propio conjunto de consultas. También existen algunas reglas generales que se aplican independientemente del idioma, estas son las llamadas consultas básicas. En su mayor parte, las consultas básicas implican buscar información que utilizan otras reglas.

    Cómo escribir reglas para Checkmarx sin volverse locoDividir reglas por idioma

  4. Las reglas son "Ejecutables" y "No ejecutables" (Ejecutadas y No ejecutadas). En mi opinión no es el nombre correcto, pero es lo que es. La conclusión es que el resultado de ejecutar las reglas "ejecutables" se mostrará en los resultados del análisis en la interfaz de usuario, y las reglas "no ejecutables" solo son necesarias para usar sus resultados en otras solicitudes (en esencia, solo una función).

    Cómo escribir reglas para Checkmarx sin volverse locoDeterminar el tipo de regla al crear

  5. Puede crear nuevas reglas o complementar/reescribir las existentes. Para reescribir una regla, debe buscarla en el árbol, hacer clic derecho y seleccionar "Anular" en el menú desplegable. Es importante recordar aquí que las nuevas reglas no se incluyen inicialmente en los ajustes preestablecidos y no están activas. Para comenzar a usarlos es necesario activarlos en el menú “Preset Manager” del instrumento. Las reglas reescritas conservan su configuración, es decir, si la regla estaba activa, permanecerá así y se aplicará inmediatamente.

    Cómo escribir reglas para Checkmarx sin volverse locoEjemplo de una nueva regla en la interfaz del Administrador de ajustes preestablecidos

  6. Durante la ejecución, se construye un "árbol" de solicitudes, que depende de qué. Las reglas que recopilan información se ejecutan primero y las que la utilizan se ejecutan en segundo lugar. El resultado de la ejecución se almacena en caché, por lo que si es posible utilizar los resultados de una regla existente, entonces es mejor hacerlo, esto reducirá el tiempo de escaneo.

  7. Las reglas se pueden aplicar en diferentes niveles:

  • Para todo el sistema: se utilizará para cualquier escaneo de cualquier proyecto.

  • A nivel de equipo (Team): solo se usará para escanear proyectos en el equipo seleccionado.

  • A nivel de proyecto - Se aplicará en un proyecto específico

    Cómo escribir reglas para Checkmarx sin volverse locoDeterminar el nivel al que se aplicará la regla

“Diccionario” para principiantes

Y comenzaré con algunas cosas que me generaron preguntas y también mostraré una serie de técnicas que simplificarán significativamente la vida.

Operaciones con listas

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

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

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

Todos los artículos encontrados

Dentro del lenguaje escaneado, puedes obtener una lista de absolutamente todos los elementos que Checkmarx ha identificado (cadenas, funciones, clases, métodos, etc.). Este es un espacio de objetos al que se puede acceder a través de All. Es decir, buscar un objeto con un nombre específico. searchMe, puede buscar, por ejemplo, por nombre en todos los objetos encontrados:

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

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

Pero, si necesitas buscar en otro idioma que por algún motivo no se incluyó en el escaneo (por ejemplo, groovy en un proyecto de Android), puedes expandir nuestro espacio de objetos a través de una variable:

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

Funciones para análisis de flujo

Estas funciones se utilizan en muchas reglas y aquí hay una pequeña hoja de referencia de lo que significan:

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

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

Obteniendo nombre/ruta de archivo

Hay varios atributos que se pueden obtener a partir de los resultados de una consulta (el nombre del archivo en el que se encontró la entrada, cadena, etc.), pero la documentación no dice cómo obtenerlos y utilizarlos. Entonces, para hacer esto, necesitas acceder a la propiedad LinePragma y los objetos que necesitamos estarán ubicados dentro de ella:

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

Vale la pena tener en cuenta que FileName contiene en realidad la ruta al archivo, ya que usamos el método GetFirstGraph.

Resultado de la ejecución

Hay una variable especial dentro de CxQL result, que devuelve el resultado de ejecutar su regla escrita. Se inicializa inmediatamente y puedes escribir resultados intermedios en él, cambiándolos y refinándolos a medida que trabajas. Pero, si no hay ninguna asignación a esta variable o función dentro de la regla return— el resultado de la ejecución siempre será cero.

La siguiente consulta no nos devolverá nada como resultado de la ejecución y siempre estará vacía:

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

Pero, habiendo asignado el resultado de la ejecución a la variable mágica resultado, veremos qué nos devuelve esta llamada:

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

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

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

Usando los resultados de otras reglas

Las reglas en Checkmarx pueden considerarse análogas a las funciones en un lenguaje de programación normal. Al escribir una regla, es muy posible que utilice los resultados de otras consultas. Por ejemplo, no es necesario buscar todas las llamadas a métodos en el código cada vez, simplemente llame a la regla deseada:

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

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

Este enfoque le permite acortar el código y reducir significativamente el tiempo de ejecución de las reglas.

Solución de problemas

Inicio sesión

Cuando se trabaja con la herramienta, a veces no es posible escribir inmediatamente la consulta deseada y hay que experimentar, probando diferentes opciones. Para tal caso, la herramienta proporciona un registro, que se denomina de la siguiente manera:

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

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

Pero vale la pena recordar que este método sólo acepta como entrada cuerda, por lo que no será posible mostrar una lista completa de elementos encontrados como resultado de la primera operación. La segunda opción, que se utiliza para la depuración, es asignar una variable mágica de vez en cuando. result el resultado de la consulta y ver qué pasa. Este enfoque no es muy conveniente; debe asegurarse de que no haya anulaciones ni operaciones con esto en el código posterior result o simplemente comente el código a continuación. O puede, como yo, olvidarse de eliminar varias llamadas de este tipo de una regla ya preparada y preguntarse por qué nada funciona.

Una forma más conveniente es llamar al método. return con el parámetro requerido. En este caso finalizará la ejecución de la regla y podremos ver qué pasó como resultado de lo que escribimos:

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

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

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

Problema de inicio de sesión

Hay situaciones en las que no se puede acceder a la herramienta CxAudit (que se utiliza para escribir reglas). Puede haber muchas razones para esto, incluidos fallos, actualizaciones repentinas de Windows, BSOD y otras situaciones imprevistas que están fuera de nuestro control. En este caso, en ocasiones queda una sesión incompleta en la base de datos, lo que impide volver a iniciar sesión. Para solucionarlo, debe ejecutar varias consultas:

Para Checkmarx anterior a 8.6:

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

Para Checkmarx después de 8.6:

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

Reglas de escritura

Ahora llegamos a la parte más interesante. Cuando comienzas a escribir reglas en CxQL, lo que a menudo te falta no es tanta documentación como algunos ejemplos vivos de cómo resolver ciertos problemas y describir el proceso de operación de consultas en general.

Intentaré hacer la vida un poco más fácil a aquellos que están empezando a sumergirse en el lenguaje de consulta y daré varios ejemplos del uso de consultas personalizadas para resolver ciertos problemas. Algunos de ellos son bastante generales y pueden usarse en su empresa prácticamente sin cambios, otros son más específicos, pero también pueden usarse cambiando el código para adaptarlo a las características específicas de sus aplicaciones.

Entonces, estos son los problemas que encontramos con más frecuencia:

Problema: Hay varios Flujos en los resultados de ejecutar la regla y uno de ellos es anidamiento de otro, debes dejar uno de ellos.

solución: De hecho, a veces Checkmarx muestra varios flujos de datos que pueden superponerse y ser una versión abreviada de otros. Existe un método especial para estos casos. Reducir flujo. Dependiendo del parámetro seleccionará el Flujo más corto o más largo:

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

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

Problema: Ampliar la lista de datos sensibles a los que reacciona la herramienta

solución: Checkmarx tiene reglas básicas, cuyos resultados se utilizan en muchas otras consultas. Al complementar algunas de estas reglas con datos específicos de su aplicación, puede mejorar inmediatamente los resultados de su análisis. A continuación se muestra una regla de ejemplo para comenzar:

Lista_de_violaciones_de_privacidad_general

Agreguemos varias variables que se utilizan en nuestra aplicación para almacenar información confidencial:

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

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

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

Problema: Ampliar la lista de variables con contraseñas

solución: Recomendaría prestar atención de inmediato a la regla básica para definir contraseñas en el código y agregarle una lista de nombres de variables que se usan comúnmente en su empresa.

Contraseña_privacidad_violación_lista

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

Problema: Agregar marcos usados ​​que no son compatibles con Checkmarx

solución: Todas las consultas en Checkmarx están divididas por idioma, por lo que debes agregar reglas para cada idioma. A continuación se muestran algunos ejemplos de dichas reglas.

Si se utilizan bibliotecas que complementan o reemplazan la funcionalidad estándar, se pueden agregar fácilmente a la regla básica. Entonces, todos los que lo utilicen conocerán inmediatamente las nuevas presentaciones. Por ejemplo, las bibliotecas para iniciar sesión en Android son Timber y Loggi. En el paquete básico, no hay reglas para identificar llamadas que no sean del sistema, por lo que si una contraseña o un identificador de sesión ingresa al registro, no lo sabremos. Intentemos agregar definiciones de dichos métodos a las reglas de Checkmarx.

Ejemplo de código de prueba que utiliza la biblioteca Timber para iniciar sesión:

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

Y aquí hay un ejemplo de una solicitud para Checkmarx, que le permitirá agregar una definición de llamada a métodos Timber como punto de salida para los datos de la aplicación:

Buscar salidas de Android

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

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

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

Y también puedes agregar a la regla vecina, pero esta se relaciona directamente con iniciar sesión en Android:

BuscarAndroidLog_Outputs

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

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

Además, si las aplicaciones de Android utilizan administrador de trabajo para trabajo asincrónico, es una buena idea informar adicionalmente a Checkmarx sobre esto agregando un método para obtener datos de la tarea getInputData:

BuscarAndroidLeer

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

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

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

Problema: Búsqueda de datos confidenciales en plist para proyectos de iOS

solución: iOS suele utilizar archivos especiales con la extensión .plist para almacenar varias variables y valores. No se recomienda almacenar contraseñas, tokens, claves y otros datos confidenciales en estos archivos, ya que se pueden extraer del dispositivo sin problemas.

Los archivos Plist tienen características que no son obvias a simple vista, pero que son importantes para Checkmarx. Escribamos una regla que busque los datos que necesitamos y nos diga si se mencionan contraseñas o tokens en alguna parte.

Un ejemplo de un archivo de este tipo, que contiene un token para la comunicación con el servicio 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>

Y una regla para Checkmarx, que tiene varios matices que conviene tener en cuenta a la hora de 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);

Problema: Encontrar información en XML

solución: Checkmarx tiene funciones muy convenientes para trabajar con XML y buscar valores, etiquetas, atributos y más. Pero, lamentablemente, hubo un error en la documentación por el cual ni un solo ejemplo funciona. A pesar de que este defecto se ha eliminado en la última versión de la documentación, tenga cuidado si utiliza 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, recibiremos un error que All no existe tal método... Y esto es cierto, ya que existe un espacio de objetos especial y separado para usar funciones para trabajar con XML. cxXPath. Así es como se ve la consulta correcta para encontrar una configuración en Android que permita el uso de tráfico HTTP:

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

Veámoslo con un poco más de detalle, ya que la sintaxis para todas las funciones es similar, una vez que haya descubierto una, solo necesita seleccionar la que necesita. Entonces, secuencialmente según los parámetros:

  • "*.xml"— máscara de archivos a buscar

  • 8 — identificación del idioma para el cual se aplica la regla

  • "cleartextTrafficPermitted"— nombre del atributo en xml

  • "true" — el valor de este atributo

  • false — uso de expresiones regulares al realizar búsquedas

  • true — significa que la búsqueda se realizará ignorando mayúsculas y minúsculas, es decir, sin distinguir entre mayúsculas y minúsculas.

Como ejemplo, utilizamos una regla que identifica configuraciones de conexión de red incorrectas, desde el punto de vista de la seguridad, en Android que permiten la comunicación con el servidor a través del protocolo HTTP. Ejemplo de una configuración que contiene un atributo cleartextTrafficPermitted con valor 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>

Problema: Limitar resultados por nombre de archivo/ruta

solución: En uno de los grandes proyectos relacionados con el desarrollo de una aplicación móvil para Android, encontramos falsos positivos en la regla que determina la configuración de ofuscación. El caso es que la regla lista para usar busca en el archivo build.gradle una configuración responsable de aplicar 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 hacen referencia a las bibliotecas incluidas en el proyecto. La peculiaridad es que incluso si estos archivos no indican la necesidad de ofuscación, la configuración del archivo ensamblador principal se aplicará durante la compilación.

Por lo tanto, la tarea es eliminar los desencadenantes en archivos secundarios que pertenecen a bibliotecas. Se pueden identificar por la presencia de la línea. apply 'com.android.library'.

Código de ejemplo del archivo build.gradle, lo que determina la necesidad de ofuscación:

apply plugin: 'com.android.application'

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

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

dependencies {
  ...
}

Ejemplo de archivo build.gradle para una biblioteca incluida en el proyecto que no tiene esta configuración:

apply plugin: 'android-library'

dependencies {
  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 bastante universal y útil no sólo para aplicaciones de Android, sino también para otros casos en los que es necesario determinar si un resultado pertenece a un archivo específico.

Problema: Agregue soporte para una biblioteca de terceros si la sintaxis no es totalmente compatible

solución: La cantidad de marcos diferentes que se utilizan en el proceso de escritura de código está simplemente fuera de serie. Por supuesto, Checkmarx no siempre sabe acerca de su existencia, y nuestra tarea es enseñarle a comprender que ciertos métodos pertenecen específicamente a este marco. A veces esto se complica por el hecho de que los marcos utilizan nombres de funciones que son muy comunes y es imposible determinar sin ambigüedades la relación de una llamada particular con una biblioteca específica.

La dificultad es que la sintaxis de dichas bibliotecas no siempre se reconoce correctamente y hay que experimentar para evitar obtener una gran cantidad de falsos positivos. Hay varias opciones para mejorar la precisión del escaneo y resolver el problema:

  • Con la primera opción, sabemos con certeza que la biblioteca se utiliza en un proyecto específico y podemos aplicar la regla a nivel de equipo. Pero si el equipo decide adoptar un enfoque diferente o utiliza varias bibliotecas en las que se superponen los nombres de las funciones, podemos obtener una imagen no muy agradable de numerosos falsos positivos.

  • La segunda opción es buscar archivos en los que la biblioteca esté claramente importada. Con este enfoque, podemos estar seguros de que la biblioteca que necesitamos se utiliza exactamente en este archivo.

  • Y la tercera opción es utilizar los dos enfoques anteriores juntos.

Como ejemplo, veamos una biblioteca muy conocida en círculos reducidos. mancha para el lenguaje de programación Scala, es decir, la funcionalidad Empalme de valores literales. En general, para pasar parámetros a una consulta SQL, debe utilizar el operador $, que sustituye datos en una consulta SQL realizada previamente. Es decir, de hecho, es un análogo directo de la declaración preparada en Java. Pero, si necesita construir dinámicamente una consulta SQL, por ejemplo, si necesita pasar nombres de tablas, puede usar el operador #$, que sustituirá directamente los datos en la consulta (casi como una concatenación de cadenas).

Ejemplo de código:

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

Checkmarx aún no sabe cómo detectar el uso de valores literales de empalme y omite operadores #$, así que intentemos enseñarle a identificar posibles inyecciones de SQL y resaltar los lugares correctos 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));
}

Problema: Busque funciones vulnerables utilizadas en bibliotecas de código abierto

solución: Muchas empresas utilizan herramientas de monitoreo de código abierto (práctica OSA) para detectar el uso de versiones vulnerables de bibliotecas en aplicaciones desarrolladas. A veces no es posible actualizar dicha biblioteca a una versión segura. En algunos casos existen limitaciones funcionales, en otros no existe ninguna versión segura. En este caso, una combinación de prácticas SAST y OSA ayudará a determinar que las funciones que conducen a la explotación de la vulnerabilidad no se utilizan en el código.

Pero a veces, especialmente cuando se considera JavaScript, esto puede no ser una tarea completamente trivial. A continuación se muestra una solución, quizás no ideal, pero que aún funciona, utilizando el ejemplo de vulnerabilidades en el componente. lodash en métodos template и *set.

Ejemplos de código de prueba 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>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>

Estamos buscando todos nuestros métodos vulnerables, que se enumeran en 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));

Problema: Búsqueda de certificados integrados en la aplicación

solución: No es raro que las aplicaciones, especialmente las móviles, utilicen certificados o claves para acceder a varios servidores o verificar la fijación SSL. Desde una perspectiva de seguridad, almacenar este tipo de cosas en código no es la mejor práctica. Intentemos 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;

Problema: Encontrar 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 una buena idea, pero las situaciones varían. Gracias a las consultas CxQL, encontrar cosas como esta es bastante fácil:

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

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

Conclusión

Espero que este artículo sea de utilidad para quienes están comenzando a familiarizarse con la herramienta Checkmarx. Quizás aquellos que han estado escribiendo sus propias reglas durante mucho tiempo también encuentren algo útil en esta guía.

Desafortunadamente, actualmente falta un recurso donde se puedan extraer nuevas ideas durante el desarrollo de reglas para Checkmarx. Por eso creamos repositorio en Github, donde publicaremos nuestro trabajo para que todos los que usan CxQL puedan encontrar algo útil en él y también tengan la oportunidad de compartir su trabajo con la comunidad. El repositorio está en proceso de llenar y estructurar contenido, ¡así que los contribuyentes son bienvenidos!

Gracias por su atención!

Fuente: habr.com

Añadir un comentario