Kuinka kirjoittaa sääntöjä Checkmarxille menemättä hulluksi

Hei Habr!

Yrityksemme käsittelee työssään hyvin usein erilaisia ​​staattisen koodin analysointityökaluja (SAST). Pakkauksesta otettuna ne kaikki toimivat keskimäärin. Tietysti kaikki riippuu projektista ja siinä käytetyistä teknologioista sekä siitä, kuinka hyvin nämä tekniikat ovat analyysisääntöjen piirissä. Mielestäni yksi tärkeimmistä kriteereistä SAST-työkalua valittaessa on kyky mukauttaa se sovellusten erityispiirteisiin, nimittäin kirjoittaa ja muuttaa analyysisääntöjä tai, kuten niitä usein kutsutaan, mukautettuja kyselyitä.

Kuinka kirjoittaa sääntöjä Checkmarxille menemättä hulluksi

Käytämme useimmiten Checkmarxia - erittäin mielenkiintoista ja tehokasta koodianalysaattoria. Tässä artikkelissa kerron kokemuksistani analyysisääntöjen kirjoittamisesta sille.

sisällysluettelo

Merkintä

Aluksi haluaisin suositella yhtä harvoista venäjänkielisistä artikkeleista Checkmarxin kyselyiden kirjoittamisen ominaisuuksista. Se julkaistiin Habréssa vuoden 2019 lopussa nimellä: "Hei, Checkmarx!" Kuinka kirjoittaa Checkmarx SAST -kysely ja löytää hienoja haavoittuvuuksia.

Siinä tarkastellaan yksityiskohtaisesti, kuinka ensimmäiset kyselyt kirjoitetaan CxQL:ssä (Checkmarx Query Language) joillekin testisovelluksille ja esitetään analyysisääntöjen toiminnan perusperiaatteet.

En toista siinä kuvattua, vaikka joitain risteyksiä on edelleen olemassa. Artikkelissani yritän koota eräänlaisen "reseptikokoelman", luettelon ratkaisuista tiettyihin ongelmiin, joita kohtasin työskennellessään Checkmarxin kanssa. Jouduin pyörittelemään aivojani monien näistä ongelmista. Joskus dokumentaatiossa ei ollut tarpeeksi tietoa, ja joskus oli jopa vaikeaa ymmärtää, miten vaadittiin. Toivon, että kokemukseni ja unettomat yöt eivät ole turhia, ja tämä "mukautetun kyselyn reseptikokoelma" säästää sinulta muutaman tunnin tai pari hermosolua. Joten, aloitetaan!

Yleistä tietoa säännöistä

