Com escriure regles per a Checkmarx sense tornar-se boig

Hola Habr!

En el nostre treball, la nostra empresa tracta sovint amb diverses eines d'anàlisi de codi estàtic (SAST). Fora de la caixa, tots funcionen de mitjana. Per descomptat, tot depèn del projecte i de les tecnologies que s'hi fan servir, així com de com aquestes tecnologies estan cobertes per les regles d'anàlisi. Al meu entendre, un dels criteris més importants a l'hora d'escollir una eina SAST és la possibilitat de personalitzar-la segons les especificitats de les vostres aplicacions, és a dir, escriure i canviar regles d'anàlisi o, com s'anomenen més sovint, consultes personalitzades.

Com escriure regles per a Checkmarx sense tornar-se boig

Sovint fem servir Checkmarx, un analitzador de codi molt interessant i potent. En aquest article parlaré de la meva experiència en escriure regles d'anàlisi per a això.

Taula de continguts

Entrada

Per començar, m'agradaria recomanar un dels pocs articles en rus sobre les característiques d'escriure consultes per a Checkmarx. Va ser publicat a Habré a finals de 2019 amb el títol: "Hola, Checkmarx!" Com escriure una consulta SAST de Checkmarx i trobar vulnerabilitats interessants.

Examina amb detall com escriure les primeres consultes en CxQL (Llenguatge de consultes de Checkmarx) per a alguna aplicació de prova i mostra els principis bàsics de com funcionen les regles d'anàlisi.

No repetiré el que s'hi descriu, tot i que algunes interseccions encara hi seran presents. En el meu article intentaré compilar una mena de "col·lecció de receptes", una llista de solucions a problemes concrets que vaig trobar durant el meu treball amb Checkmarx. M'he hagut de trencar el cervell amb molts d'aquests problemes. De vegades no hi havia prou informació a la documentació, i de vegades fins i tot era difícil entendre com fer el que es requeria. Espero que la meva experiència i les nits sense dormir no siguin en va, i aquesta "col·lecció de receptes de consultes personalitzades" us estalviarà unes hores o un parell de cèl·lules nervioses. Així doncs, comencem!

Informació general sobre les normes

En primer lloc, analitzem alguns conceptes bàsics i el procés de treball amb les regles, per a una millor comprensió del que passarà a continuació. I també perquè la documentació no diu res d'això o està molt estesa a l'estructura, cosa que no és molt convenient.

  1. Les regles s'apliquen durant l'exploració en funció del valor predefinit seleccionat a l'inici (un conjunt de regles actives). Podeu crear un nombre il·limitat de presets i com estructurar-los exactament depèn de les especificitats del vostre procés. Podeu agrupar-los per idioma o seleccionar presets per a cada projecte. El nombre de regles actives afecta la velocitat i la precisió de l'escaneig.

    Com escriure regles per a Checkmarx sense tornar-se boigConfiguració de Preset a la interfície Checkmarx

  2. Les regles s'editen en una eina especial anomenada CxAuditor. Aquesta és una aplicació d'escriptori que es connecta a un servidor que executa Checkmarx. Aquesta eina té dos modes de funcionament: edició de regles i anàlisi dels resultats d'una exploració ja realitzada.

    Com escriure regles per a Checkmarx sense tornar-se boigInterfície CxAudit

  3. Les regles de Checkmarx es divideixen per idioma, és a dir, cada idioma té el seu propi conjunt de consultes. També hi ha algunes normes generals que s'apliquen independentment de l'idioma, aquestes són les anomenades consultes bàsiques. En la seva majoria, les consultes bàsiques impliquen cercar informació que utilitzen altres regles.

    Com escriure regles per a Checkmarx sense tornar-se boigDivisió de regles per llengua

  4. Les regles són "Executable" i "No executable" (executades i no executades). No és el nom del tot correcte, al meu entendre, però això és el que és. La conclusió és que el resultat d'executar regles "executables" es mostrarà als resultats de l'escaneig a la interfície d'usuari, i les regles "no executables" només es necessiten per utilitzar els seus resultats en altres sol·licituds (de fet, només són una funció). ).

    Com escriure regles per a Checkmarx sense tornar-se boigDeterminació del tipus de regla en crear-la

  5. Podeu crear regles noves o complementar/reescriure les existents. Per reescriure una regla, cal que la trobeu a l'arbre, feu clic amb el botó dret i seleccioneu "Anul·la" al menú desplegable. És important recordar aquí que les noves regles no s'inclouen inicialment als valors predefinits i no estan actives. Per començar a utilitzar-los cal activar-los al menú "Preset Manager" de l'instrument. Les regles reescrites conserven la seva configuració, és a dir, si la regla estava activa, ho romandrà i s'aplicarà immediatament.

    Com escriure regles per a Checkmarx sense tornar-se boigExemple d'una regla nova a la interfície del Gestor de predefinits

  6. Durant l'execució, es construeix un "arbre" de peticions, que depèn de què. Les regles que recullen la informació s'executen primer i les que l'utilitzen en segon lloc. El resultat de l'execució s'emmagatzema a la memòria cau, de manera que si és possible utilitzar els resultats d'una regla existent, és millor fer-ho, això reduirà el temps d'exploració.

  7. Les regles es poden aplicar a diferents nivells:

  • Per a tot el sistema: s'utilitzarà per a qualsevol escaneig de qualsevol projecte

  • A nivell d'equip (Equip): només s'utilitzarà per escanejar projectes de l'equip seleccionat.

  • A nivell de projecte - S'aplicarà en un projecte concret

    Com escriure regles per a Checkmarx sense tornar-se boigDeterminació del nivell en què s'aplicarà la regla

