Kuidas kirjutada Checkmarxi jaoks reegleid ilma hulluks minemata

Tere Habr!

Meie ettevõte tegeleb oma töös väga sageli erinevate staatilise koodi analüüsi tööriistadega (SAST). Karbist välja võttes töötavad need kõik keskmiselt. Muidugi oleneb kõik projektist ja selles kasutatavatest tehnoloogiatest ning ka sellest, kui hästi need tehnoloogiad on analüüsireeglitega kaetud. Minu arvates on SAST-i tööriista valimisel üks olulisemaid kriteeriume võimalus seda kohandada vastavalt oma rakenduste spetsiifikale, nimelt kirjutada ja muuta analüüsireegleid või, nagu neid sagedamini nimetatakse, kohandatud päringuid.

Kuidas kirjutada Checkmarxi jaoks reegleid ilma hulluks minemata

Kõige sagedamini kasutame Checkmarxi – väga huvitavat ja võimsat koodianalüsaatorit. Selles artiklis räägin oma kogemusest selle jaoks analüüsireeglite kirjutamisel.

Sisukord

Kanne

Alustuseks tahaksin soovitada üht vähestest venekeelsetest artiklitest Checkmarxi päringute kirjutamise funktsioonide kohta. See avaldati Habré's 2019. aasta lõpus pealkirja all: "Tere, Checkmarx!" Kuidas kirjutada Checkmarxi SAST-päring ja leida lahedaid turvaauke.

Selles uuritakse üksikasjalikult, kuidas kirjutada mõne testirakenduse jaoks esimesi päringuid CxQL-is (Checkmarx Query Language), ja näidatakse analüüsireeglite tööpõhimõtteid.

Ma ei hakka selles kirjeldatut kordama, kuigi mõned ristmikud jäävad siiski alles. Püüan oma artiklis koostada omamoodi "retseptide kogu" - loendi lahendustest konkreetsetele probleemidele, millega ma Checkmarxiga töötamise ajal kokku puutusin. Ma pidin oma ajusid murrama paljude nende probleemide üle. Mõnikord ei olnud dokumentatsioonis piisavalt teavet ja mõnikord oli isegi raske aru saada, kuidas nõutavat teha. Loodan, et minu kogemus ja magamata ööd ei ole asjatud ning see “Kohandatud päringute retseptide kogu” säästab teile paar tundi või paar närvirakku. Niisiis, alustame!

Üldine teave reeglite kohta

