Como escrever regras para Checkmarx sem enlouquecer

Oi, Habr!

Em nosso trabalho, nossa empresa frequentemente lida com diversas ferramentas de análise estática de código (SAST). Fora da caixa, todos funcionam na média. Claro, tudo depende do projeto e das tecnologias nele utilizadas, bem como de quão bem essas tecnologias são abrangidas pelas regras de análise. Na minha opinião, um dos critérios mais importantes na escolha de uma ferramenta SAST é a capacidade de personalizá-la de acordo com as especificidades das suas aplicações, nomeadamente, escrever e alterar regras de análise ou, como são mais frequentemente chamadas, Consultas Personalizadas.

Como escrever regras para Checkmarx sem enlouquecer

Na maioria das vezes usamos Checkmarx - um analisador de código muito interessante e poderoso. Neste artigo falarei sobre minha experiência em escrever regras de análise para isso.

Índice analítico

Entrada

Para começar, gostaria de recomendar um dos poucos artigos em russo sobre os recursos de redação de consultas para Checkmarx. Foi publicado no Habré no final de 2019 com o título: "Olá, Checkmarx!" Como escrever uma consulta Checkmarx SAST e encontrar vulnerabilidades interessantes.

Ele examina detalhadamente como escrever as primeiras consultas em CxQL (Checkmarx Query Language) para algumas aplicações de teste e mostra os princípios básicos de como funcionam as regras de análise.

Não vou repetir o que está descrito nele, embora alguns cruzamentos ainda estejam presentes. No meu artigo tentarei compilar uma espécie de “coleção de receitas”, uma lista de soluções para problemas específicos que encontrei durante meu trabalho com Checkmarx. Tive que quebrar a cabeça com muitos desses problemas. Às vezes não havia informações suficientes na documentação e às vezes era até difícil entender como fazer o que era necessário. Espero que minha experiência e noites sem dormir não sejam em vão, e que esta “coleção de receitas de consultas personalizadas” economize algumas horas ou algumas células nervosas. Então, vamos começar!

Informações gerais sobre as regras

Primeiro, vejamos alguns conceitos básicos e o processo de trabalho com as regras, para uma melhor compreensão do que acontecerá a seguir. E também porque a documentação não fala nada sobre isso ou está muito espalhada na estrutura, o que não é muito conveniente.

  1. As regras são aplicadas durante a verificação dependendo da predefinição selecionada no início (um conjunto de regras ativas). Você pode criar um número ilimitado de predefinições, e exatamente como estruturá-las depende das especificidades do seu processo. Você pode agrupá-los por idioma ou selecionar predefinições para cada projeto. O número de regras ativas afeta a velocidade e a precisão da verificação.

    Como escrever regras para Checkmarx sem enlouquecerConfigurando Preset na interface Checkmarx

  2. As regras são editadas em uma ferramenta especial chamada CxAuditor. Este é um aplicativo de desktop que se conecta a um servidor executando Checkmarx. Esta ferramenta possui dois modos de operação: edição de regras e análise dos resultados de uma verificação já realizada.

    Como escrever regras para Checkmarx sem enlouquecerInterface CxAudit

  3. As regras no Checkmarx são divididas por idioma, ou seja, cada idioma possui seu próprio conjunto de consultas. Existem também algumas regras gerais que se aplicam independentemente do idioma, são as chamadas consultas básicas. Na maioria das vezes, as consultas básicas envolvem a busca de informações que outras regras usam.

    Como escrever regras para Checkmarx sem enlouquecerDividindo regras por idioma

  4. As regras são “Executáveis” e “Não Executáveis” (Executadas e Não Executadas). Não é bem o nome correto, na minha opinião, mas é isso. O resultado final é que o resultado da execução de regras “Executáveis” será exibido nos resultados da verificação na UI, e as regras “Não Executáveis” são necessárias apenas para usar seus resultados em outras solicitações (em essência, apenas uma função).

    Como escrever regras para Checkmarx sem enlouquecerDeterminando o tipo de regra ao criar

  5. Você pode criar novas regras ou complementar/reescrever as existentes. Para reescrever uma regra, você precisa encontrá-la na árvore, clicar com o botão direito e selecionar “Substituir” no menu suspenso. É importante lembrar aqui que as novas regras não estão inicialmente incluídas nos presets e não estão ativas. Para começar a usá-los você precisa ativá-los no menu “Preset Manager” do instrumento. As regras reescritas mantêm suas configurações, ou seja, se a regra estava ativa, ela permanecerá assim e será aplicada imediatamente.

    Como escrever regras para Checkmarx sem enlouquecerExemplo de uma nova regra na interface do Preset Manager

  6. Durante a execução, é construída uma “árvore” de solicitações, que depende de quê. As regras que coletam informações são executadas primeiro e aqueles que as utilizam depois. O resultado da execução é armazenado em cache, portanto, se for possível utilizar os resultados de uma regra existente, é melhor fazê-lo, pois isso reduzirá o tempo de verificação.

  7. As regras podem ser aplicadas em diferentes níveis:

  • Para todo o sistema - será utilizado para qualquer digitalização de qualquer projeto

  • No nível da equipe (Team) - será utilizado apenas para verificar projetos da equipe selecionada.

  • Ao nível do projeto - Será aplicado num projeto específico

    Como escrever regras para Checkmarx sem enlouquecerDeterminar o nível em que a regra será aplicada