"Diccionari" per a principiants

I començaré amb algunes coses que em van fer preguntes, i també mostraré una sèrie de tècniques que simplificaran significativament la vida.

Operacions amb llistes

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

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

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

Tots els articles trobats

Dins del llenguatge escanejat, podeu obtenir una llista de tots els elements que Checkmarx ha identificat (cadenes, funcions, classes, mètodes, etc.). Aquest és un espai d'objectes al qual es pot accedir All. És a dir, cercar un objecte amb un nom concret searchMe, podeu cercar, per exemple, per nom a tots els objectes trobats:

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

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

Però, si necessiteu cercar en un altre idioma que per algun motiu no s'ha inclòs a l'exploració (per exemple, groovy en un projecte d'Android), podeu ampliar el nostre espai d'objectes mitjançant una variable:

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

Funcions per a l'anàlisi de flux

Aquestes funcions s'utilitzen en moltes regles i aquí hi ha un petit full de trucs del que volen dir:

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

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

Obtenint el nom/ruta del fitxer

Hi ha diversos atributs que es poden obtenir dels resultats d'una consulta (el nom del fitxer on s'ha trobat l'entrada, cadena, etc.), però la documentació no diu com obtenir-los i utilitzar-los. Per tant, per fer-ho, heu d'accedir a la propietat LinePragma i els objectes que necessitem s'ubicaran al seu interior:

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

Val la pena tenir-ho en compte FileName conté realment el camí al fitxer, ja que hem utilitzat el mètode GetFirstGraph.

Resultat de l'execució

Hi ha una variable especial dins de CxQL result, que retorna el resultat de l'execució de la regla escrita. S'inicialitza immediatament i podeu escriure-hi resultats intermedis, canviant-los i perfeccionant-los a mesura que treballeu. Però, si no hi ha assignació a aquesta variable o funció dins de la regla return— el resultat de l'execució sempre serà zero.

La consulta següent no ens retornarà res com a resultat de l'execució i sempre estarà buida:

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

Però, havent assignat el resultat de l'execució al resultat de la variable màgica, veurem què ens retorna aquesta crida:

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

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

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

Utilitzant els resultats d'altres regles

Les regles de Checkmarx es poden anomenar anàlogues a les funcions en un llenguatge de programació normal. Quan escriviu una regla, podeu utilitzar els resultats d'altres consultes. Per exemple, no cal cercar totes les trucades de mètode al codi cada vegada, només cal que truqueu a la regla desitjada:

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

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

Aquest enfocament us permet escurçar el codi i reduir significativament el temps d'execució de la regla.

Solucionar problemes

Enregistrament

Quan es treballa amb l'eina, de vegades no és possible escriure immediatament la consulta desitjada i cal experimentar, provant diferents opcions. En aquest cas, l'eina proporciona registre, que s'anomena de la següent manera:

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

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

Però val la pena recordar que aquest mètode només accepta com a entrada corda, de manera que no serà possible mostrar una llista completa dels elements trobats com a resultat de la primera operació. La segona opció, que s'utilitza per a la depuració, és assignar una variable màgica de tant en tant result el resultat de la consulta i veure què passa. Aquest enfocament no és gaire convenient; heu d'assegurar-vos que no hi ha substitucions ni operacions amb això al codi després result o simplement comenta el codi a continuació. O podeu, com jo, oblidar-vos d'eliminar diverses trucades d'aquest tipus d'una regla ja feta i preguntar-vos per què no funciona res.

Una manera més còmoda és cridar el mètode return amb el paràmetre requerit. En aquest cas, s'acabarà l'execució de la norma i podrem veure què va passar arran del que vam escriure:

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

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

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

Problema d'inici de sessió

Hi ha situacions en què no podeu accedir a l'eina CxAudit (que s'utilitza per escriure regles). Hi pot haver moltes raons per això, com ara bloquejos, actualitzacions sobtades de Windows, BSOD i altres situacions imprevistes que estan fora del nostre control. En aquest cas, de vegades hi ha una sessió inacabada a la base de dades, que impedeix tornar a iniciar sessió. Per solucionar-ho, heu d'executar diverses consultes:

