Kuidas kirjutada reegleid Checkmarxile ja mitte hulluks minna

Tere, Habr!

Oma töös tegeleb meie ettevõte väga sageli erinevate staatilise koodi analüüsi tööriistadega (SAST). Nende lähteversioonid töötavad kõik keskmiselt. Loomulikult sõltub see projektist ja kasutatavast tehnoloogiast, samuti sellest, kui hästi need tehnoloogiad on analüüsi reeglitega kaetud. Minu arvates on üks peamisi kriteeriume SAST-tööriista valimisel selle kohandamisvõime, see tähendab võimalus kirjutada ja muuta analüüsi reegleid või, nagu neid sagedamini nimetatakse, Custom Queries.

Kuidas kirjutada reegleid Checkmarxile ja mitte hulluks minna

Me kasutame kõige sagedamini Checkmarxi — väga huvitavat ja võimsat koodi analüsaatorit. Selles artiklis räägin oma kogemusest reeglite kirjutamisest selle jaoks.

Sisukord

Sissejuhatus

Alustuseks soovitaksin ühte vähestest venekeelsetest artiklitest Checkmarxi päringute kirjutamise eripäradest. See avaldati Habr' s 2019. aasta lõpus pealkirjaga: „Tere, Checkmarx!“. Kuidas kirjutada päring Checkmarx SAST jaoks ja leida ägedaid haavatavusi.

Selles käsitletakse üksikasjalikult, kuidas kirjutada esimesi päringuid CxQL (Checkmarx Query Language) keeles mõne testrakenduse jaoks ja näidatakse analüüsi reeglite töö põhiprintsiipe.

Ma ei hakka kordama seda, mis seal on kirjeldatud, kuigi mõned kattumised siiski esinevad. Oma artiklis püüan koostada mingisuguse „retseptikogu“, loetelu konkreetsetest probleemide lahendustest, millega olen oma Checkmarxiga töötamise ajal kokku puutunud. Paljude nende ülesannete puhul pidin tõeliselt pead murdma. Mõnikord ei olnud dokumentatsioonis piisavalt teavet, aga mõnikord oli ka üldiselt raske mõista, kuidas teha seda, mis on vajalik. Loodan, et minu kogemus ja unetud ööd ei lähe raisku ning see „Custom Queries retseptikogu“ säästab teile paar tundi või paar närvirakku. Nii et alustame!

Üldine teave reeglite kohta