Kõigepealt vaatame mõnda põhikontseptsiooni ja reeglitega töötamise protsessi, et paremini mõista, mis järgmisena juhtub. Ja ka seetõttu, et dokumentatsioon ei ütle selle kohta midagi või on struktuuris väga laialivalguv, mis pole eriti mugav.

  1. Reegleid rakendatakse skannimise ajal sõltuvalt alguses valitud eelseadist (aktiivsete reeglite komplekt). Saate luua piiramatul arvul eelseadistusi ja nende struktureerimine sõltub teie protsessi spetsiifikast. Saate need rühmitada keele järgi või valida iga projekti jaoks eelseaded. Aktiivsete reeglite arv mõjutab skannimise kiirust ja täpsust.

    Kuidas kirjutada Checkmarxi jaoks reegleid ilma hulluks minemataEelseadistuse seadistamine Checkmarxi liideses

  2. Reegleid redigeeritakse spetsiaalses tööriistas nimega CxAuditor. See on töölauarakendus, mis loob ühenduse Checkmarxi töötava serveriga. Sellel tööriistal on kaks töörežiimi: reeglite redigeerimine ja juba tehtud skannimise tulemuste analüüs.

    Kuidas kirjutada Checkmarxi jaoks reegleid ilma hulluks minemataCxAuditi liides

  3. Checkmarxi reeglid on jagatud keele järgi, see tähendab, et igal keelel on oma päringute komplekt. On ka mõned üldised reeglid, mis kehtivad olenemata keelest, need on nn põhipäringud. Enamasti hõlmavad põhipäringud teabe otsimist, mida teised reeglid kasutavad.

    Kuidas kirjutada Checkmarxi jaoks reegleid ilma hulluks minemataReeglite jagamine keele järgi

  4. Reeglid on "käivitatav" ja "mittetäittav" (täidetud ja täitmata). Minu arvates pole see päris õige nimi, aga nii see on. Lõpptulemus on see, et „käivitatavate” reeglite täitmise tulemus kuvatakse kasutajaliidese skannimistulemustes ja „mittekäivitatavad” reeglid on vajalikud ainult nende tulemuste kasutamiseks muudes päringutes (sisuliselt lihtsalt funktsioonina).

    Kuidas kirjutada Checkmarxi jaoks reegleid ilma hulluks minemataReegli tüübi määramine loomisel

  5. Saate luua uusi reegleid või täiendada/ümber kirjutada olemasolevaid. Reegli ümberkirjutamiseks peate selle puust üles leidma, paremklõpsake ja valige rippmenüüst "Alista". Siinkohal on oluline meeles pidada, et uued reeglid ei sisaldu algselt eelseadistustes ega ole aktiivsed. Nende kasutamise alustamiseks peate need aktiveerima instrumendi menüüs "Preset Manager". Ümberkirjutatud reeglid säilitavad oma sätted, st kui reegel oli aktiivne, jääb see selliseks ja rakendub kohe.

    Kuidas kirjutada Checkmarxi jaoks reegleid ilma hulluks minemataNäide uuest reeglist eelseadehalduri liideses

  6. Täitmise ajal ehitatakse päringute "puu", mis sõltub sellest, millest. Esiteks täidetakse reeglid, mis koguvad teavet, ja seejärel need, kes seda kasutavad. Täitmise tulemus salvestatakse vahemällu, nii et kui on võimalik kasutada olemasoleva reegli tulemusi, on parem seda teha, see vähendab skannimisaega.

  7. Reegleid saab rakendada erinevatel tasanditel:

  • Kogu süsteemi jaoks - kasutatakse mis tahes projekti skannimiseks

  • Meeskonna tasemel (meeskond) - kasutatakse ainult valitud meeskonna projektide skannimiseks.

  • Projekti tasemel – rakendatakse konkreetses projektis

    Kuidas kirjutada Checkmarxi jaoks reegleid ilma hulluks minemataReegli rakendamise taseme määramine

"Sõnastik" algajatele

Ja alustan mõnest asjast, mis tekitasid minus küsimusi, ja näitan ka mitmeid tehnikaid, mis elu oluliselt lihtsustavad.

Tehted loenditega

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

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

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

Kõik leitud esemed

Skannitud keeles saate hankida nimekirja absoluutselt kõigist elementidest, mille Checkmarx on tuvastanud (stringid, funktsioonid, klassid, meetodid jne). See on objektide ruum, mille kaudu pääseb juurde All. See tähendab, et otsida konkreetse nimega objekti searchMe, saate otsida kõigi leitud objektide hulgast näiteks nime järgi:

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

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

Kuid kui teil on vaja otsida mõnes muus keeles, mida skannimisel mingil põhjusel ei kaasatud (näiteks Androidi projektis groovy), saate meie objektiruumi laiendada muutuja kaudu:

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

Vooluanalüüsi funktsioonid

Neid funktsioone kasutatakse paljudes reeglites ja siin on väike petuleht nende tähendusest:

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

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

Faili nime/tee hankimine

Päringu tulemuste põhjal on võimalik saada mitmeid atribuute (faili nimi, millest kirje leiti, string jne), kuid dokumentatsioonis pole kirjas, kuidas neid hankida ja kasutada. Nii et selleks peate pääsema ligi LinePragma atribuudile ja selle sees asuvad meile 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);

Seda tasub meeles pidada FileName sisaldab tegelikult faili teed, kuna kasutasime meetodit GetFirstGraph.

