Como escribir regras para Checkmarx sen volverse tolo

Ola Habr!

No noso traballo, a nosa empresa trata moitas veces con varias ferramentas de análise de código estático (SAST). Fóra da caixa funcionan todos na media. Por suposto, todo depende do proxecto e das tecnoloxías utilizadas nel, así como do ben que estas tecnoloxías estean cubertas polas regras de análise. Na miña opinión, un dos criterios máis importantes á hora de elixir unha ferramenta SAST é a posibilidade de personalizala segundo as especificidades das túas aplicacións, é dicir, escribir e cambiar as regras de análise ou, como se lles chama máis frecuentemente, Consultas personalizadas.

Como escribir regras para Checkmarx sen volverse tolo

A maioría das veces usamos Checkmarx, un analizador de código moi interesante e poderoso. Neste artigo falarei da miña experiencia de escribir regras de análise para iso.

Índice analítico

Entrada

Para comezar, gustaríame recomendar un dos poucos artigos en ruso sobre as características de escribir consultas para Checkmarx. Publicouse en Habré a finais de 2019 co título: "Ola, Checkmarx!" Como escribir unha consulta SAST de Checkmarx e atopar vulnerabilidades interesantes.

Examina en detalle como escribir as primeiras consultas en CxQL (Checkmarx Query Language) para algunha aplicación de proba e mostra os principios básicos de como funcionan as regras de análise.

Non repetirei o descrito nela, aínda que aínda estarán presentes algunhas interseccións. No meu artigo intentarei recompilar unha especie de "colección de receitas", unha lista de solucións a problemas específicos que atopei durante o meu traballo con Checkmarx. Tiven que meter o cerebro sobre moitos destes problemas. Ás veces non había suficiente información na documentación, e ás veces incluso era difícil entender como facer o que se requiría. Espero que a miña experiencia e as noites sen durmir non sexan en balde, e esta "colección de receitas de consultas personalizadas" aforrache unhas horas ou un par de células nerviosas. Entón, imos comezar!

Información xeral sobre as normas

En primeiro lugar, vexamos algúns conceptos básicos e o proceso de traballo coas regras, para comprender mellor o que sucederá a continuación. E tamén porque a documentación non di nada disto ou está moi espallada na estrutura, o que non é moi conveniente.

  1. As regras aplícanse durante a exploración dependendo da configuración predeterminada seleccionada ao inicio (un conxunto de regras activas). Podes crear un número ilimitado de presets, e a forma exacta de estruturalos depende das características específicas do teu proceso. Podes agrupalos por idioma ou seleccionar presets para cada proxecto. O número de regras activas afecta a velocidade e precisión da dixitalización.

    Como escribir regras para Checkmarx sen volverse toloConfigurando Preset na interface Checkmarx

  2. As regras edítanse nunha ferramenta especial chamada CxAuditor. Esta é unha aplicación de escritorio que se conecta a un servidor que executa Checkmarx. Esta ferramenta ten dous modos de funcionamento: editar regras e analizar os resultados dunha exploración xa realizada.

    Como escribir regras para Checkmarx sen volverse toloInterface CxAudit

  3. As regras en Checkmarx divídense por idiomas, é dicir, cada idioma ten o seu propio conxunto de consultas. Tamén hai algunhas regras xerais que se aplican independentemente da lingua, estas son as chamadas consultas básicas. Na súa maior parte, as consultas básicas implican buscar información que utilizan outras regras.

    Como escribir regras para Checkmarx sen volverse toloDividir regras por lingua

  4. As regras son "Executábeis" e "Non executables" (Executadas e Non Executadas). Non é o nome correcto, na miña opinión, pero iso é o que é. A conclusión é que o resultado da execución das regras "Executábeis" mostrarase nos resultados da análise na IU, e as regras "Non executables" só son necesarias para usar os seus resultados noutras solicitudes (en esencia, só unha función).

    Como escribir regras para Checkmarx sen volverse toloDeterminar o tipo de regra ao crear

  5. Podes crear novas regras ou complementar/reescribir as existentes. Para reescribir unha regra, cómpre atopala na árbore, facer clic co botón dereito e seleccionar "Anular" no menú despregable. É importante lembrar aquí que as novas regras non se inclúen inicialmente nos presets e non están activas. Para comezar a utilizalos, cómpre activalos no menú "Xestor de presets" do instrumento. As regras reescritas conservan a súa configuración, é dicir, se a regra estaba activa, permanecerá así e aplicarase inmediatamente.

    Como escribir regras para Checkmarx sen volverse toloExemplo dunha nova regra na interface do Xestor de predefinidos

  6. Durante a execución, constrúese unha "árbore" de solicitudes, que depende de que. As regras que recollen a información execútanse primeiro e as que a usan en segundo lugar. O resultado da execución está almacenado na caché, polo que se é posible usar os resultados dunha regra existente, é mellor facelo, isto reducirá o tempo de exploración.

  7. As regras pódense aplicar a diferentes niveis:

  • Para todo o sistema - será usado para calquera dixitalización de calquera proxecto

  • A nivel de equipo (Equipo): só se utilizará para dixitalizar proxectos no equipo seleccionado.

  • A nivel de proxecto - Aplicarase nun proxecto específico

    Como escribir regras para Checkmarx sen volverse toloDeterminación do nivel no que se aplicará a norma