“Dicionário” para iniciantes

E começarei com algumas coisas que me causaram dúvidas e também mostrarei uma série de técnicas que simplificarão significativamente a vida.

Operações com listas

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

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

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

Todos os itens encontrados

Dentro da linguagem digitalizada, você pode obter uma lista de absolutamente todos os elementos que Checkmarx identificou (strings, funções, classes, métodos, etc.). Este é algum espaço de objetos que pode ser acessado através All. Ou seja, para procurar um objeto com um nome específico searchMe, você pode pesquisar, por exemplo, por nome em todos os objetos encontrados:

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

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

Mas, se precisar pesquisar em outro idioma que por algum motivo não foi incluído no scan (por exemplo, groovy em um projeto Android), você pode expandir nosso espaço de objetos através de uma variável:

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

Funções para análise de fluxo

Essas funções são usadas em muitas regras e aqui está uma pequena folha de dicas sobre o que elas significam:

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

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

Obtendo nome/caminho do arquivo

Existem vários atributos que podem ser obtidos a partir dos resultados de uma consulta (nome do arquivo em que a entrada foi encontrada, string, etc.), mas a documentação não diz como obtê-los e utilizá-los. Então, para fazer isso, você precisa acessar a propriedade LinePragma e os objetos que precisamos estarão localizados 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);

Vale a pena ter em mente que FileName contém na verdade o caminho para o arquivo, já que usamos o método GetFirstGraph.

Resultado da execução

Existe uma variável especial dentro do CxQL result, que retorna o resultado da execução de sua regra escrita. Ele é inicializado imediatamente e você pode gravar resultados intermediários nele, alterando-os e refinando-os à medida que trabalha. Mas, se não houver atribuição a esta variável ou função dentro da regra return— o resultado da execução será sempre zero.

A consulta a seguir não nos retornará nada como resultado da execução e estará sempre vazia:

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

Mas, tendo atribuído o resultado da execução à variável mágica result, veremos o que esta chamada nos retorna:

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

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

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

Usando os resultados de outras regras

As regras no Checkmarx podem ser chamadas de análogas às funções em uma linguagem de programação regular. Ao escrever uma regra, você pode usar os resultados de outras consultas. Por exemplo, não há necessidade de procurar todas as chamadas de método no código todas as vezes, basta chamar a regra desejada:

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

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

Essa abordagem permite encurtar o código e reduzir significativamente o tempo de execução da regra.

Solução de problemas

Exploração madeireira

Ao trabalhar com a ferramenta, às vezes não é possível escrever imediatamente a consulta desejada e é necessário experimentar, tentando diferentes opções. Nesse caso, a ferramenta fornece registro, que é denominado da seguinte forma:

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

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

Mas vale lembrar que este método só aceita como entrada a corda, portanto não será possível exibir uma lista completa dos elementos encontrados como resultado da primeira operação. A segunda opção, usada para depuração, é atribuir uma variável mágica de tempos em tempos result o resultado da consulta e veja o que acontece. Essa abordagem não é muito conveniente; você precisa ter certeza de que não há substituições ou operações com isso no código após result ou simplesmente comente o código abaixo. Ou você pode, como eu, esquecer de remover várias dessas chamadas de uma regra pronta e se perguntar por que nada funciona.