Katsotaanpa ensin muutamia peruskäsitteitä ja sääntöjen kanssa työskentelyprosessia, jotta ymmärrämme paremmin, mitä seuraavaksi tapahtuu. Ja myös siksi, että dokumentaatio ei kerro tästä mitään tai se on rakenteessa hyvin hajallaan, mikä ei ole kovin kätevää.

  1. Sääntöjä sovelletaan skannauksen aikana käynnistyksessä valitun esiasetuksen mukaan (joukko aktiivisia sääntöjä). Voit luoda rajattoman määrän esiasetuksia, ja niiden tarkka rakenne riippuu prosessisi erityispiirteistä. Voit ryhmitellä ne kielen mukaan tai valita esiasetukset kullekin projektille. Aktiivisten sääntöjen määrä vaikuttaa skannauksen nopeuteen ja tarkkuuteen.

    Kuinka kirjoittaa sääntöjä Checkmarxille menemättä hulluksiPresetin asettaminen Checkmarx-käyttöliittymässä

  2. Sääntöjä muokataan erikoistyökalulla nimeltä CxAuditor. Tämä on työpöytäsovellus, joka muodostaa yhteyden Checkmarxia käyttävään palvelimeen. Tällä työkalulla on kaksi toimintatapaa: sääntöjen muokkaaminen ja jo suoritetun tarkistuksen tulosten analysointi.

    Kuinka kirjoittaa sääntöjä Checkmarxille menemättä hulluksiCxAudit-käyttöliittymä

  3. Checkmarxin säännöt on jaettu kielten mukaan, eli jokaisella kielellä on omat kyselynsä. On myös joitain yleisiä sääntöjä, jotka pätevät kielestä riippumatta, nämä ovat niin sanottuja peruskyselyjä. Useimmiten peruskyselyihin kuuluu muiden sääntöjen käyttämien tietojen etsiminen.

    Kuinka kirjoittaa sääntöjä Checkmarxille menemättä hulluksiSääntöjen jakaminen kielen mukaan

  4. Säännöt ovat "Suoritettava" ja "Ei suoritettavissa" (suoritettu ja ei suoriteta). Ei ihan oikea nimi mielestäni, mutta niin se on. Tärkeintä on, että suoritettavien sääntöjen suorittamisen tulos näytetään käyttöliittymän tarkistustuloksissa, ja "ei-suoritettavat" -säännöt tarvitaan vain tulosten käyttämiseen muissa pyynnöissä (oleellisesti vain funktiona).

    Kuinka kirjoittaa sääntöjä Checkmarxille menemättä hulluksiSääntötyypin määrittäminen luotaessa

  5. Voit luoda uusia sääntöjä tai täydentää/kirjoittaa olemassa olevia sääntöjä. Jotta voit kirjoittaa säännön uudelleen, sinun on löydettävä se puusta, napsauta hiiren kakkospainikkeella ja valitse avattavasta valikosta "Ohita". Tässä on tärkeää muistaa, että uudet säännöt eivät alun perin sisälly esiasetuksiin eivätkä ole aktiivisia. Aloittaaksesi niiden käytön, sinun on aktivoitava ne laitteen "Preset Manager" -valikossa. Uudelleenkirjoitetut säännöt säilyttävät asetuksesi, eli jos sääntö oli aktiivinen, se pysyy sellaisena ja otetaan käyttöön välittömästi.

    Kuinka kirjoittaa sääntöjä Checkmarxille menemättä hulluksiEsimerkki uudesta säännöstä Preset Manager -liittymässä

  6. Suorituksen aikana rakennetaan pyyntöjen "puu", joka riippuu siitä, mitä. Säännöt, jotka keräävät tietoa, toteutetaan ensin ja niitä käyttävät vasta sitten. Suoritustulos tallennetaan välimuistiin, joten jos on mahdollista käyttää olemassa olevan säännön tuloksia, on parempi tehdä niin, tämä lyhentää skannausaikaa.

  7. Sääntöjä voidaan soveltaa eri tasoilla:

  • Koko järjestelmälle - käytetään minkä tahansa projektin skannaukseen

  • Ryhmätasolla (Tiimi) - käytetään vain valitun tiimin projektien skannaamiseen.

  • Projektitasolla - Sovelletaan tietyssä hankkeessa

    Kuinka kirjoittaa sääntöjä Checkmarxille menemättä hulluksiMääritetään taso, jolla sääntöä sovelletaan

"Sanakirja" aloittelijoille

Ja aloitan muutamalla asialla, jotka herättivät kysymyksiä mielessäni, ja näytän myös useita tekniikoita, jotka yksinkertaistavat elämää merkittävästi.

Toiminnot listoilla

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

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

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

Kaikki löydetyt kohteet

Skannatun kielen sisällä saat luettelon ehdottomasti kaikista Checkmarxin tunnistamista elementeistä (merkkijonot, funktiot, luokat, menetelmät jne.). Tämä on objektitila, jonka kautta pääsee käsiksi All. Toisin sanoen kohteen etsiminen tietyllä nimellä searchMe, voit etsiä esimerkiksi nimen perusteella kaikista löydetyistä objekteista:

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

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

Mutta jos sinun on tehtävä haku muulla kielellä, jota ei jostain syystä ollut mukana skannauksessa (esimerkiksi groovy Android-projektissa), voit laajentaa objektitilaamme muuttujan avulla:

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

Flow-analyysin toiminnot

Näitä toimintoja käytetään monissa säännöissä, ja tässä on pieni huijauslehti siitä, mitä ne tarkoittavat:

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

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

Haetaan tiedoston nimi/polku

Kyselyn tuloksista voidaan saada useita attribuutteja (tiedoston nimi, josta merkintä löydettiin, merkkijono jne.), mutta dokumentaatiossa ei kerrota, kuinka ne hankitaan ja miten niitä käytetään. Joten, jotta voit tehdä tämän, sinun on käytettävä LinePragma-omaisuutta ja tarvitsemamme objektit sijaitsevat sen sisällä:

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

Se kannattaa pitää mielessä FileName sisältää itse asiassa tiedoston polun, koska käytimme menetelmää GetFirstGraph.

Toteutustulos

CxQL:n sisällä on erityinen muuttuja result, joka palauttaa kirjoitetun sääntösi suorittamisen tuloksen. Se alustetaan välittömästi ja voit kirjoittaa siihen välituloksia, muuttaa ja tarkentaa niitä työskennellessään. Mutta jos tälle muuttujalle tai funktiolle ei ole määritetty säännön sisällä return— suoritustulos on aina nolla.