Täitmise tulemus

CxQL-i sees on spetsiaalne muutuja result, mis tagastab teie kirjutatud reegli täitmise tulemuse. See lähtestatakse kohe ja saate sellesse kirjutada vahetulemusi, muutes ja täpsustades neid töö käigus. Kuid kui reegli sees pole sellele muutujale või funktsioonile määramist return— täitmise tulemus on alati null.

Järgmine päring ei tagasta meile midagi täitmise tulemusel ja jääb alati tühjaks:

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

Kuid pärast täitmistulemuse määramist maagilise muutuja tulemusele, näeme, mida see kõne meile tagastab:

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

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

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

Teiste reeglite tulemuste kasutamine

Checkmarxi reegleid võib nimetada analoogseteks funktsioonidega tavalises programmeerimiskeeles. Reegli kirjutamisel võite kasutada ka teiste päringute tulemusi. Näiteks pole vaja koodist iga kord otsida kõiki meetodikutseid, lihtsalt helistage soovitud reegel:

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

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

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

Probleemide lahendus

Logimine

Tööriistaga töötades ei ole mõnikord võimalik soovitud päringut kohe kirjutada ja peate katsetama, proovides erinevaid võimalusi. Sellisel juhul pakub tööriist logimist, mida nimetatakse järgmiselt:

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

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

Kuid tasub meeles pidada, et see meetod aktsepteerib ainult sisendit string, seega ei ole esimese toimingu tulemusel võimalik kuvada leitud elementide täielikku loendit. Teine võimalus, mida kasutatakse silumiseks, on aeg-ajalt maagilise muutuja määramine result päringu tulemuse ja vaata, mis juhtub. See lähenemine pole eriti mugav; peate olema kindel, et pärast seda pole koodis alistamisi ega toiminguid result või lihtsalt kommenteerige allolevat koodi. Või võite nagu mina unustada mitu sellist kõnet valmisreeglist eemaldada ja mõelda, miks miski ei tööta.

Mugavam viis on kutsuda meetod return vajaliku parameetriga. Sel juhul reegli täitmine lõppeb ja saame näha, mis meie kirjutatu tulemusel juhtus:

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

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

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

Sisselogimisprobleem

On olukordi, kus te ei pääse juurde CxAuditi tööriistale (mida kasutatakse reeglite kirjutamiseks). Sellel võib olla palju põhjuseid, sealhulgas kokkujooksmised, Windowsi äkilised värskendused, BSOD ja muud ettenägematud olukorrad, mis ei ole meie kontrolli all. Sellisel juhul on mõnikord andmebaasis mõni lõpetamata seanss, mis takistab uuesti sisse logimist. Selle parandamiseks peate käivitama mitu päringut:

Checkmarxi puhul enne 8.6:

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

Checkmarxi puhul pärast 8.6:

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

Kirjutamise reeglid

Nüüd jõuame kõige huvitavama osani. Kui hakkate CxQL-is reegleid kirjutama, pole teil sageli puudu mitte niivõrd dokumentatsioonist, kuivõrd mõnest elavast näitest teatud probleemide lahendamisest ja päringute üldise toimimise protsessi kirjeldamisest.

Püüan nende elu veidi lihtsamaks teha, kes hakkavad päringukeelde sukelduma ja toon mitmeid näiteid kohandatud päringute kasutamisest teatud probleemide lahendamiseks. Mõned neist on üsna üldised ja teie ettevõttes kasutatavad praktiliselt ilma muudatusteta, teised on spetsiifilisemad, kuid neid saab kasutada ka koodi muutmisel vastavalt oma rakenduste spetsiifikale.

Niisiis, siin on probleemid, millega me kõige sagedamini kokku puutusime:

Ülesanne: Reegli täitmise tulemustes on mitu voogu ja üks neist on teise pesakond, peate ühe neist lahkuma.