Uma maneira mais conveniente é chamar o método return com o parâmetro necessário. Neste caso, a execução da regra terminará e poderemos ver o que aconteceu como resultado do que escrevemos:

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

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

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

Problema de login

Existem situações em que você não consegue acessar a ferramenta CxAudit (que é usada para escrever regras). Pode haver vários motivos para isso, incluindo travamentos, atualizações repentinas do Windows, BSOD e outras situações imprevistas que estão além do nosso controle. Nesse caso, às vezes há uma sessão inacabada no banco de dados, o que impede você de efetuar login novamente. Para corrigir isso, você precisa executar várias 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 após 8.6:

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

Regras de escrita

Agora chegamos à parte mais interessante. Quando você começa a escrever regras em CxQL, o que muitas vezes falta não é tanto documentação, mas alguns exemplos vivos de como resolver certos problemas e descrever o processo de como as consultas funcionam em geral.

Vou tentar facilitar um pouco a vida de quem está começando a mergulhar na linguagem de consulta e dar vários exemplos de uso de Consultas Personalizadas para resolver determinados problemas. Alguns deles são bastante gerais e podem ser utilizados em sua empresa praticamente sem alterações, outros são mais específicos, mas também podem ser utilizados alterando o código para se adequar às especificidades de suas aplicações.

Então, aqui estão os problemas que encontramos com mais frequência:

Tarefa: Existem vários Fluxos nos resultados da execução da regra e um deles é um aninhamento de outro, você deve deixar um deles.

solução: Na verdade, às vezes o Checkmarx mostra vários fluxos de dados que podem se sobrepor e ser uma versão abreviada de outros. Existe um método especial para esses casos Reduzir Fluxo. Dependendo do parâmetro, selecionará o Fluxo mais curto ou mais longo:

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

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

Tarefa: Expanda a lista de dados confidenciais aos quais a ferramenta reage

solução: Checkmarx possui regras básicas, cujos resultados são utilizados por muitas outras consultas. Ao complementar algumas dessas regras com dados específicos do seu aplicativo, você pode melhorar imediatamente os resultados da verificação. Abaixo está um exemplo de regra para você começar:

General_privacy_violation_list

Vamos adicionar diversas variáveis ​​que são usadas em nossa aplicação para armazenar informações confidenciais:

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

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

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

Tarefa: Expanda a lista de variáveis ​​com senhas

solução: Eu recomendaria prestar atenção imediatamente à regra básica para definir senhas no código e adicionar a ela uma lista de nomes de variáveis ​​comumente usadas em sua empresa.

Senha_privacy_violation_list

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

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

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

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

Tarefa: Adicione estruturas usadas que não são suportadas pela Checkmarx

solução: Todas as consultas no Checkmarx são divididas por idioma, portanto é necessário adicionar regras para cada idioma. Abaixo estão alguns exemplos de tais regras.

Se forem usadas bibliotecas que complementem ou substituam a funcionalidade padrão, elas podem ser facilmente adicionadas à regra básica. Então, todos que o usarem aprenderão imediatamente sobre as novas introduções. Por exemplo, as bibliotecas para login no Android são Timber e Loggi. No pacote básico, não há regras para identificar chamadas que não sejam do sistema; portanto, se uma senha ou identificador de sessão entrar no log, não saberemos. Vamos tentar adicionar definições de tais métodos às regras do Checkmarx.

Exemplo de código de teste que usa a biblioteca Timber para 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");
    }
}

E aqui está um exemplo de solicitação para Checkmarx, que permitirá adicionar uma definição de chamada de métodos Timber como ponto de saída para dados do aplicativo:

Encontrar saídas do Android

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

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

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

E você também pode adicionar à regra vizinha, mas esta está diretamente relacionada ao login no Android:

EncontrarAndroidLog_Outputs

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

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

Além disso, se os aplicativos Android usarem Gerente de Trabalho para trabalho assíncrono, é uma boa ideia informar adicionalmente a Checkmarx sobre isso adicionando um método para obter dados da tarefa getInputData:

EncontrarAndroidLer

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

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

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

Tarefa: Procurando dados confidenciais no plist para projetos iOS

