Kaip parašyti Checkmarx taisykles neišprotėjus

Sveiki, Habr!

Savo darbe mūsų įmonė labai dažnai susiduria su įvairiais statinio kodo analizės įrankiais (SAST). Iš dėžutės jie visi veikia vidutiniškai. Žinoma, viskas priklauso nuo projekto ir jame naudojamų technologijų, taip pat nuo to, kaip šias technologijas apima analizės taisyklės. Mano nuomone, vienas iš svarbiausių kriterijų renkantis SAST įrankį yra galimybė jį pritaikyti pagal savo taikomųjų programų specifiką, būtent rašyti ir keisti analizės taisykles arba, kaip jos dažniau vadinamos, Custom Queries.

Kaip parašyti Checkmarx taisykles neišprotėjus

Dažniausiai naudojame Checkmarx – labai įdomų ir galingą kodų analizatorių. Šiame straipsnyje papasakosiu apie savo patirtį rašant analizės taisykles.

Turinys

Įrašas

Pirmiausia norėčiau rekomenduoti vieną iš nedaugelio straipsnių rusų kalba apie „Checkmarx“ užklausų rašymo ypatybes. Jis buvo paskelbtas Habré 2019 m. pabaigoje pavadinimu: "Sveikas, Čemarksai!" Kaip parašyti Checkmarx SAST užklausą ir rasti puikių spragų.

Jame išsamiai nagrinėjama, kaip parašyti pirmąsias užklausas CxQL (Checkmarx Query Language) kai kurioms testavimo programoms, ir parodomi pagrindiniai analizės taisyklių veikimo principai.

Nekartosiu, kas jame aprašyta, nors kai kurios sankryžos vis tiek bus. Savo straipsnyje pabandysiu sudaryti savotišką „receptų rinkinį“, konkrečių problemų, su kuriomis susidūriau dirbdamas su „Checkmarx“, sprendimų sąrašą. Turėjau sukti galvą dėl daugelio šių problemų. Kartais dokumentacijoje pritrūkdavo informacijos, o kartais net būdavo sunku suprasti, kaip reikia padaryti, ką reikia. Tikiuosi, kad mano patirtis ir bemiegės naktys nenueis veltui, o šis „Custom Queries receptų rinkinys“ sutaupys kelias valandas ar porą nervų ląstelių. Taigi, pradėkime!

Bendra informacija apie taisykles