"Dicionario" para principiantes

E comezarei con algunhas cousas que me provocaron preguntas, e tamén mostrarei unha serie de técnicas que simplificarán significativamente a vida.

Operacións con listas

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

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

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

Todos os elementos atopados

Dentro da linguaxe dixitalizada, pode obter unha lista de absolutamente todos os elementos que Checkmarx identificou (cadeas, funcións, clases, métodos, etc.). Este é un espazo de obxectos ao que se pode acceder All. É dicir, buscar un obxecto cun nome específico searchMe, pode buscar, por exemplo, polo nome en todos os obxectos atopados:

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

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

Pero, se necesitas buscar noutro idioma que por algún motivo non se incluíu na exploración (por exemplo, groovy nun proxecto de Android), podes ampliar o noso espazo de obxectos a través dunha variable:

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

Funcións para a análise de fluxo

Estas funcións úsanse en moitas regras e aquí tes unha pequena folla de trucos do que significan:

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

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

Obtendo o nome/ruta do ficheiro

Son varios os atributos que se poden obter dos resultados dunha consulta (o nome do ficheiro no que se atopou a entrada, cadea, etc.), pero a documentación non indica como obtelos e utilizalos. Entón, para facelo, cómpre acceder á propiedade LinePragma e os obxectos que necesitamos situaranse dentro dela:

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

Paga a pena ter en conta que FileName contén realmente o camiño ao ficheiro, xa que usamos o método GetFirstGraph.

Resultado da execución

Hai unha variable especial dentro de CxQL result, que devolve o resultado de executar a súa regra escrita. Iníciase inmediatamente e podes escribir resultados intermedios nel, cambiándoos e perfeccionándoos mentres traballas. Pero, se non hai asignación a esta variable ou función dentro da regra return— o resultado da execución será sempre cero.

A seguinte consulta non nos devolverá nada como resultado da execución e sempre estará baleira:

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

Pero, unha vez asignado o resultado da execución ao resultado da variable máxica, veremos o que nos devolve esta chamada:

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

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

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

Utilizar os resultados doutras regras

As regras en Checkmarx pódense chamar análogas ás funcións nunha linguaxe de programación normal. Ao escribir unha regra, pode usar os resultados doutras consultas. Por exemplo, non é necesario buscar todas as chamadas de método no código cada vez, simplemente chame a regra desexada:

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

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

Este enfoque permítelle acurtar o código e reducir significativamente o tempo de execución da regra.

Resolución de problemas

Rexistro

Cando se traballa coa ferramenta, ás veces non é posible escribir inmediatamente a consulta desexada e hai que experimentar, probando diferentes opcións. Para tal caso, a ferramenta proporciona rexistro, que se chama do seguinte xeito:

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

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

Pero vale a pena lembrar que este método só acepta como entrada corda, polo que non será posible mostrar unha lista completa dos elementos atopados como resultado da primeira operación. A segunda opción, que se usa para a depuración, é asignar a unha variable máxica de cando en vez result o resultado da consulta e ver que pasa. Este enfoque non é moi conveniente; cómpre asegurarse de que non hai substitucións nin operacións con isto no código despois result ou simplemente comenta o código a continuación. Ou pode, coma min, esquecerse de eliminar varias chamadas deste tipo dunha regra preparada e preguntarse por que nada funciona.