Seuraava kysely ei palauta meille mitään suorituksen seurauksena ja on aina tyhjä:

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

Mutta kun suoritustulos on määritetty maagisen muuttujan tulokselle, näemme, mitä tämä kutsu palauttaa meille:

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

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

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

Muiden sääntöjen tulosten käyttäminen

Checkmarxin sääntöjä voidaan kutsua analogisiksi tavallisen ohjelmointikielen funktioille. Kun kirjoitat sääntöä, voit käyttää muiden kyselyiden tuloksia. Esimerkiksi kaikkia menetelmäkutsuja ei tarvitse etsiä koodista joka kerta, vain kutsu haluttu sääntö:

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

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

Tämän lähestymistavan avulla voit lyhentää koodia ja lyhentää merkittävästi säännön suoritusaikaa.

ongelmien ratkaiseminen

Kirjaaminen

Työkalun kanssa työskennellessä ei joskus ole mahdollista kirjoittaa haluttua kyselyä heti ja joudut kokeilemaan eri vaihtoehtoja. Tällaista tapausta varten työkalu tarjoaa kirjauksen, jota kutsutaan seuraavasti:

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

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

Mutta on syytä muistaa, että tämä menetelmä hyväksyy vain syötteen merkkijono, joten ei ole mahdollista näyttää täydellistä luetteloa löydetyistä elementeistä ensimmäisen toiminnon seurauksena. Toinen vaihtoehto, jota käytetään virheenkorjaukseen, on määrittää maaginen muuttuja aika ajoin result kyselyn tuloksen ja katso mitä tapahtuu. Tämä lähestymistapa ei ole kovin kätevä; sinun on varmistettava, että koodissa ei ole ohituksia tai toimintoja tämän jälkeen. result tai yksinkertaisesti kommentoi alla olevaa koodia. Tai voit, kuten minä, unohtaa poistaa useita tällaisia ​​puheluita valmiista säännöstä ja ihmetellä, miksi mikään ei toimi.

Kätevämpi tapa on kutsua menetelmä return vaaditulla parametrilla. Tässä tapauksessa säännön suorittaminen päättyy ja voimme nähdä, mitä tapahtui kirjoittamamme seurauksena:

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

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

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

Kirjautumisongelma

On tilanteita, joissa et voi käyttää CxAudit-työkalua (jolla kirjoitetaan sääntöjä). Tähän voi olla monia syitä, mukaan lukien kaatumiset, äkilliset Windows-päivitykset, BSOD ja muut odottamattomat tilanteet, joihin emme voi vaikuttaa. Tällöin tietokannassa on joskus keskeneräinen istunto, joka estää sinua kirjautumasta uudelleen sisään. Korjataksesi sen, sinun on suoritettava useita kyselyitä:

Checkmarx ennen 8.6:

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

Checkmarx 8.6:n jälkeen:

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

Kirjoittamisen säännöt

Nyt päästään mielenkiintoisimpaan osaan. Kun alat kirjoittaa sääntöjä CxQL:ssä, sinulta puuttuu usein ei niinkään dokumentaatio, vaan eläviä esimerkkejä tiettyjen ongelmien ratkaisemisesta ja kyselyiden toimintaprosessin kuvauksesta yleensä.

Yritän tehdä elämästä hieman helpompaa niille, jotka alkavat sukeltaa kyselykieleen, ja annan useita esimerkkejä mukautettujen kyselyiden käytöstä tiettyjen ongelmien ratkaisemiseen. Jotkut niistä ovat melko yleisiä ja niitä voidaan käyttää yrityksessäsi käytännössä ilman muutoksia, toiset ovat tarkempia, mutta niitä voidaan käyttää myös muuttamalla koodia sovellusten erityispiirteiden mukaan.

Tässä ovat ongelmat, joita kohtasimme useimmin:

tavoite: Säännön suorittamisen tuloksissa on useita Floweja ja yksi niistä on toisen sisäkkäinen, sinun täytyy jättää yksi niistä.

ratkaisu: Joskus Checkmarx näyttää useita tietovirtoja, jotka voivat olla päällekkäisiä ja olla lyhennetty versio muista. Tällaisia ​​tapauksia varten on olemassa erityinen menetelmä ReduceFlow. Parametrista riippuen se valitsee lyhimmän tai pisimmän virtauksen:

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

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