Pirmiausia pažvelkime į keletą pagrindinių sąvokų ir darbo su taisyklėmis procesą, kad geriau suprastume, kas nutiks toliau. Taip pat todėl, kad dokumentacijoje apie tai nieko nesakoma arba ji yra labai išsklaidyta struktūroje, o tai nėra labai patogu.

  1. Taisyklės taikomos nuskaitymo metu, atsižvelgiant į pradžioje pasirinktą išankstinį nustatymą (aktyvių taisyklių rinkinys). Galite sukurti neribotą skaičių išankstinių nustatymų, o kaip tiksliai juos struktūrizuoti, priklauso nuo jūsų proceso specifikos. Galite juos sugrupuoti pagal kalbą arba pasirinkti išankstinius kiekvieno projekto nustatymus. Aktyvių taisyklių skaičius turi įtakos nuskaitymo greičiui ir tikslumui.

    Kaip parašyti Checkmarx taisykles neišprotėjusIšankstinio nustatymo nustatymas „Checkmarx“ sąsajoje

  2. Taisyklės redaguojamos specialiu įrankiu CxAuditor. Tai darbalaukio programa, kuri jungiasi prie serverio, kuriame veikia „Checkmarx“. Šis įrankis turi du veikimo režimus: redaguoti taisykles ir analizuoti jau atlikto nuskaitymo rezultatus.

    Kaip parašyti Checkmarx taisykles neišprotėjusCxAudit sąsaja

  3. „Checkmarx“ taisyklės yra suskirstytos pagal kalbą, tai yra, kiekviena kalba turi savo užklausų rinkinį. Taip pat yra keletas bendrų taisyklių, kurios galioja nepriklausomai nuo kalbos, tai yra vadinamosios pagrindinės užklausos. Dažniausiai pagrindinės užklausos apima informacijos, kurią naudoja kitos taisyklės, paiešką.

    Kaip parašyti Checkmarx taisykles neišprotėjusTaisyklių skirstymas pagal kalbą

  4. Taisyklės yra „vykdomos“ ir „nevykdomos“ (vykdomos ir nevykdomos). Mano nuomone, ne visai teisingas pavadinimas, bet taip yra. Esmė ta, kad „Vykdomųjų“ taisyklių vykdymo rezultatas bus rodomas vartotojo sąsajos nuskaitymo rezultatuose, o „Nevykdomos“ taisyklės reikalingos tik norint naudoti jų rezultatus kitose užklausose (iš esmės tai tik funkcija).

    Kaip parašyti Checkmarx taisykles neišprotėjusTaisyklės tipo nustatymas kuriant

  5. Galite kurti naujas taisykles arba papildyti/perrašyti esamas. Norėdami perrašyti taisyklę, turite ją rasti medyje, dešiniuoju pelės mygtuku spustelėkite ir išskleidžiamajame meniu pasirinkite „Nepaisyti“. Čia svarbu atsiminti, kad naujos taisyklės iš pradžių nėra įtrauktos į išankstinius nustatymus ir nėra aktyvios. Norėdami pradėti juos naudoti, turite juos suaktyvinti prietaiso meniu „Preset Manager“. Perrašytos taisyklės išlaiko savo nustatymus, tai yra, jei taisyklė buvo aktyvi, ji tokia išliks ir bus taikoma nedelsiant.

    Kaip parašyti Checkmarx taisykles neišprotėjusNaujos taisyklės iš anksto nustatytų tvarkyklės sąsajoje pavyzdys

  6. Vykdymo metu sukuriamas užklausų „medis“, kuris priklauso nuo ko. Taisyklės, kurios renka informaciją, vykdomos pirmiausia, o paskui – tie, kurie ją naudoja. Vykdymo rezultatas yra talpykloje, todėl jei galima naudoti esamos taisyklės rezultatus, tai geriau tai padaryti, tai sumažins nuskaitymo laiką.

  7. Taisyklės gali būti taikomos įvairiais lygiais:

  • Visai sistemai – bus naudojamas bet kokio projekto skenavimui

  • Komandos lygiu (Team) – bus naudojamas tik projektams nuskaityti pasirinktoje komandoje.

  • Projekto lygmeniu – bus taikomas konkrečiame projekte

    Kaip parašyti Checkmarx taisykles neišprotėjusNustatyti, kokio lygio taisyklė bus taikoma

„Žodynas“ pradedantiesiems

Ir aš pradėsiu nuo kelių dalykų, kurie man sukėlė klausimų, taip pat parodysiu keletą metodų, kurie žymiai supaprastins gyvenimą.

Operacijos su sąrašais

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

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

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

Visos rastos prekės

Nuskaitytoje kalboje galite gauti absoliučiai visų elementų, kuriuos nustatė „Checkmarx“ (eilutės, funkcijos, klasės, metodai ir kt.), sąrašą. Tai yra tam tikra objektų erdvė, pro kurią galima patekti All. Tai yra, ieškoti objekto konkrečiu pavadinimu searchMe, galite ieškoti, pavyzdžiui, pagal pavadinimą visuose rastuose objektuose:

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

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

Bet jei jums reikia ieškoti kita kalba, kuri dėl kokių nors priežasčių nebuvo įtraukta į nuskaitymą (pavyzdžiui, groovy Android projekte), galite išplėsti objektų erdvę naudodami kintamąjį:

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

Srauto analizės funkcijos

Šios funkcijos naudojamos daugelyje taisyklių, o čia yra nedidelis jų reikšmės aprašymas:

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

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

Gaunamas failo pavadinimas / kelias

Yra keletas atributų, kuriuos galima gauti iš užklausos rezultatų (failo, kuriame buvo rastas įrašas, pavadinimas, eilutė ir kt.), tačiau dokumentacijoje nenurodyta, kaip juos gauti ir naudoti. Taigi, norėdami tai padaryti, turite pasiekti „LinePragma“ nuosavybę, o jos viduje bus mums reikalingi objektai:

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

Verta tai turėti omenyje FileName iš tikrųjų yra failo kelias, nes mes naudojome šį metodą GetFirstGraph.

Vykdymo rezultatas

CxQL viduje yra specialus kintamasis result, kuris grąžina jūsų parašytos taisyklės vykdymo rezultatą. Jis inicijuojamas iš karto ir į jį galite įrašyti tarpinius rezultatus, keisdami ir tobulindami juos dirbdami. Bet jei taisyklėje nėra šio kintamojo ar funkcijos priskyrimo return— vykdymo rezultatas visada bus lygus nuliui.