Unha forma máis conveniente é chamar ao método return co parámetro necesario. Neste caso, a execución da norma rematará e poderemos ver o que pasou como consecuencia do que escribimos:

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

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

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

Problema de inicio de sesión

Hai situacións nas que non pode acceder á ferramenta CxAudit (que se usa para escribir regras). Pode haber moitas razóns para iso, incluíndo fallos, actualizacións repentinas de Windows, BSOD e outras situacións imprevistas que están fóra do noso control. Neste caso, ás veces hai unha sesión sen rematar na base de datos, o que impide que inicie sesión de novo. Para solucionalo, cómpre executar varias consultas:

Para Checkmarx antes de 8.6:

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

Para Checkmarx despois de 8.6:

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

Normas de redacción

Agora chegamos á parte máis interesante. Cando comezas a escribir regras en CxQL, o que moitas veces te falta non é tanto documentación como algúns exemplos vivos de resolución de certos problemas e descrición do proceso de como funcionan as consultas en xeral.

Tentarei facilitar un pouco a vida aos que comezan a mergullarse na linguaxe de consulta e dar varios exemplos de uso de Consultas personalizadas para resolver certos problemas. Algúns deles son bastante xerais e pódense usar na túa empresa practicamente sen cambios, outros son máis específicos, pero tamén se poden usar cambiando o código para adaptalo ás especificidades das túas aplicacións.

Entón, aquí están os problemas que atopamos con máis frecuencia:

Unha tarefa: Hai varios fluxos nos resultados da execución da regra e un deles é un aniñado doutro, debes deixar un deles.

solución: De feito, ás veces Checkmarx mostra varios fluxos de datos que poden solaparse e ser unha versión abreviada doutros. Existe un método especial para tales casos Reducir o fluxo. Dependendo do parámetro, seleccionará o caudal máis curto ou longo:

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

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

Unha tarefa: Amplía a lista de datos sensibles aos que reacciona a ferramenta

solución: Checkmarx ten regras básicas, cuxos resultados son usados ​​por moitas outras consultas. Ao complementar algunhas destas regras con datos específicos da súa aplicación, pode mellorar inmediatamente os resultados da exploración. A continuación móstrase un exemplo de regra para comezar:

Lista_de_violacións_de_privacidade_xeral

Engademos varias variables que se usan na nosa 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);

Unha tarefa: Amplíe a lista de variables con contrasinais

solución: Recomendaríache prestar atención inmediatamente á regra básica para definir os contrasinais no código e engadirlle unha lista de nomes de variables que se usan habitualmente na túa empresa.

Lista_de_violacións_de_privacidade_contrasinais

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

Unha tarefa: Engade marcos usados ​​que non son compatibles con Checkmarx

solución: Todas as consultas en Checkmarx están divididas por idiomas, polo que cómpre engadir regras para cada idioma. A continuación móstranse algúns exemplos de tales regras.

Se se usan bibliotecas que complementan ou substitúen a funcionalidade estándar, pódense engadir facilmente á regra básica. Entón todos os que o usen coñecerán inmediatamente as novas presentacións. Como exemplo, as bibliotecas para iniciar sesión en Android son Timber e Loggi. No paquete básico, non hai regras para identificar chamadas non do sistema, polo que se un contrasinal ou identificador de sesión entra no rexistro, non o saberemos. Tentemos engadir definicións de tales métodos ás regras de Checkmarx.

Exemplo de código de proba que usa a biblioteca Timber para o rexistro:

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

E aquí tes un exemplo de solicitude de Checkmarx, que che permitirá engadir unha definición de chamar aos métodos Timber como punto de saída dos datos da aplicación:

BuscarAndroidOutputs

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

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

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

E tamén pode engadir á regra veciña, pero esta está relacionada directamente co inicio de sesión en Android:

Buscar AndroidLog_Outputs

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

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

Ademais, se usan aplicacións de Android Xestor de traballo para o traballo asíncrono, é unha boa idea informar adicionalmente a Checkmarx sobre isto engadindo un método para obter datos da tarefa getInputData:

BuscarAndroidRead

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

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

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