Kuna alustame mitmete põhiliste mõistete ja reeglite tööprotsessi vaatamisest, et paremini mõista, mis edasi toimub. Ja veel, sest dokumentatsioonis pole sellest öeldud või see on struktuuris liiga laiali venitatud, mis ei ole just mugav.

  1. Reeglid rakenduvad skaneerimise käigus sõltuvalt valitud algseadistusest (aktiivsete reeglite kogumist). Seadistusi saab luua piiramatult ning nende struktuur sõltub teie protsessi omadustest. Need võib grupeerida keele järgi või eraldada iga projekti jaoks. Aktiivsete reeglite arv mõjutab skaneerimise kiirus ja täpsust.

    Kuidas kirjutada reegleid Checkmarxile ja mitte hulluks minnaPreset'i seadistamine Checkmarxi liideses

  2. Reegleid redigeeritakse spetsiaalses tööriistas nimega CxAuditor. See on töölauarakendus, mis ühendub Checkmarxi serveriga. Sellel tööriistal on kaks töörežiimi: reeglite redigeerimine ja juba läbiviidud skaneerimise tulemuste analüüs.

    Kuidas kirjutada reegleid Checkmarxile ja mitte hulluks minnaCxAudit'i liides

  3. Reeglid Checkmarx’is on jagatud keeltesse, mis tähendab, et iga keele jaoks on oma päringute kogum. Samuti on mõned üldised reeglid, mis kehtivad sõltumatult keelest, need on nn põhiküsimused. Enamasti sisaldavad põhiküsimused teabe otsimist, mida kasutavad teised reeglid.

    Kuidas kirjutada reegleid Checkmarxile ja mitte hulluks minnaReeglite jaotamine keeltesse

  4. Reeglid võivad olla "Executable" ja "Non-Executable" (Teostatavad ja Mitte teostatavad). Minu arvates pole see täpne nimetus, kuid mis seal ikka. Tegelik sisu on see, et "Executable" reeglite täitmise tulemused kuvatakse skaneerimise tulemustes kasutajaliideses, samas kui "Non-Executable" reeglid on vajalikud ainult nende tulemuste kasutamiseks teistes päringutes (sisuliselt - lihtsalt funktsioon).

    Kuidas kirjutada reegleid Checkmarxile ja mitte hulluks minnaReegli tüübi määramine loomisel

  5. Uute reeglite loomine või olemasolevate täiendamine/taaselustamine on võimalik. Reegli taasalustamiseks tuleb see leida puust, paremklõpsata ja avanevas menüüs valida punkt "Override". Siin on oluline meeles pidada, et uued reeglid pole algselt seadistustes sisse lülitatud ega aktivoitud. Nende kasutamiseks tuleb need aktiveerida tööriista "Preset Manager" menüüs. Taasalustatud reeglid säilitavad oma seaded, st kui reegel oli aktiivne, jääb see aktiivseks ja rakendatakse kohe.

    Kuidas kirjutada reegleid Checkmarxile ja mitte hulluks minnaNäidis uue reegli kohta Preset Manageri liideses

  6. Täitmise ajal ehitatakse "puu" päringutest, mis misest sõltub. Esimesena täidetakse reeglid, mis koguvad teavet, teiseks need, kes seda kasutavad. Täitmise tulemus salvestatakse vahemällu, seega kui on võimalik kasutada olemasoleva reegli tulemusi, on parem seda teha, kuna see vähendab skaneerimise aega.

  7. Reegleid saab rakendada erinevatel tasanditel:

  • Kogu süsteemi tasandil — kasutatakse mis tahes skaneerimisel mis tahes projektis

  • Meeskonna (Team) tasandil — rakendatakse ainult valitud meeskonna projektide skaneerimiseks.

  • Projekti tasandil — rakendatakse konkreetses projektis

    Kuidas kirjutada reegleid Checkmarxile ja mitte hulluks minnaReegli rakendamise taseme määratlemine

„Sõnastik“ algajale

Alustan mitmest asjast, mis on mind vaevanud, ja näitan rida nippe, mis muudavad elu oluliselt lihtsamaks.

Tegevused loenditega

- ühe arvust teise lahutamine (list2 - list1)
* loendite lõikamine (list1 * list2)
+ loendite liitmine (list1 + list2)

& (loogiline JA) - ühendab loendeid vastavuse alusel (list1 & list2), sarnane lõikamisega (list1 * list2)
| (loogiline VÕI) - ühendab loendeid laia otsingu alusel (list1 | list2)

Loenditega ei toimi: ^ && || % / 

Kõik leitud elemendid

Skaneeritava keele kontekstis saab saada täieliku nimekirja kõigist elementidest, mille on määratlenud Checkmarx (read, funktsioonid, klassid, meetodid jne). See on teatud objektide ruum, millele saab viidata läbi Kõik. See tähendab, et objekti otsimiseks kindla nimega searchMe, saab teha näiteks otsingut kõigi leitud objektide nimede järgi:

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

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

Kuid kui on vaja otsida teises keeles, mis mingil põhjusel skaneerimisse ei kuulunud (näiteks groovy Androidi projektis), saab laiendada meie objektide ruumi muutuja kaudu:

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

Analüüsi voogude funktsioonid

Need funktsioonid on paljudes reeglites kasutusel ning siin on väike abimaterjal, mida need tähendavad:

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

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

Faili nime/tee saamine

On mitmeid atribuutide, mida saab saada päringu täitmise tulemustest (faili nimi, kus esinemine leiti, rida jne), kuid kuidas neid saada ja kasutada, ei ole dokumentatsioonis mainitud. Seega, et seda teha, tuleb pöörduda LinePragma omaduse poole ja selle sees on meie jaoks vajalikud objektid:

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

Tasub meeles pidada, et Faili nimi sisaldab tegelikult faili teed, kuna kasutasime meetodit GetFirstGraph.

Täitmise tulemus

CxQL-is on ette nähtud spetsiaalne muutuja result, mis tagastab tulemuse teie kirjutatud reegli täitmisest. See on kohe algselt määratud ja me saame sellesse salvestada vahepealseid tulemusi, muutes ja täpsustades neid töö käigus. Kuid kui reegli sees ei ole sellele muutuja või funktsiooni määramist return— on tulemuseks alati null.