Ši užklausa mums nieko negrąžins dėl vykdymo ir visada bus tuščia:

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

Tačiau priskyrę vykdymo rezultatą magiškojo kintamojo rezultatui, pamatysime, ką šis skambutis mums grąžins:

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

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

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

Naudojant kitų taisyklių rezultatus

Checkmarx taisykles galima pavadinti analogiškomis funkcijoms įprastoje programavimo kalboje. Rašydami taisyklę galite naudoti kitų užklausų rezultatus. Pavyzdžiui, nereikia kiekvieną kartą kode ieškoti visų metodų iškvietimų, tiesiog iškvieskite norimą taisyklę:

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

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

Šis metodas leidžia sutrumpinti kodą ir žymiai sutrumpinti taisyklės vykdymo laiką.

Trikčių šalinimas

Miško ruoša

Dirbant su įrankiu kartais nepavyksta iš karto parašyti norimos užklausos ir tenka eksperimentuoti, bandant įvairius variantus. Tokiu atveju įrankis teikia registravimą, kuris vadinamas taip:

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

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

Tačiau verta atsiminti, kad šis metodas priimamas tik kaip įvestis eilutė, todėl atlikus pirmą operaciją nebus galima parodyti viso rastų elementų sąrašo. Antroji parinktis, naudojama derinimui, yra laikas nuo laiko priskirti magišką kintamąjį result užklausos rezultatą ir pažiūrėkite, kas atsitiks. Šis metodas nėra labai patogus; turite būti tikri, kad po to kode nėra jokių nepaisymų ar operacijų su šiuo result arba tiesiog pakomentuokite žemiau esantį kodą. Arba galite, kaip aš, pamiršti pašalinti kelis tokius skambučius iš parengtos taisyklės ir susimąstyti, kodėl niekas neveikia.

Patogesnis būdas yra iškviesti metodą return su reikiamu parametru. Tokiu atveju taisyklės vykdymas baigsis ir pamatysime, kas atsitiko dėl to, ką parašėme:

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

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

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

Prisijungimo problema

Yra situacijų, kai negalite įvesti CxAudit įrankio (kuris naudojamas taisyklėms rašyti). Tam gali būti daug priežasčių, įskaitant gedimus, staigius Windows atnaujinimus, BSOD ir kitas nenumatytas situacijas, kurių mes negalime kontroliuoti. Tokiu atveju kartais duomenų bazėje yra nebaigtas seansas, kuris neleidžia vėl prisijungti. Norėdami tai išspręsti, turite atlikti keletą užklausų:

„Checkmarx“ iki 8.6:

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

„Checkmarx“ po 8.6:

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

Rašymo taisyklės

Dabar pereiname prie įdomiausios dalies. Kai pradedate rašyti taisykles CxQL, jums dažnai trūksta ne tiek dokumentacijos, kiek gyvų pavyzdžių, kaip išspręsti tam tikras problemas ir aprašyti užklausų veikimo procesą apskritai.

Pabandysiu šiek tiek palengvinti gyvenimą tiems, kurie pradeda gilintis į užklausų kalbą, ir pateiksiu keletą pavyzdžių, kaip naudoti Custom Queries sprendžiant tam tikras problemas. Kai kurie iš jų yra gana bendri ir gali būti naudojami jūsų įmonėje praktiškai be pakeitimų, kiti yra konkretesni, tačiau juos galima naudoti ir pakeitus kodą, kad jis atitiktų jūsų taikomųjų programų specifiką.

Taigi, čia yra problemos, su kuriomis susiduriame dažniausiai:

Užduotis: Taisyklės vykdymo rezultatuose yra keli srautai ir vienas iš jų yra kito lizdas, vieną iš jų turite palikti.

sprendimas: Iš tiesų, kartais „Checkmarx“ rodo kelis duomenų srautus, kurie gali sutapti ir būti sutrumpinta kitų. Tokiems atvejams yra specialus metodas ReduceFlow. Priklausomai nuo parametro, jis pasirinks trumpiausią arba ilgiausią srautą:

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

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

Užduotis: Išplėskite jautrių duomenų, į kuriuos reaguoja įrankis, sąrašą