Unha tarefa: Buscando datos confidenciais en plist para proxectos iOS

solución: iOS adoita usar ficheiros especiais coa extensión .plist para almacenar varias variables e valores. Non se recomenda almacenar contrasinais, tokens, claves e outros datos confidenciais nestes ficheiros, xa que se poden extraer do dispositivo sen ningún problema.

Os ficheiros Plist teñen características que non son obvias a simple vista, pero que son importantes para Checkmarx. Escribamos unha regra que buscará os datos que necesitamos e nos diga se se mencionan contrasinais ou tokens nalgún lugar.

Un exemplo deste tipo de ficheiro, que contén un token para a comunicación co servizo de 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>

E unha regra para Checkmarx, que ten varios matices que se deben ter en conta ao 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);

Unha tarefa: Busca información en XML

solución: Checkmarx ten funcións moi convenientes para traballar con XML e buscar valores, etiquetas, atributos e moito máis. Pero, por desgraza, houbo un erro na documentación polo que non funciona nin un só exemplo. A pesar de que este defecto foi eliminado na última versión da documentación, teña coidado se utiliza versións anteriores dos documentos.

Aquí tes un exemplo incorrecto da documentación:

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

Como resultado do intento de execución, recibiremos un erro que All non existe tal método... E isto é certo, xa que hai un espazo de obxectos especial e separado para usar funcións para traballar con XML - cxXPath. Este é o aspecto da consulta correcta para atopar unha configuración en Android que permita o uso do tráfico HTTP:

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

Vexámolo cun pouco máis de detalle, xa que a sintaxe de todas as funcións é semellante, despois de descubrir unha, só tes que seleccionar a que necesitas. Entón, secuencialmente segundo os parámetros:

  • "*.xml"— máscara dos ficheiros a buscar

  • 8 — ID do idioma para o que se aplica a regra

  • "cleartextTrafficPermitted"- nome do atributo en xml

  • "true" - o valor deste atributo

  • false — Uso de expresións regulares na procura

  • true — significa que a busca se realizará ignorando maiúsculas e minúsculas, é dicir, non distingue entre maiúsculas e minúsculas

Como exemplo, utilizamos unha regra que identifica incorrectas, dende o punto de vista da seguridade, a configuración de conexión de rede en Android que permite a comunicación co servidor a través do protocolo HTTP. Exemplo dunha configuración que contén 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>

Unha tarefa: Limite os resultados polo nome/ruta do ficheiro

solución: Nun dos grandes proxectos relacionados co desenvolvemento dunha aplicación móbil para Android, atopámonos con falsos positivos da regra que determina a configuración de ofuscación. O caso é que a regra fóra da caixa busca no ficheiro build.gradle unha configuración responsable de aplicar regras de ofuscación para a versión de lanzamento da aplicación.

Pero en grandes proxectos ás veces hai ficheiros fillos build.gradle, que fan referencia ás bibliotecas incluídas no proxecto. A peculiaridade é que aínda que estes ficheiros non indiquen a necesidade de ofuscar, a configuración do ficheiro de montaxe principal aplicarase durante a compilación.

Así, a tarefa é cortar os disparadores nos ficheiros fillos que pertencen ás bibliotecas. Pódense identificar pola presenza da liña apply 'com.android.library'.

Exemplo de código do ficheiro build.gradle, que determina a necesidade de ofuscar:

apply plugin: 'com.android.application'

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

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

dependencies {
  ...
}

Ficheiro de exemplo build.gradle para unha biblioteca incluída no proxecto que non ten esta configuración:

apply plugin: 'android-library'

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

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

E a regra 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 pode ser bastante universal e útil non só para aplicacións de Android, senón tamén para outros casos nos que precisa determinar se un resultado pertence a un ficheiro específico.

Unha tarefa: Engade soporte para unha biblioteca de terceiros se a sintaxe non é totalmente compatible

solución: O número de marcos que se usan no proceso de escritura de código está simplemente fóra dos gráficos. Por suposto, Checkmarx non sempre coñece a súa existencia, e a nosa tarefa é ensinarlle a comprender que certos métodos pertencen especificamente a este marco. Ás veces isto complícase polo feito de que os frameworks usan nomes de funcións moi comúns e é imposible determinar sen ambigüidades a relación dunha determinada chamada cunha biblioteca específica.