Järgmine päring ei tagasta meile midagi täitmise tulemuses ja on alati tühi:

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

Kuid kui määrame täitmise tulemuse maagilisele muutuja result — näeme, mida see kutsung meile tagastab:

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

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

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

Muude reeglite täitmise tulemuste kasutamine

Reegleid Checkmarx-is saab nimetada tavalise programmeerimiskeele funktsioonide analoogiks. Reegli kirjutamisel saate kasutada teiste päringute tulemusi. Näiteks ei ole vajadust igal korral otsida kõiki meetodite kutsungeid koodis, piisab, kui kutsuda vajalik reegel:

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

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

Selline lähenemine võimaldab koodi lühendada ja oluliselt vähendada reegli täitmise aega.

Probleemide lahendamine

Logimine

Tööriistaga töötamisel ei õnnestu mõnikord kohe vajalikku päringut kirjutada ja tuleb katsetada, proovides erinevaid variante. Selliste juhtumite jaoks on tööriistades ette nähtud logimine, mida kutsutakse järgmisel viisil:

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

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

Kuid tuleb meeles pidada, et see meetod võtab sisendiks ainult stringi, nii et esimese operatsiooni tulemusena leitud elementide täielikku loendit ei saa väljundisse tuua. Teine võimalus, mida kasutatakse tõrkeotsinguks — on aeg-ajalt määrata maagilise muutujaga result päringu täitmise tulemus ja vaadata, mis seisundis see võib olla. Selline lähenemine ei ole eriti mugav, peaksite olema kindel, et järgnevates koodides ei ole ümbersuunamisi või operatsioone selle result või lihtsalt kommenteerima allpool asuva koodi. Võib ka juhtuda, et nagu mina, unustate eemaldada valmis reeglist mõned sellised kutsungid ja imestada, miks miski ei tööta.

Mugavam viis on kutsuda vajalik meetod return õige parameetriga. Sel juhul lõpeb reegli täitmine ja me saame näha, mis tulemusena me oleme saavutanud:

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

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

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

Sisselogimise probleem

On occassions when you're unable to access the CxAudit tool (which is used for writing rules), numerous reasons may be at play, such as an unexpected shutdown, a sudden Windows update, BSOD, and other unforeseen circumstances beyond our control. In such cases, there may be an unfinished session left in the database that prevents re-entry. To resolve this, several queries need to be executed:

For Checkmarx up to 8.6:

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

For Checkmarx after 8.6:

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

Reeglite kirjutamine

Now we’ve reached the most interesting part. When you start writing rules in CxQL, often what’s lacking isn’t so much documentation but rather some real-life examples of solutions for specific tasks and an overall description of how queries work.

I will try to make life a bit easier for those beginning to dive into query language by providing a few examples of using Custom Queries for solving specific tasks. Some of them are quite general and can be applied in your company almost without changes, while others are more specific but can still be used by adapting the code to fit your application's specifics.

So, here are the tasks we've encountered most frequently:

Task: In the results of rule execution, there are several Flows, and one of them is an attachment of another; one of them must be retained.

Lahendus: Indeed, sometimes Checkmarx shows multiple data flow paths that may overlap and be a shortened version of others. For these cases, there is a special method ReduceFlow. Depending on the parameter, it will select the shortest or longest Flow:

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

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

Task: Expand the list of sensitive data that the tool reacts to.

Lahendus: In Checkmarx, there are basic rules whose execution results are utilized by many other queries. By adding data specific to your application to some of these rules, the scanning results can be immediately improved. Below is an example of a rule to start with:

General_privacy_violation_list

Let’s add a few variables used in our application for storing sensitive information:

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

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

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

Task: Expand the list of password variables.

Lahendus: Soovitan kohe tähelepanu pöörata paroolide määramise põhireeglile koodis ja lisada sellele variabelite nimekiri, mida teie ettevõttes tavaliselt kasutatakse.

Password_privacy_violation_list

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