sprendimas: „Checkmarx“ turi pagrindines taisykles, kurių rezultatai naudojami daugelyje kitų užklausų. Kai kurias iš šių taisyklių papildę konkrečiai jūsų programai skirtais duomenimis, galite iš karto pagerinti nuskaitymo rezultatus. Žemiau yra taisyklės pavyzdys, kaip pradėti:

General_privacy_violation_list

Pridėkime kelis kintamuosius, kurie mūsų programoje naudojami neskelbtinai informacijai saugoti:

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

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

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

Užduotis: Išplėskite kintamųjų sąrašą naudodami slaptažodžius

sprendimas: Rekomenduočiau nedelsiant atkreipti dėmesį į pagrindinę slaptažodžių apibrėžimo kode taisyklę ir į ją įtraukti kintamųjų pavadinimų, kurie dažniausiai naudojami jūsų įmonėje, sąrašą.

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

Užduotis: Pridėkite naudotas sistemas, kurių nepalaiko „Checkmarx“.

sprendimas: Visos „Checkmarx“ užklausos yra suskirstytos pagal kalbą, todėl kiekvienai kalbai turite pridėti taisykles. Žemiau pateikiami keli tokių taisyklių pavyzdžiai.

Jei naudojamos bibliotekos, kurios papildo arba pakeičia standartines funkcijas, jas galima lengvai pridėti prie pagrindinės taisyklės. Tada visi juo besinaudojantys iškart sužinos apie naujas įžangas. Pavyzdžiui, bibliotekos, skirtos prisijungti prie „Android“, yra „Timber“ ir „Loggi“. Pagrindiniame pakete nėra taisyklių, kaip identifikuoti nesisteminius skambučius, todėl jei slaptažodis arba seanso identifikatorius pateks į žurnalą, mes apie tai nesužinosime. Pabandykime prie „Checkmarx“ taisyklių pridėti tokių metodų apibrėžimus.

Bandymo kodo pavyzdys, kuris naudoja medienos biblioteką registravimui:

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

Čia yra „Checkmarx“ užklausos pavyzdys, kuris leis jums pridėti „Timber“ metodų iškvietimo apibrėžimą kaip duomenų išėjimo iš programos tašką:

Raskite Android Outputs

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

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

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

Taip pat galite pridėti prie gretimos taisyklės, tačiau ši tiesiogiai susijusi su prisijungimu prie „Android“:

Raskite AndroidLog_Outputs

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

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

Be to, jei naudoja Android programas Darbo vadovas asinchroniniam darbui pravartu papildomai apie tai informuoti „Checkmarx“, pridedant metodą, kaip gauti duomenis iš užduoties. getInputData:

Raskite AndroidRead

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

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

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

Užduotis: Ieškoma neskelbtinų duomenų „iOS“ projektų sąraše

sprendimas: iOS dažnai naudoja specialius failus su plėtiniu .plist įvairiems kintamiesiems ir reikšmėms saugoti. Šiuose failuose nerekomenduojama saugoti slaptažodžių, žetonų, raktų ir kitų jautrių duomenų, nes juos galima be problemų išgauti iš įrenginio.

Plist failai turi funkcijų, kurios nėra akivaizdžios plika akimi, bet yra svarbios „Checkmarx“. Parašykime taisyklę, kuri ieškos mums reikalingų duomenų ir pasakys, ar kur nors minimi slaptažodžiai ar žetonai.

Tokio failo, kuriame yra ryšio su užpakalinės sistemos paslauga prieigos raktas, pavyzdys:

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

Ir „Checkmarx“ taisyklė, kuri turi keletą niuansų, į kuriuos reikia atsižvelgti rašant:

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

Užduotis: Informacijos paieška XML formatu

sprendimas: „Checkmarx“ turi labai patogias funkcijas, skirtas darbui su XML ir reikšmių, žymų, atributų ir kt. Bet, deja, dokumentacijoje buvo klaida, dėl kurios neveikia nei vienas pavyzdys. Nepaisant to, kad šis defektas buvo pašalintas naujausioje dokumentacijos versijoje, būkite atsargūs, jei naudojate ankstesnes dokumentų versijas.

Štai neteisingas pavyzdys iš dokumentacijos:

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

Dėl bandymo vykdyti gausime klaidą, kad All tokio metodo nėra... Ir tai tiesa, nes yra speciali, atskira objektų erdvė, skirta naudoti funkcijas, skirtas darbui su XML - cxXPath. Štai kaip atrodo teisinga užklausa ieškant „Android“ nustatymo, leidžiančio naudoti HTTP srautą:

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