tavoite: Laajenna luettelo arkaluonteisista tiedoista, joihin työkalu reagoi

ratkaisu: Checkmarxilla on perussäännöt, joiden tuloksia käytetään monissa muissa kyselyissä. Täydentämällä joitain näistä säännöistä sovelluskohtaisilla tiedoilla voit parantaa skannaustuloksiasi välittömästi. Alla on esimerkkisääntö, jolla pääset alkuun:

General_privacy_violation_list

Lisätään useita muuttujia, joita sovelluksessamme käytetään arkaluonteisten tietojen tallentamiseen:

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

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

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

tavoite: Laajenna muuttujien luetteloa salasanoilla

ratkaisu: Suosittelen, että kiinnität välittömästi huomiota perussääntöön salasanojen määrittämisestä koodissa ja lisäät siihen listan yrityksessäsi yleisesti käytetyistä muuttujien nimistä.

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

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

tavoite: Lisää käytetyt puitteet, joita Checkmarx ei tue

ratkaisu: Kaikki Checkmarxin kyselyt on jaettu kielten mukaan, joten sinun on lisättävä säännöt kullekin kielelle. Alla on esimerkkejä tällaisista säännöistä.

Jos käytetään kirjastoja, jotka täydentävät tai korvaavat vakiotoimintoja, ne voidaan helposti lisätä perussääntöön. Sitten kaikki sitä käyttävät saavat heti tiedon uusista esittelyistä. Esimerkiksi Androidiin kirjautumisen kirjastot ovat Timber ja Loggi. Peruspaketissa ei ole sääntöjä ei-järjestelmäkutsujen tunnistamiseen, joten jos salasana tai istuntotunniste päätyy lokiin, emme tiedä siitä. Yritetään lisätä tällaisten menetelmien määritelmiä Checkmarxin sääntöihin.

Testikoodiesimerkki, joka käyttää puukirjastoa kirjaamiseen:

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 tässä on esimerkki Checkmarx-pyynnöstä, jonka avulla voit lisätä määritelmän Timber-menetelmien kutsumisesta sovelluksen tietojen poistumispisteeksi:

Etsi Android Outputs

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

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

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

Ja voit myös lisätä naapurisääntöön, mutta tämä liittyy suoraan Androidiin kirjautumiseen:

EtsiAndroidLog_Outputs

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

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

Myös jos Android-sovellukset käyttävät WorkManager asynkronisessa työssä on hyvä idea ilmoittaa tästä lisäksi Checkmarxille lisäämällä menetelmä tietojen saamiseksi tehtävästä getInputData:

Etsi AndroidRead

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

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

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

tavoite: Arkaluontoisten tietojen etsiminen iOS-projektien plististä

ratkaisu: iOS käyttää usein .plist-tunnisteella varustettuja erikoistiedostoja eri muuttujien ja arvojen tallentamiseen. Salasanojen, tokenien, avainten ja muiden arkaluontoisten tietojen tallentamista näihin tiedostoihin ei suositella, koska ne voidaan poimia laitteesta ilman ongelmia.

Plist-tiedostoissa on ominaisuuksia, jotka eivät ole ilmeisiä paljaalla silmällä, mutta ovat tärkeitä Checkmarxille. Kirjoitetaan sääntö, joka etsii tarvitsemamme tiedot ja kertoo, jos salasanoja tai tunnuksia mainitaan jossain.

Esimerkki tällaisesta tiedostosta, joka sisältää tunnuksen viestintää varten taustapalvelun kanssa:

<?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 sääntö Checkmarxille, jossa on useita vivahteita, jotka tulee ottaa huomioon kirjoittaessa:

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

tavoite: Tietojen etsiminen XML:stä

ratkaisu: Checkmarxilla on erittäin käteviä toimintoja XML-työskentelyyn ja arvojen, tunnisteiden, attribuuttien ja muiden etsimiseen. Mutta valitettavasti dokumentaatiossa oli virhe, jonka vuoksi yksikään esimerkki ei toimi. Huolimatta siitä, että tämä vika on poistettu dokumentaation uusimmasta versiosta, ole varovainen, jos käytät asiakirjojen aikaisempia versioita.

Tässä on virheellinen esimerkki dokumentaatiosta:

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

Suoritusyrityksen seurauksena saamme virheilmoituksen, joka All sellaista menetelmää ei ole... Ja tämä on totta, koska XML:n kanssa työskentelyn funktioiden käyttämiseen on erityinen, erillinen objektitila - cxXPath. Tältä näyttää oikea kysely löytää Androidista asetus, joka sallii HTTP-liikenteen käytön:

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