lahendus: Tõepoolest, mõnikord näitab Checkmarx mitut andmevoogu, mis võivad kattuda ja olla teiste lühendatud versioon. Sellistel juhtudel on olemas spetsiaalne meetod ReduceFlow. Sõltuvalt parameetrist valib see lühima või pikima voolu:

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

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

Ülesanne: Laiendage tundlike andmete loendit, millele tööriist reageerib

lahendus: Checkmarxil on põhireeglid, mille tulemusi kasutavad paljud teised päringud. Täiendades mõnda neist reeglitest teie rakendusele omaste andmetega, saate kohe skannimistulemusi parandada. Alustuseks on allpool toodud näide reeglist.

Üldine_privaatsuse_rikkumiste_loend

Lisame mitu muutujat, mida meie rakenduses kasutatakse tundliku teabe salvestamiseks:

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

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

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

Ülesanne: Laiendage muutujate loendit paroolidega

lahendus: Soovitaksin koheselt tähelepanu pöörata põhireeglile paroolide koodis defineerimisel ja lisada sellele nimekiri muutujate nimedest, mida teie ettevõttes tavaliselt kasutatakse.

Parooli_privaatsuse_rikkumiste_loend

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

Ülesanne: Lisage kasutatud raamistikud, mida Checkmarx ei toeta

lahendus: Kõik Checkmarxi päringud on jagatud keelte järgi, seega peate iga keele jaoks lisama reeglid. Allpool on mõned näited sellistest reeglitest.

Kui kasutatakse teeke, mis täiendavad või asendavad standardseid funktsioone, saab neid hõlpsasti põhireeglisse lisada. Siis saavad kõik, kes seda kasutavad, kohe uute tutvustuste kohta teada. Näiteks Androidi sisselogimise teegid on Timber ja Loggi. Põhipaketis pole süsteemiväliste kõnede tuvastamiseks reegleid, nii et kui logisse satub parool või seansi identifikaator, ei saa me sellest teada. Proovime lisada selliste meetodite definitsioonid Checkmarxi reeglitele.

Testkoodi näide, mis kasutab logimiseks Timberi teeki:

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

Ja siin on näide Checkmarxi päringust, mis võimaldab teil lisada rakenduse andmete väljumispunktina Timber-meetodite kutsumise määratluse:

Leidke Androidi väljundid

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

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

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

Ja saate lisada ka naaberreegli, kuid see on otseselt seotud Androidi sisselogimisega:

Otsige üles AndroidLog_Outputs

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

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

Samuti kui Androidi rakendused kasutavad Tööjuht asünkroonse töö jaoks on hea mõte sellest Checkmarxi täiendavalt teavitada, lisades meetodi ülesandest andmete hankimiseks getInputData:

Leidke AndroidRead

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

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

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

Ülesanne: Tundlike andmete otsimine iOS-i projektide loendist

lahendus: iOS kasutab erinevate muutujate ja väärtuste salvestamiseks sageli spetsiaalseid faile laiendiga .plist. Paroolide, žetoonide, võtmete ja muude tundlike andmete salvestamine nendesse failidesse ei ole soovitatav, kuna neid saab seadmest probleemideta välja tõmmata.

Plist-failidel on funktsioone, mis pole palja silmaga nähtavad, kuid on Checkmarxi jaoks olulised. Kirjutame reegli, mis otsib meile vajalikud andmed ja annab teada, kui kuskil on mainitud paroole või žetoone.

Sellise faili näide, mis sisaldab taustateenusega suhtlemise luba:

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

Ja reegel Checkmarxi jaoks, millel on mitmeid nüansse, 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);

Ülesanne: Teabe otsimine XML-is

lahendus: Checkmarxil on väga mugavad funktsioonid XML-iga töötamiseks ning väärtuste, siltide, atribuutide ja muu otsimiseks. Kuid kahjuks oli dokumentatsioonis viga, mille tõttu ei tööta ükski näide. Vaatamata asjaolule, et see defekt on dokumentatsiooni uusimas versioonis kõrvaldatud, olge dokumentide varasemate versioonide kasutamisel ettevaatlik.