Pažvelkime į tai šiek tiek išsamiau, nes visų funkcijų sintaksė yra panaši, išsiaiškinus vieną, tereikia pasirinkti tą, kurios jums reikia. Taigi, nuosekliai pagal parametrus:

  • "*.xml"— ieškomų failų kaukė

  • 8 — kalbos, kuriai taikoma taisyklė, id

  • "cleartextTrafficPermitted"- atributo pavadinimas xml

  • "true" — šio požymio vertė

  • false — reguliariosios išraiškos naudojimas ieškant

  • true — reiškia, kad paieška bus atliekama ignoruojant didžiąsias ir mažąsias raides, ty neskiriant didžiųjų ir mažųjų raidžių

Kaip pavyzdį panaudojome taisyklę, kuri saugumo požiūriu nustato neteisingus tinklo ryšio nustatymus sistemoje „Android“, leidžiančius palaikyti ryšį su serveriu per HTTP protokolą. Nustatymo su atributu pavyzdys cleartextTrafficPermitted su prasme 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>

Užduotis: Apribokite rezultatus pagal failo pavadinimą / kelią

sprendimas: Viename iš didelių projektų, susijusių su mobiliosios programos, skirtos „Android“, kūrimu, susidūrėme su klaidingais taisyklės, kuri nustato užtemdymo nustatymą, teigiamų rezultatų. Faktas yra tas, kad taisykle atliekama paieška faile build.gradle nustatymas, atsakingas už aptemdymo taisyklių taikymą programos leidimo versijai.

Tačiau dideliuose projektuose kartais yra antrinių failų build.gradle, kurie nurodo į projektą įtrauktas bibliotekas. Ypatumas yra tas, kad net jei šie failai nenurodo, kad reikia užmaskuoti, kompiliavimo metu bus taikomi pirminio surinkimo failo nustatymai.

Taigi užduotis yra išjungti trigerius antriniuose failuose, priklausančiuose bibliotekoms. Juos galima atpažinti pagal linijos buvimą apply 'com.android.library'.

Kodo pavyzdys iš failo build.gradle, kuris nustato užtemdymo poreikį:

apply plugin: 'com.android.application'

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

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

dependencies {
  ...
}

Pavyzdinis failas build.gradle bibliotekai, įtrauktai į projektą, kurioje nėra šio nustatymo:

apply plugin: 'android-library'

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

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

Ir „Checkmarx“ taisyklė:

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

Šis metodas gali būti gana universalus ir naudingas ne tik Android programoms, bet ir kitais atvejais, kai reikia nustatyti, ar rezultatas priklauso konkrečiam failui.

Užduotis: Pridėkite trečiosios šalies bibliotekos palaikymą, jei sintaksė nėra visiškai palaikoma

sprendimas: Įvairių struktūrų, naudojamų kodo rašymo procese, skaičius yra tiesiog nepastebėtas. Žinoma, „Checkmarx“ ne visada žino apie jų egzistavimą, o mūsų užduotis yra išmokyti jį suprasti, kad tam tikri metodai priklauso būtent šiai sistemai. Kartais tai apsunkina tai, kad karkasuose naudojami labai dažni funkcijų pavadinimai ir neįmanoma vienareikšmiškai nustatyti konkretaus iškvietimo ryšio su konkrečia biblioteka.

Sunkumas yra tas, kad tokių bibliotekų sintaksė ne visada atpažįstama teisingai ir jūs turite eksperimentuoti, kad išvengtumėte daugybės klaidingų teigiamų rezultatų. Yra keletas variantų, kaip pagerinti nuskaitymo tikslumą ir išspręsti problemą:

  • Pirmasis variantas, mes tikrai žinome, kad biblioteka naudojama konkrečiame projekte ir gali taikyti taisyklę komandos lygiu. Bet jei komanda nusprendžia imtis kitokio požiūrio arba naudoja kelias bibliotekas, kuriose funkcijų pavadinimai sutampa, galime susidaryti nelabai malonų daugelio klaidingų teigiamų rezultatų vaizdą.

  • Antrasis variantas – ieškoti failų, kuriuose aiškiai importuota biblioteka. Taikydami šį metodą galime būti tikri, kad mums reikalinga biblioteka yra tiksliai naudojama šiame faile.

  • Ir trečiasis variantas yra naudoti du aukščiau pateiktus metodus kartu.