Katsotaanpa sitä hieman yksityiskohtaisemmin, koska kaikkien funktioiden syntaksi on samanlainen, kun olet keksinyt yhden, sinun on vain valittava tarvitsemasi. Joten peräkkäin parametrien mukaan:

  • "*.xml"— haettavien tiedostojen peite

  • 8 — sen kielen tunnus, johon sääntöä sovelletaan

  • "cleartextTrafficPermitted"- määritteen nimi xml-muodossa

  • "true" — tämän määritteen arvo

  • false — säännöllisen lausekkeen käyttö haussa

  • true — tarkoittaa, että haku suoritetaan huomiotta isot ja pienet kirjaimet, eli kirjainkoolla ei ole merkitystä

Esimerkkinä käytimme sääntöä, joka tunnistaa tietoturvan kannalta virheelliset verkkoyhteysasetukset Androidissa, jotka mahdollistavat yhteydenpidon palvelimen kanssa HTTP-protokollan kautta. Esimerkki asetuksesta, joka sisältää määritteen cleartextTrafficPermitted arvoilla 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>

tavoite: Rajoita tuloksia tiedostonimen/polun mukaan

ratkaisu: Yhdessä Android-mobiilisovelluksen kehittämiseen liittyvässä suuressa projektissa havaitsimme vääriä positiivisia hämäräasetuksen määrittävän säännön. Tosiasia on, että "out of the box" -sääntö etsii tiedostosta build.gradle asetus, joka vastaa hämärtymissääntöjen soveltamisesta sovelluksen julkaisuversioon.

Mutta suurissa projekteissa joskus on lapsitiedostoja build.gradle, jotka viittaavat hankkeessa mukana oleviin kirjastoihin. Erikoisuus on, että vaikka nämä tiedostot eivät osoita hämärtämisen tarvetta, emokokoonpanotiedoston asetuksia käytetään kääntämisen aikana.

Siten tehtävänä on katkaista liipaisimet kirjastoihin kuuluvista lapsitiedostoista. Ne voidaan tunnistaa viivan läsnäolosta apply 'com.android.library'.

Esimerkkikoodi tiedostosta build.gradle, joka määrittää hämärtämisen tarpeen:

apply plugin: 'com.android.application'

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

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

dependencies {
  ...
}

Tiedostoesimerkki build.gradle projektiin sisältyvälle kirjastolle, jossa ei ole tätä asetusta:

apply plugin: 'android-library'

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

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

Ja Checkmarxin sääntö:

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

Tämä lähestymistapa voi olla melko yleinen ja hyödyllinen paitsi Android-sovelluksissa, myös muissa tapauksissa, joissa sinun on määritettävä, kuuluuko tulos tiettyyn tiedostoon.

tavoite: Lisää tuki kolmannen osapuolen kirjastolle, jos syntaksia ei tueta täysin

ratkaisu: Koodin kirjoittamisessa käytettyjen erilaisten kehysten määrä on yksinkertaisesti poissa kaavioista. Checkmarx ei tietenkään aina tiedä niiden olemassaolosta, ja meidän tehtävämme on opettaa se ymmärtämään, että tietyt menetelmät kuuluvat nimenomaan tähän kehykseen. Joskus tätä monimutkaistaa se, että kehykset käyttävät funktionimiä, jotka ovat hyvin yleisiä, ja on mahdotonta määrittää yksiselitteisesti tietyn kutsun suhdetta tiettyyn kirjastoon.

Vaikeus on, että tällaisten kirjastojen syntaksia ei aina tunnisteta oikein, ja sinun on kokeiltava välttääksesi suuren määrän vääriä positiivisia. Skannauksen tarkkuuden parantamiseksi ja ongelman ratkaisemiseksi on useita vaihtoehtoja:

  • Ensimmäinen vaihtoehto, tiedämme varmasti, että kirjastoa käytetään tietyssä projektissa ja voimme soveltaa sääntöä ryhmätasolla. Mutta jos tiimi päättää ottaa toisenlaisen lähestymistavan tai käyttää useita kirjastoja, joissa funktioiden nimet menevät päällekkäin, voimme saada epämiellyttävän kuvan lukuisista vääristä positiivisista.

  • Toinen vaihtoehto on etsiä tiedostoja, joissa kirjasto on selvästi tuotu. Tällä lähestymistavalla voimme olla varmoja, että tarvitsemamme kirjastoa käytetään juuri tässä tiedostossa.

  • Ja kolmas vaihtoehto on käyttää kahta yllä olevaa lähestymistapaa yhdessä.