Per a Checkmarx abans de 8.6:

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

Per a Checkmarx després de 8.6:

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

Normes d'escriptura

Ara anem a la part més interessant. Quan comenceu a escriure regles en CxQL, el que sovint us falta no és tant documentació com alguns exemples vius de resolució de determinats problemes i de descripció del procés d'operació de la consulta en general.

Intentaré facilitar una mica la vida a aquells que comencen a submergir-se en el llenguatge de consultes i donaré diversos exemples d'ús de consultes personalitzades per resoldre determinats problemes. Alguns d'ells són força generals i es poden utilitzar a la vostra empresa pràcticament sense canvis, d'altres són més específics, però també es poden utilitzar canviant el codi per adaptar-se a les especificitats de les vostres aplicacions.

Per tant, aquests són els problemes que hem trobat més sovint:

Una tasca: Hi ha diversos fluxos en els resultats de l'execució de la regla i un d'ells és una nidificació d'un altre, heu de deixar un d'ells.

solució: De fet, de vegades Checkmarx mostra diversos fluxos de dades que es poden solapar i ser una versió escurçada d'altres. Hi ha un mètode especial per a aquests casos Redueix el flux. Depenent del paràmetre, seleccionarà el flux més curt o més llarg:

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

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

Una tasca: Amplieu la llista de dades sensibles a les quals reacciona l'eina

solució: Checkmarx té regles bàsiques, els resultats de les quals són utilitzats per moltes altres consultes. Si complementeu algunes d'aquestes regles amb dades específiques de la vostra aplicació, podeu millorar immediatament els resultats de l'escaneig. A continuació es mostra un exemple de regla per començar:

Llista_general_de_violacions_de_privadesa

Afegim diverses variables que s'utilitzen a la nostra aplicació per emmagatzemar informació sensible:

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

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

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

Una tasca: Amplieu la llista de variables amb contrasenyes

solució: Us recomanaria que presteu atenció immediatament a la regla bàsica per definir les contrasenyes al codi i afegir-hi una llista de noms de variables que s'utilitzen habitualment a la vostra empresa.

Llista_de_violacions_de_privadesa_contrasenyes

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

Una tasca: Afegiu marcs usats que no són compatibles amb Checkmarx

solució: Totes les consultes a Checkmarx es divideixen per idioma, de manera que cal afegir regles per a cada idioma. A continuació es mostren alguns exemples d'aquestes regles.

Si s'utilitzen biblioteques que complementen o substitueixen la funcionalitat estàndard, es poden afegir fàcilment a la regla bàsica. Aleshores, tots els que l'utilitzin aprendran immediatament les noves presentacions. Com a exemple, les biblioteques per iniciar sessió a Android són Timber i Loggi. Al paquet bàsic, no hi ha regles per identificar les trucades que no són del sistema, de manera que si una contrasenya o un identificador de sessió entra al registre, no ho sabrem. Intentem afegir definicions d'aquests mètodes a les regles de Checkmarx.

Exemple de codi de prova que utilitza la biblioteca Timber per al registre:

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

I aquí teniu un exemple d'una sol·licitud per a Checkmarx, que us permetrà afegir una definició de trucada als mètodes Timber com a punt de sortida de les dades de l'aplicació:

Cerca Sortides d'Android

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

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

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

I també podeu afegir a la regla veïna, però aquesta es relaciona directament amb l'inici de sessió a Android:

Cerca AndroidLog_Outputs

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

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

A més, si utilitzen aplicacions d'Android WorkManager per al treball asíncron, és una bona idea informar-ne addicionalment a Checkmarx afegint un mètode per obtenir dades de la tasca. getInputData:

FindAndroidRead

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

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

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