// Täiendame vaikimisi muutuja nimekirja
List  pswdIncludeList = new List{"*password*", "*psw", "psw*", "pwd*", "*pwd", "*authKey*", "pass*", "cipher*", "*cipher", "pass", "adgangskode", "benutzerkennwort", "chiffre", "clave", "codewort", "contrasena", "contrasenya", "geheimcode", "geslo", "heslo", "jelszo", "kennwort", "losenord", "losung", "losungswort", "lozinka", "modpas", "motdepasse", "parol", "parola", "parole", "pasahitza", "pasfhocal", "passe", "passord", "passwort", "pasvorto", "paswoord", "salasana", "schluessel", "schluesselwort", "senha", "sifre", "wachtwoord", "wagwoord", "watchword", "zugangswort", "PAROLACHIAVE", "PAROLA CHIAVE", "PAROLECHIAVI", "PAROLE CHIAVI", "paroladordine", "verschluesselt", "sisma",
                "pincode",
								"pin"};
								
List  pswdExcludeList = new List{"*pass", "*passable*", "*passage*", "*passenger*", "*passer*", "*passing*", "*passion*", "*passive*", "*passover*", "*passport*", "*passed*", "*compass*", "*bypass*", "pass-through", "passthru", "passthrough", "passbytes", "passcount", "passratio"};

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

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

Task: Lisage kasutatavad raamistikud, mida Checkmarx ei toeta

Lahendus: Kõik Checkmarxi päringud on jagatud keelte kaupa, seega tuleb reegleid täiendada iga keele jaoks. Allpool on mõned näited sellistest reeglitest.

Kui kasutatakse teeke, mis täiendab või asendab standardset funktsionaalsust, on need lihtne lisada põhireeglile. Sellega saavad kõik, kes seda kasutavad, koheselt teadlikuks uutest muudatustest. Näiteks Androidi logimise teegid — Timber ja Loggi. Põhivarustuses ei ole mitte-süsteemsete funktsioonide määratlemise reegleid, seega kui parool või sessiooni ID satub logisse, ei saa me sellest aimu. Proovime lisada Checkmarxi reeglitesse selliste meetodite määratlemise.

Katsetusnäide koodist, mis kasutab Timberi teeki logimise jaoks:

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

Siin on näide Checkmarxi päringust, mis võimaldab lisada Timberi meetodite kõnede määratlemist, kui punkt, kust andmeid rakendusest välja juhitakse:

FindAndroidOutputs

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

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

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

Samuti saab lisada naabrusesse reeglit, mis puudutab otseselt Androidi logimist:

FindAndroidLog_Outputs

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

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

Kui Androidi rakendustes kasutatakse WorkManager asünkroonseks töötamiseks, on mõistlik ka Checkmarxile sellest teatada, lisades meetodi andmete saamiseks ülesandest getInputData:

FindAndroidRead

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

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

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

Task: Tundlike andmete otsimine plist-failides iOS projektide jaoks

Lahendus: Sageli kasutatakse iOS-is erinevate muutujate ja väärtuste salvestamiseks spetsiaalseid failiformaate .plist. Paroolide, tokenite, võtmete ja muude tundlike andmete salvestamine nendes failides ei ole soovitatav, kuna need võivad olla seadme käest kergesti välja võetud.

Plist-failidel on omadused, mis pole palja silmaga nähtavad, kuid on Checkmarxi jaoks olulised. Kirjutame reegli, mis otsib vajalikud andmed ja teatab meile, kui kuskil mainitakse paroole või tokenite olemasolu.

Näide sellisest failist, milles on sisestatud token teenusega suhtlemiseks backend:

DeviceDictionary
	
		phone
		iPhone 6s
		
	privatekey
	MIICXAIBAAKBgQCqGKukO1De7zhZj6+

Ja reegel Checkmarxi jaoks, milles on mitmed nüansid, mida tuleks kirjutamisel arvestada:

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

Task: Teabe otsimine XML-ist

Lahendus: Checkmarxil on XML-i ja väärtuste, sõnumite, atribuutide ja muude otsimiseks väga mugavad funktsioonid. Kuid dokumentatsioonis on kahjuks viga, mille tõttu ei toimi ükski näide. Kuigi viimasel versioonil on see puudus parendatud — olge ettevaatlik, kui kasutate varasemaid versioone dokumentidest.

Siin on vale näide dokumentatsioonist:

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