Katsotaanpa esimerkkinä kapeista piireistä tunnettua kirjastoa liukas Scala-ohjelmointikielelle, nimittäin toiminnallisuudelle Kirjaimellisten arvojen liittäminen. Yleensä parametrien välittämiseksi SQL-kyselyyn on käytettävä operaattoria $, joka korvaa tiedot valmiiksi muodostetuksi SQL-kyselyksi. Eli itse asiassa se on Java Prepared Statementin suora analogi. Mutta jos sinun on rakennettava dynaamisesti SQL-kysely, esimerkiksi jos sinun on välitettävä taulukoiden nimiä, voit käyttää operaattoria #$, joka korvaa tiedot suoraan kyselyyn (melkein kuin merkkijonojen yhdistäminen).

Koodiesimerkki:

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

Checkmarx ei vielä osaa havaita Splicing Literal Values ​​-arvojen käyttöä ja ohittaa operaattoreita #$, joten opetetaan se tunnistamaan mahdolliset SQL-injektiot ja korostamaan oikeat kohdat koodissa:

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

tavoite: Etsi käytettyjä haavoittuvia toimintoja avoimen lähdekoodin kirjastoista

ratkaisu: Monet yritykset käyttävät avoimen lähdekoodin seurantatyökaluja (OSA-käytäntö) havaitakseen kirjastojen haavoittuvien versioiden käytön kehitetyissä sovelluksissa. Joskus tällaista kirjastoa ei ole mahdollista päivittää suojattuun versioon. Joissakin tapauksissa on toiminnallisia rajoituksia, toisissa ei ole turvallista versiota ollenkaan. Tässä tapauksessa SAST- ja OSA-käytäntöjen yhdistelmä auttaa määrittämään, että haavoittuvuuden hyväksikäyttöön johtavia toimintoja ei käytetä koodissa.

Mutta joskus, varsinkin kun harkitaan JavaScriptiä, tämä ei ehkä ole täysin triviaali tehtävä. Alla on ratkaisu, joka ei ehkä ole ihanteellinen, mutta kuitenkin toimiva, esimerkkinä komponentin haavoittuvuuksista lodash menetelmissä template и *set.

Esimerkkejä JS-tiedoston mahdollisesti haavoittuvan koodin testaamisesta:

/**
 * 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 kun yhdistät suoraan html:llä:

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

Etsimme kaikkia haavoittuvia menetelmiämme, jotka on lueteltu haavoittuvuuksissa:

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

tavoite: Haetaan sovellukseen upotettuja varmenteita

ratkaisu: Ei ole harvinaista, että sovellukset, erityisesti mobiilisovellukset, käyttävät varmenteita tai avaimia päästäkseen eri palvelimiin tai tarkistaakseen SSL-kiinnityksen. Turvallisuuden näkökulmasta tällaisten asioiden tallentaminen koodiin ei ole paras käytäntö. Yritetään kirjoittaa sääntö, joka etsii samankaltaisia ​​tiedostoja arkistosta:

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

tavoite: Vaarallisten tunnuksien löytäminen sovelluksesta

ratkaisu: Usein on tarpeen peruuttaa vaarantuneet tunnukset tai muut koodissa olevat tärkeät tiedot. Tietenkään niiden tallentaminen lähdekoodiin ei ole hyvä idea, mutta tilanteet vaihtelevat. CxQL-kyselyiden ansiosta tällaisten asioiden löytäminen on melko helppoa:

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

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

Johtopäätös

Toivon, että tämä artikkeli on hyödyllinen niille, jotka aloittavat tutustumisen Checkmarx-työkaluun. Ehkä ne, jotka ovat kirjoittaneet omia sääntöjään pitkään, löytävät tästä oppaasta jotain hyödyllistä.

Valitettavasti tällä hetkellä puuttuu resurssit, josta uusia ideoita voitaisiin poimia Checkmarxin sääntöjen kehittämisen aikana. Siksi loimme arkisto Githubissa, jossa julkaisemme työmme, jotta jokainen CxQL:ää käyttävä löytää siitä jotain hyödyllistä ja heillä on myös mahdollisuus jakaa työnsä yhteisön kanssa. Arkiston sisältöä täydennetään ja strukturoidaan, joten kirjoittajat ovat tervetulleita!

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

Lähde: will.com

Lisää kommentti