Una tasca: Cerca de dades sensibles a plist per a projectes iOS

solució: iOS sovint utilitza fitxers especials amb l'extensió .plist per emmagatzemar diverses variables i valors. No es recomana emmagatzemar contrasenyes, fitxes, claus i altres dades sensibles en aquests fitxers, ja que es poden extreure del dispositiu sense cap problema.

Els fitxers Plist tenen característiques que no són òbvies a simple vista, però que són importants per a Checkmarx. Escrivim una regla que buscarà les dades que necessitem i ens dirà si s'esmenten contrasenyes o testimonis en algun lloc.

Un exemple d'aquest fitxer, que conté un testimoni per a la comunicació amb el servei de fons:

<?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>

I una regla per a Checkmarx, que té diversos matisos que s'han de tenir en compte a l'hora d'escriure:

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

Una tasca: Cercar informació en XML

solució: Checkmarx té funcions molt convenients per treballar amb XML i cercar valors, etiquetes, atributs i molt més. Però, malauradament, hi va haver un error a la documentació pel qual no funciona cap exemple. Tot i que aquest defecte s'ha eliminat a la darrera versió de la documentació, aneu amb compte si feu servir versions anteriors dels documents.

Aquí teniu un exemple incorrecte de la documentació:

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

Com a resultat de l'intent d'execució, rebrem un error que All no existeix aquest mètode... I això és cert, ja que hi ha un espai d'objectes especial i separat per utilitzar funcions per treballar amb XML: cxXPath. Així és com es veu la consulta correcta per trobar una configuració a Android que permet l'ús del trànsit HTTP:

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

Vegem-ho amb una mica més de detall, ja que la sintaxi de totes les funcions és similar, després d'haver-ne esbrinat una, només cal que seleccioneu la que necessiteu. Així, seqüencialment segons els paràmetres:

  • "*.xml"— màscara dels fitxers a cercar

  • 8 — id de la llengua a la qual s'aplica la regla

  • "cleartextTrafficPermitted"— nom de l'atribut en xml

  • "true" - el valor d'aquest atribut

  • false — ús d'expressions regulars en cercar

  • true — significa que la cerca es realitzarà ignorant majúscules i minúscules, és a dir, sense distingir entre majúscules i minúscules

Com a exemple, hem utilitzat una regla que identifica, des del punt de vista de la seguretat, configuracions de connexió de xarxa incorrectes a Android que permeten la comunicació amb el servidor mitjançant el protocol HTTP. Exemple d'una configuració que conté un atribut cleartextTrafficPermitted amb significat 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>

Una tasca: Limiteu els resultats pel nom/ruta del fitxer

solució: En un dels grans projectes relacionats amb el desenvolupament d'una aplicació mòbil per a Android, ens vam trobar amb falsos positius de la regla que determina la configuració d'ofuscament. El fet és que la regla fora de la caixa cerca al fitxer build.gradle una configuració responsable d'aplicar regles d'ofuscament per a la versió de llançament de l'aplicació.

Però en projectes grans de vegades hi ha fitxers fills build.gradle, que fan referència a les biblioteques incloses en el projecte. La particularitat és que, fins i tot si aquests fitxers no indiquen la necessitat d'ofuscament, la configuració del fitxer de muntatge pare s'aplicarà durant la compilació.

Per tant, la tasca és tallar els activadors dels fitxers fills que pertanyen a les biblioteques. Es poden identificar per la presència de la línia apply 'com.android.library'.

Exemple de codi del fitxer build.gradle, que determina la necessitat d'ofuscament:

apply plugin: 'com.android.application'

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

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

dependencies {
  ...
}

Exemple de fitxer build.gradle per a una biblioteca inclosa al projecte que no té aquesta configuració:

apply plugin: 'android-library'

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

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

I la regla per a 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);
		}
	}
}

Aquest enfocament pot ser bastant universal i útil no només per a aplicacions d'Android, sinó també per a altres casos en què necessiteu determinar si un resultat pertany a un fitxer específic.

Una tasca: Afegiu suport per a una biblioteca de tercers si la sintaxi no és totalment compatible

solució: El nombre de diferents marcs que s'utilitzen en el procés d'escriptura de codi és simplement fora dels gràfics. Per descomptat, Checkmarx no sempre sap de la seva existència, i la nostra tasca és ensenyar-li a entendre que determinats mètodes pertanyen específicament a aquest marc. De vegades això es complica pel fet que els frameworks utilitzen noms de funcions que són molt comuns i és impossible determinar sense ambigüitats la relació d'una trucada concreta amb una biblioteca específica.