Katsetuse tulemusena saame vea, et Kõik selline meetod puudub... Ja see on tõsi, kuna XML-iga töötamiseks on olemas spetsiaalne, eraldi objektide ruum - cxXPath. Nii näeb välja õige päring Androidis, mis võimaldab HTTP liikluse kasutamist:

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

Uurime natuke lähemalt, kuna süntektil on kõikide funktsioonide puhul sarnane, pärast ühega tutvumist tuleb vaid valida vajalik. Nii, järjestikku parameetrite kaupa:

  • "*.xml"— failimask, mille alusel otsingut teostatakse

  • 8 — keele id, mille jaoks reegel kehtib

  • "cleartextTrafficPermitted"— atribuudi nimi xml-is

  • "true" — selle atribuudi väärtus

  • false — regulaaravaldiste kasutamine otsingus

  • true — tähendab, et otsingut tehakse suuruse järgi, st case-insensitive

Näiteks on kasutatud reeglit, mis määratleb vale, turvalisuse seisukohalt, võrguühenduse seadistused Androidis, mis lubavad suhelda serveriga HTTP protokolli kaudu. Näide seadistusest, mis sisaldab atribuuti cleartextTrafficPermitted väärtusega true:

example.com
        
            
        
            secure.example.com

Task: Otsingut piirata faili nime/tee kaudu

Lahendus: Ühes suuremas projektis, mis on seotud Androidi mobiilirakenduse arendamisega, kohtasime valehäiret reeglile, mis määratleb obfuskeeringu seadistuse. Asi on selles, et reegel otsib pakendist saadud failist build.gradle seadistust, mis vastutab obfuskeerimise reeglite rakendamise eest rakenduse versioonis.

Kuid suurtes projektides on vahel ka tütarfailid, build.gradlemis kuuluvad projektis sisaldatud raamatukogude alla. Eripära on see, et isegi kui nendes failides ei ole obfuskeeringu vajadust, rakendatakse kompileerimise ajal vanemate koostamisfailide seadistusi.

Seega seisneb ülesanne valehäirete välistamises tütarfailides, mis kuuluvad raamatukogude alla. Neid on võimalik määrata, kui leidub rida apply 'com.android.library'.

Koodinäide failist build.gradle, mis määratleb obfuskeeringu vajaduse:

apply plugin: 'com.android.application'

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

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

dependencies {
  ...
}

Näidisfail build.gradle raamatukogu jaoks, mis on projektis kaasatud ja millel ei ole sellist seadet:

apply plugin: 'android-library'

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

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

Ja reegel Checkmarxi jaoks:

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

See lähenemine võib olla piisavalt universaalne ja osutuda kasulikuks mitte ainult Android rakenduste jaoks, vaid ka muudel juhtudel, kus on vajalik määrata tulemuse kuuluvust teatud failile.

Task: Lisada tugi kolmanda osapoole raamatukogule, kui süntaksit ei toetata täielikult

Lahendus: Erinevate raamistikud, mida koodi kirjutamise protsessis kasutatakse, on lihtsalt liiga palju. Loomulikult ei tea Checkmarx alati nende olemasolust ja meie ülesanne on õpetada teda mõistma, et teatud meetodid kuuluvad just sellele raamistikule. Mõnikord raskendab selle ülesande täitmist see, et raamistiku funktsioonide nimed on väga levinud ja ei saa üheselt määrata, kuidas konkreetne kutse konkreetse raamatukoguga seotud on.

Raskus seisneb selles, et selliste raamatukogude süntaksit ei pruugita alati õigesti tuvastada ning tuleb eksperimenteerida, et mitte saada suurt hulka valehäireid. On mitmeid võimalusi, kuidas skaneerimise täpsust parandada ja ülesanne lahendada:

  • Esimene variant, me teame täpselt, et raamatukogu kasutatakse kindlas projektis ja saame rakendada reeglit meeskonna tasandil. Kuid juhul, kui meeskond otsustab kasutada muud lähenemist või kasutab mitut raamatukogu, mille funktsioonide nimed kattuvad, võime saada mitte väga meeldiva pildi arvukatest valehäiretest.

  • Teine variant, rakendada failiotsingut, kus raamatukogu selgelt imporditakse. Sellise lähenemise korral saame olla kindlad, et antud failis kasutatakse meie jaoks vajalikke raamatukogusid.

  • Ja kolmas variant on kahe eelnevalt nimetatud lähenemise samaaegne kasutamine.