solução: O iOS geralmente usa arquivos especiais com a extensão .plist para armazenar diversas variáveis ​​e valores. Não é recomendado armazenar senhas, tokens, chaves e outros dados sensíveis nesses arquivos, pois eles podem ser extraídos do dispositivo sem problemas.

Os arquivos Plist possuem recursos que não são óbvios a olho nu, mas são importantes para o Checkmarx. Vamos escrever uma regra que irá procurar os dados que precisamos e nos informar se senhas ou tokens são mencionados em algum lugar.

Um exemplo desse arquivo, que contém um token para comunicação com o serviço de back-end:

<?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 uma regra para Checkmarx, que possui diversas nuances que devem ser levadas em consideração na hora de escrever:

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

Tarefa: Encontrando informações em XML

solução: Checkmarx possui funções muito convenientes para trabalhar com XML e pesquisar valores, tags, atributos e muito mais. Mas, infelizmente, houve um erro na documentação que fez com que nenhum exemplo funcionasse. Apesar de esse defeito ter sido eliminado na versão mais recente da documentação, tome cuidado ao usar versões anteriores de documentos.

Aqui está um exemplo incorreto da documentação:

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

Como resultado da tentativa de execução, receberemos um erro que All não existe tal método... E isso é verdade, pois existe um espaço de objeto especial e separado para usar funções para trabalhar com XML - cxXPath. Esta é a aparência da consulta correta para encontrar uma configuração no Android que permita o uso de tráfego HTTP:

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

Vejamos com mais detalhes, já que a sintaxe de todas as funções é semelhante, depois de descobrir uma, basta selecionar a que você precisa. Então, sequencialmente de acordo com os parâmetros:

  • "*.xml"— máscara de arquivos a serem pesquisados

  • 8 — id do idioma para o qual a regra é aplicada

  • "cleartextTrafficPermitted"— nome do atributo em xml

  • "true" — o valor deste atributo

  • false — uso de expressão regular ao pesquisar

  • true — significa que a pesquisa será realizada ignorando maiúsculas e minúsculas, ou seja, sem distinção entre maiúsculas e minúsculas

Como exemplo, utilizamos uma regra que identifica incorretas, do ponto de vista de segurança, configurações de conexão de rede no Android que permitem a comunicação com o servidor via protocolo HTTP. Exemplo de uma configuração contendo um atributo cleartextTrafficPermitted com 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>

Tarefa: Limitar resultados por nome/caminho do arquivo

solução: Em um dos grandes projetos relacionados ao desenvolvimento de um aplicativo mobile para Android, encontramos falsos positivos da regra que determina a configuração de ofuscação. O fato é que a regra fora da caixa pesquisa no arquivo build.gradle uma configuração responsável por aplicar regras de ofuscação para a versão de lançamento do aplicativo.

Mas em grandes projetos às vezes existem arquivos filhos build.gradle, que se referem às bibliotecas incluídas no projeto. A peculiaridade é que mesmo que esses arquivos não indiquem a necessidade de ofuscação, as configurações do arquivo assembly pai serão aplicadas durante a compilação.

Assim, a tarefa é cortar gatilhos em arquivos filhos que pertencem a bibliotecas. Eles podem ser identificados pela presença da linha apply 'com.android.library'.

Exemplo de código do arquivo build.gradle, que determina a necessidade de ofuscação:

apply plugin: 'com.android.application'

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

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

dependencies {
  ...
}

Exemplo de arquivo build.gradle para uma biblioteca incluída no projeto que não possui esta configuração:

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:

ProGuardObfuscaçãoNotInUse

// Поиск метода 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);
		}
	}
}

Essa abordagem pode ser bastante universal e útil não apenas para aplicativos Android, mas também para outros casos em que você precisa determinar se um resultado pertence a um arquivo específico.

Tarefa: Adicione suporte para uma biblioteca de terceiros se a sintaxe não for totalmente suportada

solução: O número de várias estruturas usadas no processo de escrita de código é simplesmente extraordinário. É claro que Checkmarx nem sempre sabe de sua existência, e nossa tarefa é ensiná-lo a entender que certos métodos pertencem especificamente a esse framework. Às vezes, isso é complicado pelo fato de que as estruturas usam nomes de funções que são muito comuns e é impossível determinar inequivocamente o relacionamento de uma chamada específica com uma biblioteca específica.