La dificultat és que la sintaxi d'aquestes biblioteques no sempre es reconeix correctament i cal experimentar per evitar obtenir un gran nombre de falsos positius. Hi ha diverses opcions per millorar la precisió de l'escaneig i resoldre el problema:

  • La primera opció, sabem del cert que la biblioteca s'utilitza en un projecte concret i pot aplicar la regla a nivell d'equip. Però si l'equip decideix adoptar un enfocament diferent o utilitza diverses biblioteques en què els noms de les funcions es superposen, podem obtenir una imatge poc agradable de nombrosos falsos positius.

  • La segona opció és buscar fitxers en què la biblioteca estigui clarament importada. Amb aquest enfocament, podem estar segurs que la biblioteca que necessitem s'utilitza exactament en aquest fitxer.

  • I la tercera opció és utilitzar els dos enfocaments anteriors junts.

Com a exemple, mirem una biblioteca coneguda en cercles estrets taca per al llenguatge de programació Scala, és a dir, la funcionalitat Splicing de valors literals. En general, per passar paràmetres a una consulta SQL, cal utilitzar l'operador $, que substitueix les dades en una consulta SQL preformada. És a dir, de fet, és un anàleg directe de Prepared Statement a Java. Però, si necessiteu construir dinàmicament una consulta SQL, per exemple, si necessiteu passar noms de taules, podeu utilitzar l'operador #$, que substituirà directament les dades a la consulta (gairebé com la concatenació de cadenes).

Codi de mostra:

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

Checkmarx encara no sap com detectar l'ús de Splicing Literal Values ​​i salta els operadors #$, així que intentem ensenyar-li a identificar possibles injeccions SQL i ressaltar els llocs adequats del codi:

// Находим все импорты
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));
}

Una tasca: Cerqueu funcions vulnerables utilitzades a les biblioteques de codi obert

solució: Moltes empreses utilitzen eines de monitorització de codi obert (pràctica OSA) per detectar l'ús de versions vulnerables de biblioteques en aplicacions desenvolupades. De vegades no és possible actualitzar aquesta biblioteca a una versió segura. En alguns casos hi ha limitacions funcionals, en altres no hi ha cap versió segura. En aquest cas, una combinació de pràctiques SAST i OSA ajudarà a determinar que les funcions que condueixen a l'explotació de la vulnerabilitat no s'utilitzen al codi.

Però de vegades, especialment quan es té en compte JavaScript, pot ser que aquesta no sigui una tasca completament trivial. A continuació es mostra una solució, potser no ideal, però tanmateix funcionant, utilitzant l'exemple de vulnerabilitats del component lodash en mètodes template и *set.

Exemples de prova de codi potencialment vulnerable en un fitxer 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!'

I quan es connecta directament 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>

Estem buscant tots els nostres mètodes vulnerables, que s'enumeren a vulnerabilitats:

// Ищем все строки: в которых встречается строка 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));

Una tasca: Cerca de certificats incrustats a l'aplicació

solució: No és estrany que les aplicacions, especialment les mòbils, utilitzin certificats o claus per accedir a diversos servidors o verificar SSL-Pinning. Des d'una perspectiva de seguretat, emmagatzemar aquestes coses al codi no és la millor pràctica. Intentem escriure una regla que cerqui fitxers similars al repositori:

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

Una tasca: Trobar fitxes compromeses a l'aplicació

solució: Sovint és necessari revocar els testimonis compromesos o altra informació important que hi ha al codi. Per descomptat, emmagatzemar-los dins del codi font no és una bona idea, però les situacions varien. Gràcies a les consultes CxQL, trobar coses com aquesta és bastant fàcil:

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

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

Conclusió

Espero que aquest article sigui útil per a aquells que comencen a conèixer l'eina Checkmarx. Potser els que han estat escrivint les seves pròpies regles durant molt de temps també trobaran alguna cosa útil en aquesta guia.

Malauradament, actualment falta un recurs on es puguin obtenir noves idees durant el desenvolupament de regles per a Checkmarx. Per això hem creat repositori a Github, on penjarem el nostre treball perquè tothom que faci servir CxQL hi trobi alguna cosa útil, i també tingui l'oportunitat de compartir el seu treball amb la comunitat. El repositori està en procés d'omplir i estructurar el contingut, així que els col·laboradors són benvinguts!

Gràcies!

Font: www.habr.com

Afegeix comentari