A dificultade é que a sintaxe destas bibliotecas non sempre se recoñece correctamente e hai que experimentar para evitar obter un gran número de falsos positivos. Hai varias opcións para mellorar a precisión da dixitalización e resolver o problema:

  • A primeira opción, sabemos con certeza que a biblioteca se utiliza nun proxecto concreto e pode aplicar a regra a nivel de equipo. Pero se o equipo decide adoptar un enfoque diferente ou utiliza varias bibliotecas nas que se superpoñen os nomes de funcións, podemos obter unha imaxe non moi agradable de numerosos falsos positivos.

  • A segunda opción é buscar ficheiros nos que a biblioteca estea claramente importada. Con este enfoque, podemos estar seguros de que a biblioteca que necesitamos se usa exactamente neste ficheiro.

  • E a terceira opción é utilizar os dous enfoques anteriores xuntos.

A modo de exemplo, vexamos unha biblioteca coñecida en círculos estreitos carafio para a linguaxe de programación Scala, é dicir, a funcionalidade Empalme de valores literais. En xeral, para pasar parámetros a unha consulta SQL, debes usar o operador $, que substitúe os datos nunha consulta SQL preformada. É dicir, de feito, é un análogo directo de Prepared Statement en Java. Pero, se precisa construír dinámicamente unha consulta SQL, por exemplo, se precisa pasar nomes de táboas, pode usar o operador #$, que substituirá directamente os datos na consulta (case como a concatenación de cadeas).

Código de exemplo:

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

Checkmarx aínda non sabe como detectar o uso de Splicing Literal Values ​​e omite operadores #$, entón imos tentar ensinarlle a identificar posibles inxeccións SQL e resaltar os lugares correctos do 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));
}

Unha tarefa: Busca funcións vulnerables usadas nas bibliotecas de código aberto

solución: Moitas empresas utilizan ferramentas de monitorización de código aberto (práctica OSA) para detectar o uso de versións vulnerables das bibliotecas en aplicacións desenvolvidas. Ás veces non é posible actualizar esa biblioteca a unha versión segura. Nalgúns casos hai limitacións funcionais, noutros non hai unha versión segura. Neste caso, unha combinación de prácticas SAST e OSA axudará a determinar que as funcións que conducen á explotación da vulnerabilidade non se utilizan no código.

Pero ás veces, especialmente cando se considera JavaScript, isto pode non ser unha tarefa completamente trivial. A continuación móstrase unha solución, quizais non a ideal, pero que con todo funciona, utilizando o exemplo de vulnerabilidades do compoñente lodash en métodos template и *set.

Exemplos de código potencialmente vulnerable de proba nun ficheiro 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!'

E ao conectar 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>

Buscamos todos os nosos 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));

Unha tarefa: Buscando certificados incorporados na aplicación

solución: Non é raro que as aplicacións, especialmente as móbiles, utilicen certificados ou claves para acceder a varios servidores ou verificar SSL-Pinning. Desde unha perspectiva de seguridade, almacenar tales cousas no código non é a mellor práctica. Imos tentar escribir unha regra que buscará ficheiros similares no 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;

Unha tarefa: Atopar tokens comprometidos na aplicación

solución: Moitas veces é necesario revogar os tokens comprometidos ou outra información importante que estea presente no código. Por suposto, almacenalos dentro do código fonte non é unha boa idea, pero as situacións varían. Grazas ás consultas CxQL, atopar cousas como esta é bastante fácil:

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

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

Conclusión

Espero que este artigo sexa útil para aqueles que comezan a coñecer a ferramenta Checkmarx. Quizais aqueles que levan moito tempo escribindo as súas propias regras tamén atopen algo útil nesta guía.

Desafortunadamente, actualmente falta un recurso onde se poidan recoller novas ideas durante o desenvolvemento das regras de Checkmarx. Por iso creamos repositorio en Github, onde publicaremos o noso traballo para que todos os que utilicen CxQL poidan atopar nel algo útil, e tamén teñan a oportunidade de compartir o seu traballo coa comunidade. O repositorio está en proceso de encher e estruturar o contido, polo que os colaboradores son benvidos!

Спасибо за внимание!

Fonte: www.habr.com

Engadir un comentario