Siin on vale näide dokumentatsioonist:

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

Täitmiskatse tulemusena saame veateate, et All sellist meetodit pole... Ja see on tõsi, kuna XML-iga töötamiseks on funktsioonide kasutamiseks spetsiaalne eraldi objektiruum - cxXPath. Selline näeb välja õige päring, et leida Androidis seade, mis lubab kasutada HTTP-liiklust:

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

Vaatame seda veidi üksikasjalikumalt, kuna kõigi funktsioonide süntaks on sarnane, pärast seda, kui olete ühe välja mõelnud, peate lihtsalt valima selle, mida vajate. Niisiis, järjestikku vastavalt parameetritele:

  • "*.xml"— otsitavate failide mask

  • 8 — selle keele id, mille jaoks reeglit kohaldatakse

  • "cleartextTrafficPermitted"- atribuudi nimi xml-s

  • "true" — selle atribuudi väärtus

  • false — regulaaravaldise kasutamine otsimisel

  • true — tähendab, et otsing sooritatakse suur- ja suurtähti ignoreerides, st tõstutundlik

Näitena kasutasime reeglit, mis tuvastab Androidis turvalisuse seisukohast valed võrguühenduse sätted, mis võimaldavad serveriga HTTP-protokolli kaudu suhelda. Näide atribuuti sisaldavast seadistusest cleartextTrafficPermitted tähendusega 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>

Ülesanne: Piirake tulemusi failinime/tee järgi

lahendus: Ühes suures projektis, mis oli seotud Androidi mobiilirakenduse arendamisega, leidsime hägustamise seadet määrava reegli valepositiivseid tulemusi. Fakt on see, et karbist väljas olev reegel otsib failist build.gradle säte, mis vastutab rakenduse väljalaskeversiooni hägustamise reeglite rakendamise eest.

Kuid suurtes projektides on mõnikord alamfaile build.gradle, mis viitavad projekti kaasatud raamatukogudele. Omapära on see, et isegi kui need failid ei näita hägustamise vajadust, rakendatakse kompileerimisel põhikoostefaili sätteid.

Seega on ülesandeks teekidesse kuuluvates alamfailides trigerid ära lõigata. Neid saab tuvastada joone olemasolu järgi apply 'com.android.library'.

Näidiskood failist build.gradle, mis määrab hägustamise vajaduse:

apply plugin: 'com.android.application'

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

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

dependencies {
  ...
}

Faili näide build.gradle projekti kaasatud teegi jaoks, millel pole seda sätet:

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ähenemisviis võib olla üsna universaalne ja kasulik mitte ainult Androidi rakenduste jaoks, vaid ka muudel juhtudel, kui peate kindlaks tegema, kas tulemus kuulub konkreetsesse faili.

Ülesanne: Kui süntaksit täielikult ei toetata, lisage tugi kolmanda osapoole teegile

lahendus: Koodi kirjutamise protsessis kasutatavate erinevate raamistike arv on lihtsalt graafikust väljas. Muidugi ei tea Checkmarx alati nende olemasolust ja meie ülesanne on õpetada teda mõistma, et teatud meetodid kuuluvad konkreetselt sellesse raamistikku. Mõnikord muudab selle keeruliseks asjaolu, et raamistikud kasutavad funktsiooninimesid, mis on väga levinud ja konkreetse kõne seost konkreetse teegiga on võimatu üheselt kindlaks teha.

Raskus seisneb selles, et selliste teekide süntaksit ei tuvastata alati õigesti ja peate katsetama, et vältida suure arvu valepositiivseid tulemusi. Skannimise täpsuse parandamiseks ja probleemi lahendamiseks on mitu võimalust.

  • Esimene võimalus, me teame kindlalt, et teeki kasutatakse konkreetses projektis ja saame reeglit rakendada meeskonna tasemel. Kuid kui meeskond otsustab kasutada teistsugust lähenemist või kasutab mitut teeki, milles funktsioonide nimed kattuvad, saame paljudest valepositiivsetest tulemustest mitte eriti meeldiva pildi

  • Teine võimalus on otsida faile, milles teek on selgelt imporditud. Selle lähenemisviisiga võime olla kindlad, et selles failis kasutatakse täpselt vajalikku teeki.

  • Ja kolmas võimalus on kasutada kahte ülaltoodud lähenemisviisi koos.