Kaip pavyzdį pažvelkime į siauruose ratuose gerai žinomą biblioteką dėmė Scala programavimo kalbai, būtent funkcionalumui Pažodinių vertybių sujungimas. Apskritai, norėdami perduoti parametrus SQL užklausai, turite naudoti operatorių $, kuris pakeičia duomenis į iš anksto suformuotą SQL užklausą. Tai iš tikrųjų yra tiesioginis „Java“ parengto pareiškimo analogas. Bet jei jums reikia dinamiškai sukurti SQL užklausą, pavyzdžiui, jei reikia perduoti lentelių pavadinimus, galite naudoti operatorių #$, kuris tiesiogiai pakeis duomenis į užklausą (beveik kaip eilutės sujungimas).

Kodo pavyzdys:

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

„Checkmarx“ dar nežino, kaip aptikti „Splicing Literal Values“ naudojimą ir praleidžia operatorius #$, todėl pabandykime jį išmokyti identifikuoti galimas SQL injekcijas ir paryškinti tinkamas vietas kode:

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

Užduotis: Atvirojo kodo bibliotekose ieškokite naudotų pažeidžiamų funkcijų

sprendimas: Daugelis įmonių naudoja atvirojo kodo stebėjimo įrankius (OSA praktika), kad nustatytų pažeidžiamų bibliotekų versijų naudojimą sukurtose programose. Kartais tokios bibliotekos atnaujinti į saugią versiją neįmanoma. Kai kuriais atvejais yra funkcinių apribojimų, kitais atvejais nėra saugios versijos. Tokiu atveju SAST ir OSA praktikų derinys padės nustatyti, kad kode nenaudojamos funkcijos, dėl kurių išnaudojamas pažeidžiamumas.

Tačiau kartais, ypač kalbant apie „JavaScript“, tai gali būti ne visai nereikšminga užduotis. Žemiau pateikiamas sprendimas, galbūt ne idealus, bet vis dėlto veikiantis, naudojant komponento pažeidžiamumo pavyzdį lodash metoduose template и *set.

Galimai pažeidžiamo kodo JS faile pavyzdžiai:

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

O jungiantis tiesiogiai html:

<!DOCTYPE html>
<html>
<head>
    <title>Lodash Tutorial</title>
    <script src="./node_modules/lodash.js"></script>
    <script type="text/javascript">
  // Lodash chunking array
        nums = [1, 2, 3, 4, 5, 6, 7, 8, 9];

        let c1 = _.template('<% print("hello " + js); %>!');
        console.log(c1);

        let c2 = _.template('<% print("hello " + js); %>!');
        console.log(c2);
    </script>
</head>
<body></body>
</html>

Mes ieškome visų mūsų pažeidžiamų metodų, kurie yra išvardyti pažeidžiamuose vietose:

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

Užduotis: Ieškoma programoje įterptų sertifikatų

sprendimas: Neretai programos, ypač mobiliosios, naudoja sertifikatus ar raktus, kad pasiektų įvairius serverius arba patikrintų SSL prisegimą. Saugumo požiūriu tokių dalykų saugojimas kode nėra geriausia praktika. Pabandykime parašyti taisyklę, kuri saugykloje ieškotų panašių failų:

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

Užduotis: Programoje rasti pažeistų žetonų

sprendimas: Dažnai reikia atšaukti pažeistus žetonus ar kitą svarbią informaciją, esančią kode. Žinoma, saugoti juos šaltinio kode nėra gera idėja, tačiau situacijos skiriasi. CxQL užklausų dėka gana lengva rasti tokius dalykus:

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

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

išvada

Tikiuosi, kad šis straipsnis bus naudingas tiems, kurie pradeda pažintį su „Checkmarx“ įrankiu. Galbūt šiame vadove ką nors naudingo ras ir tie, kurie ilgą laiką rašė savo taisykles.

Deja, šiuo metu trūksta išteklių, kur būtų galima pasisemti naujų idėjų kuriant „Checkmarx“ taisykles. Dėl to ir sukūrėme „Github“ saugykla, kur publikuosime savo darbus, kad kiekvienas besinaudojantis CxQL rastų jame ką nors naudingo, o taip pat turėtų galimybę pasidalinti savo darbais su bendruomene. Saugykloje pildomas ir struktūrizuojamas turinys, todėl laukiami bendradarbiai!

Dėkojame už dėmesį!

Šaltinis: www.habr.com

Добавить комментарий