Näidisena vaatame üle tuntud kitsastes ringkondades raamatukogu slick Scala programmeerimiskeele jaoks, nimelt funktsionaalsus Splicing Literal ValuesÜldiselt, et edastada parameetreid SQL-päringusse, tuleb kasutada operaatorit $, mis sisestab andmed eelnevalt koostatud SQL-päringusse. Teisisõnu, on see otsene analoog Prepared Statement'ile Java's. Kuid vajadusel, kui on vaja dünaamiliselt konstrueerida SQL-päring, näiteks kui tuleb edastada tabelite nimesid, on võimalik kasutada operaatorit #$, mis sisestab andmed otse päringusse (praktiliselt nagu stringide ühendamine).

Koodinäide:

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

Checkmarx ei suuda veel tuvastada Splicing Literal Values'i ja jätab operaatorid #$, seega püüame õpetada seda tuvastama potentsiaalseid SQL-süstikud ja esile tõstma vajalikud kohad koodis:

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

Task: Haavatavate funktsioonide otsimine avatud lähtekoodiga raamatukogudes

Lahendus: Paljudes ettevõtetes kasutatakse avatud lähtekoodi kontrollimise tööriistu (OSA praktika), mis võimaldavad tuvastada haavatavate versioonide raamatukogude kasutamist arendatavates rakendustes. Mõnikord ei ole võimalik sellist raamatukogu uuendada turvalisele versioonile. Mõnedel juhtudel on funktsionaalsed piirangud, teistel aga ei ole üldse turvalist versiooni. Sellistes olukordades aitab SAST ja OSA praktikate kombinatsioon, mis võimaldab tuvastada, et haavatavuse kasutamisele viivad funktsioonid ei ole koodis kasutusel.

Kuid mõnikord, eriti JavaScripti puhul, võib see olla mitte just triviaalne ülesanne. Allpool on esitatud lahendus, mis ei pruugi olla ideaalne, kuid töötab siiski, näiteks haavatavuste kohta komponendis lodash meetodites template ja *set.

Näited potentsiaalselt haavatavast koodist JS-failis:

/**
 * 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!'

Ja otse HTML-i lisamisel:

<!DOCTYPE html>
<html>
<head>
    <title>Lodashi õpetus</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>

Otsime kõiki meie haavatavaid meetodeid, mis on loetletud haavatavustes:

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

Task: Koodisse sisse kodeeritud sertifikaatide otsimine

Lahendus: Tihti kasutavad rakendused, eriti mobiilsed rakendused, sertifikaate või võtmeid erinevate serverite juurde pääsemiseks või SSL-Pinningi kontrollimiseks. Turvalisuse seisukohalt ei ole selliste asjade hoidmine koodis parim praktika. Proovime kirjutada reegli, mis otsib selliseid faile alla.

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

Task: Otsime rakenduses kompromiteeritud token'e

Lahendus: Sageli tuleb tühistada kompromiteeritud tokenid või muu oluline teave, mis on kodeeritud. Muidugi, nende hoidmine lähtekoodis ei ole parim idee, kuid olukordi tuleb ette erinevaid. Tänu CxQL päringutele on selliste asjade leidmine piisavalt lihtne:

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

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

Kokkuvõte

Loodan, et see artikkel on kasulik neile, kes alustavad oma tutvumist tööriista Checkmarxiga. Võib-olla leiavad ka need, kes on juba pikka aega oma reegleid kirjutanud, sellest juhendist midagi kasulikku.

Kahjuks puudub praegu tõeliselt hea ressurss, kust saaks uusi ideid Checkmarxi reeglite arendamise käigus ammutada. Seetõttu lõime repo GitHubis, kuhu jagame oma saavutusi, et igaüks, kes kasutab CxQL-i, saaks leida sealt midagi kasulikku ning jagada oma töid kogukonnaga. Repositoorium on sisu täiendamise ja struktureerimise protsessis, seega on panustajad teretulnud!

Aitäh tähelepanu eest!

Allikas: habr.com

Osta usaldusväärne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid 🔥 Osta usaldusväärne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid | ProHoster