Näitena vaatleme kitsastes ringkondades tuntud raamatukogu õlilaik Scala programmeerimiskeele jaoks, nimelt funktsionaalsus Literaalsete väärtuste liitmine. Üldiselt peate SQL-päringule parameetrite edastamiseks kasutama operaatorit $, mis asendab andmed eelvormitud SQL-päringuga. See tähendab, et tegelikult on see Java ettevalmistatud avalduse otsene analoog. Kuid kui teil on vaja dünaamiliselt SQL-päringut koostada, näiteks kui teil on vaja edastada tabelinimesid, saate kasutada operaatorit #$, mis asendab andmed otse päringus (peaaegu nagu stringide ühendamine).

Näidiskood:

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

Checkmarx ei tea veel, kuidas Splicing Literal Values ​​kasutamist tuvastada ja jätab operaatorid vahele #$, nii et proovime seda õpetada tuvastama võimalikke SQL-i süste ja tooma esile õiged 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));
}

Ülesanne: Otsige avatud lähtekoodiga teekides kasutatud haavatavaid funktsioone

lahendus: Paljud ettevõtted kasutavad avatud lähtekoodiga jälgimistööriistu (OSA tava), et tuvastada teekide haavatavate versioonide kasutamist arendatud rakendustes. Mõnikord pole sellist teeki võimalik turvalisele versioonile värskendada. Mõnel juhul on funktsionaalsed piirangud, mõnel juhul pole turvalist versiooni üldse. Sel juhul aitab SAST-i ja OSA tavade kombinatsioon kindlaks teha, et koodis ei kasutata funktsioone, mis viivad haavatavuse ärakasutamiseni.

Kuid mõnikord, eriti JavaScripti silmas pidades, ei pruugi see olla täiesti triviaalne ülesanne. Allpool on lahendus, võib-olla mitte ideaalne, kuid siiski töötav, kasutades komponendi haavatavuste näidet lodash meetodites template и *set.

Näited potentsiaalselt haavatava koodi testimiseks 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 kui ühendate otse html-is:

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

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

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

Ülesanne: Rakendusse manustatud sertifikaatide otsimine

lahendus: Pole haruldane, et rakendused, eriti mobiilsed, kasutavad erinevatele serveritele juurdepääsuks või SSL-kinnituse kontrollimiseks sertifikaate või võtmeid. Turvalisuse seisukohast ei ole selliste asjade koodi salvestamine parim tava. Proovime kirjutada reegli, mis otsib hoidlast sarnaseid faile:

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

Ülesanne: Rakenduses ohustatud žetoonide leidmine

lahendus: Sageli on vaja tühistada ohustatud märgid või muu koodis sisalduv oluline teave. Loomulikult ei ole nende salvestamine lähtekoodi sees hea mõte, kuid olukorrad on erinevad. Tänu CxQL-päringutele on selliste asjade leidmine üsna lihtne:

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

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

Järeldus

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

Kahjuks napib praegu ressurssi, kust saaks Checkmarxi reeglite väljatöötamise käigus uusi ideid ammutada. Sellepärast me lõimegi hoidla Githubis, kuhu postitame oma tööd, et kõik, kes CxQL-i kasutavad, leiaksid sealt midagi kasulikku ning saaksid ka oma töid kogukonnaga jagada. Repositoorium on sisu täitmisel ja struktureerimisel, seega on kaastöölised oodatud!

Tänan teid tähelepanu eest!

Allikas: www.habr.com

Lisa kommentaar