A dificuldade é que a sintaxe dessas bibliotecas nem sempre é reconhecida corretamente e é necessário experimentar para evitar um grande número de falsos positivos. Existem várias opções para melhorar a precisão da digitalização e resolver o problema:

  • Na primeira opção, sabemos com certeza que a biblioteca é utilizada em um projeto específico e podemos aplicar a regra em nível de equipe. Mas se a equipe decidir adotar uma abordagem diferente ou usar diversas bibliotecas nas quais os nomes das funções se sobrepõem, poderemos obter uma imagem não muito agradável de numerosos falsos positivos

  • A segunda opção é procurar arquivos nos quais a biblioteca seja claramente importada. Com essa abordagem, podemos ter certeza de que a biblioteca que precisamos é usada exatamente neste arquivo.

  • E a terceira opção é usar as duas abordagens acima juntas.

Como exemplo, vejamos uma biblioteca bem conhecida em círculos estreitos liso para a linguagem de programação Scala, ou seja, a funcionalidade Emendando valores literais. Em geral, para passar parâmetros para uma consulta SQL, você deve usar o operador $, que substitui os dados em uma consulta SQL pré-formada. Na verdade, é um análogo direto da declaração preparada em Java. Mas, se você precisar construir dinamicamente uma consulta SQL, por exemplo, se precisar passar nomes de tabelas, poderá usar o operador #$, que substituirá diretamente os dados na consulta (quase como uma concatenação de strings).

Exemplo de código:

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

Checkmarx ainda não sabe como detectar o uso de Splicing Literal Values ​​​​e ignora operadores #$, então vamos tentar ensiná-lo a identificar possíveis injeções de SQL e destacar os locais certos no 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));
}

Tarefa: Procure funções vulneráveis ​​usadas em bibliotecas de código aberto

solução: Muitas empresas usam ferramentas de monitoramento de código aberto (prática OSA) para detectar o uso de versões vulneráveis ​​de bibliotecas em aplicativos desenvolvidos. Às vezes não é possível atualizar essa biblioteca para uma versão segura. Em alguns casos existem limitações funcionais, em outros não existe nenhuma versão segura. Neste caso, uma combinação de práticas SAST e OSA ajudará a determinar que as funções que levam à exploração da vulnerabilidade não são utilizadas no código.

Mas às vezes, especialmente quando se considera JavaScript, esta pode não ser uma tarefa completamente trivial. Abaixo está uma solução, talvez não ideal, mas mesmo assim funcionando, usando o exemplo de vulnerabilidades no componente lodash em métodos template и *set.

Exemplos de teste de código potencialmente vulnerável em um arquivo 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 diretamente em 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 procurando todos os nossos métodos vulneráveis, listados em 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));

Tarefa: Procurando certificados incorporados no aplicativo

solução: Não é incomum que aplicativos, especialmente os móveis, usem certificados ou chaves para acessar vários servidores ou verificar a fixação SSL. Do ponto de vista da segurança, armazenar essas coisas em código não é a prática recomendada. Vamos tentar escrever uma regra que irá procurar arquivos semelhantes no repositório:

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

Tarefa: Encontrando tokens comprometidos no aplicativo

solução: Muitas vezes é necessário revogar tokens comprometidos ou outras informações importantes presentes no código. É claro que armazená-los dentro do código-fonte não é uma boa ideia, mas as situações variam. Graças às consultas CxQL, encontrar coisas como esta é bastante fácil:

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

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

Conclusão

Espero que este artigo seja útil para quem está começando a conhecer a ferramenta Checkmarx. Talvez aqueles que já escrevem suas próprias regras há muito tempo também encontrem algo útil neste guia.

Infelizmente, atualmente falta um recurso onde novas ideias possam ser obtidas durante o desenvolvimento de regras para Checkmarx. É por isso que criamos repositório no Github, onde postaremos nosso trabalho para que todos que usam CxQL possam encontrar algo útil nele, e também tenham a oportunidade de compartilhar seu trabalho com a comunidade. O repositório está em processo de preenchimento e estruturação de conteúdo, então colaboradores são bem-vindos!

Obrigado!

Fonte: habr.com

Adicionar um comentário