Kiel skribi regulojn por Checkmarx sen freneziĝi

Hej Habr!

En nia laboro, nia kompanio tre ofte traktas diversajn statikajn kodanalizajn ilojn (SAST). El la skatolo ili ĉiuj funkcias averaĝe. Kompreneble, ĉio dependas de la projekto kaj de la teknologioj uzataj en ĝi, kaj ankaŭ de kiom bone ĉi tiuj teknologioj estas kovritaj de la reguloj de analizo. Laŭ mi, unu el la plej gravaj kriterioj kiam vi elektas SAST-ilon estas la kapablo agordi ĝin laŭ la specifaĵoj de viaj aplikoj, nome, skribi kaj ŝanĝi analizajn regulojn aŭ, kiel ili estas pli ofte nomataj, Propraj Demandoj.

Kiel skribi regulojn por Checkmarx sen freneziĝi

Ni plej ofte uzas Checkmarx - tre interesan kaj potencan kodan analizilon. En ĉi tiu artikolo mi parolos pri mia sperto verki analizajn regulojn por ĝi.

Enhavtabelo

eniro

Komence, mi ŝatus rekomendi unu el la malmultaj artikoloj en la rusa pri la funkcioj de skribado de demandoj por Checkmarx. Ĝi estis publikigita ĉe Habré fine de 2019 sub la titolo: "Saluton, Checkmarx!" Kiel skribi Checkmarx SAST-demandon kaj trovi bonegajn vundeblecojn.

Ĝi detale ekzamenas kiel skribi la unuajn demandojn en CxQL (Checkmarx Query Language) por iu testa aplikaĵo kaj montras la bazajn principojn pri kiel funkcias analizreguloj.

Mi ne ripetos tion, kio estas priskribita en ĝi, kvankam kelkaj intersekcoj ankoraŭ ĉeestos. En mia artikolo mi provos kompili ian "kolekton de receptoj", liston de solvoj al specifaj problemoj, kiujn mi renkontis dum mia laboro kun Checkmarx. Mi devis trudi miajn cerbon super multaj el ĉi tiuj problemoj. Foje ne estis sufiĉe da informoj en la dokumentado, kaj foje eĉ estis malfacile kompreni kiel fari tion, kion oni bezonis. Mi esperas, ke mia sperto kaj sendormaj noktoj ne estos vanaj, kaj ĉi tiu "kolekto de Propraj Demandaj receptoj" ŝparos al vi kelkajn horojn aŭ kelkajn nervajn ĉelojn. Do, ni komencu!

Ĝeneralaj informoj pri la reguloj

Unue, ni rigardu kelkajn bazajn konceptojn kaj la procezon labori kun la reguloj, por pli bone kompreni kio okazos poste. Kaj ankaŭ ĉar la dokumentado diras nenion pri tio aŭ estas tre disvastigita en la strukturo, kio ne estas tre oportuna.

  1. La reguloj estas aplikataj dum skanado depende de la antaŭdiro elektita ĉe komenco (aro de aktivaj reguloj). Vi povas krei senliman nombron da antaŭdiroj, kaj ĝuste kiel strukturi ilin dependas de la specifaĵoj de via procezo. Vi povas grupigi ilin laŭ lingvo aŭ elekti antaŭdirojn por ĉiu projekto. La nombro da aktivaj reguloj influas la rapidecon kaj precizecon de skanado.

    Kiel skribi regulojn por Checkmarx sen freneziĝiAgordo de Antaŭdiro en la Checkmarx-interfaco

  2. La reguloj estas redaktitaj en speciala ilo nomata CxAuditor. Ĉi tio estas labortabla aplikaĵo, kiu konektas al servilo, kiu funkcias Checkmarx. Ĉi tiu ilo havas du manierojn de funkciado: redaktado de reguloj kaj analizado de la rezultoj de jam farita skanado.

    Kiel skribi regulojn por Checkmarx sen freneziĝiCxAudit-interfaco

  3. Reguloj en Checkmarx estas dividitaj laŭ lingvo, tio estas, ĉiu lingvo havas sian propran aron de demandoj. Estas ankaŭ iuj ĝeneralaj reguloj kiuj validas sendepende de lingvo, ĉi tiuj estas la tiel nomataj bazaj demandoj. Plejparte, bazaj demandoj implikas serĉi informojn, kiujn aliaj reguloj uzas.

    Kiel skribi regulojn por Checkmarx sen freneziĝiDivido de reguloj laŭ lingvo

  4. Reguloj estas "Efektiveblaj" kaj "Ne-Efektiveblaj" (Efektivigitaj kaj Ne Efektivigitaj). Ne tute ĝusta nomo, laŭ mi, sed tio estas kio ĝi estas. La fundo estas, ke la rezulto de ekzekuto de "Efektiveblaj" reguloj estos montrata en la skanaj rezultoj en la UI, kaj "Ne-Efektiveblaj" reguloj estas necesaj nur por uzi siajn rezultojn en aliaj petoj (esence, nur funkcio).

    Kiel skribi regulojn por Checkmarx sen freneziĝiDeterminante la regultipo dum kreado

  5. Vi povas krei novajn regulojn aŭ kompletigi/reskribi ekzistantajn. Por reverki regulon, vi devas trovi ĝin en la arbo, dekstre alklaku kaj elektu "Anstataŭigi" el la falmenuo. Gravas memori ĉi tie, ke la novaj reguloj ne estas komence inkluzivitaj en la antaŭdiroj kaj ne aktivas. Por komenci uzi ilin, vi devas aktivigi ilin en la menuo "Preset Manager" en la instrumento. Reskribitaj reguloj konservas siajn agordojn, tio estas, se la regulo estis aktiva, ĝi restos tiel kaj estos tuj aplikata.

    Kiel skribi regulojn por Checkmarx sen freneziĝiEkzemplo de nova regulo en la Preset Manager-interfaco

  6. Dum ekzekuto, "arbo" de petoj estas konstruita, kiu dependas de kio. La reguloj kiuj kolektas informojn estas ekzekutitaj unue, kaj tiuj kiuj uzas ĝin due. La ekzekutrezulto estas kaŝmemorigita, do se eblas uzi la rezultojn de ekzistanta regulo, tiam estas pli bone fari tion, ĉi tio reduktos la skanan tempon.

  7. Reguloj povas esti aplikataj sur malsamaj niveloj:

  • Por la tuta sistemo - estos uzata por ajna skanado de ajna projekto

  • Je la teamnivelo (Teamo) - nur estos uzata por skani projektojn en la elektita teamo.

  • Je la nivelo de projekto - Aplikiĝos en specifa projekto

    Kiel skribi regulojn por Checkmarx sen freneziĝiDeterminante la nivelon ĉe kiu la regulo estos aplikita

"Vortaro" por komencantoj

Kaj mi komencos per kelkaj aferoj, kiuj kaŭzis al mi demandojn, kaj mi ankaŭ montros kelkajn teknikojn, kiuj signife simpligos la vivon.

Operacioj kun listoj

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

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

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

Ĉiuj trovitaj eroj

Ene de la skanita lingvo, vi povas ricevi liston de absolute ĉiuj elementoj, kiujn Checkmarx identigis (ŝnuroj, funkcioj, klasoj, metodoj ktp.). Ĉi tio estas iu spaco de objektoj, kiujn oni povas aliri All. Tio estas, serĉi objekton kun specifa nomo searchMe, vi povas serĉi, ekzemple, laŭnome tra ĉiuj trovitaj objektoj:

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

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

Sed, se vi bezonas serĉi en alia lingvo, kiu ial ne estis inkluzivita en la skanado (ekzemple groovy en Android-projekto), vi povas vastigi nian objektan spacon per variablo:

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

Funkcioj por Flua analizo

Ĉi tiuj funkcioj estas uzataj en multaj reguloj kaj jen malgranda trompfolio pri tio, kion ili signifas:

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

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

Akirante dosiernomon/padon

Estas pluraj atributoj kiuj povas esti akiritaj de la rezultoj de konsulto (la nomo de la dosiero en kiu la eniro estis trovita, ĉeno, ktp.), sed la dokumentaro ne diras kiel akiri kaj uzi ilin. Do, por fari tion, vi devas aliri la posedaĵon LinePragma kaj la objektoj, kiujn ni bezonas, troviĝos en ĝi:

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

Indas tion memori FileName enhavas fakte la vojon al la dosiero, ĉar ni uzis la metodon GetFirstGraph.

Rezulto de ekzekuto

Estas speciala variablo ene de CxQL result, kiu resendas la rezulton de ekzekuto de via skribita regulo. Ĝi estas pravigita tuj kaj vi povas skribi mezajn rezultojn en ĝin, ŝanĝante kaj rafinante ilin dum vi laboras. Sed, se ne estas asigno al ĉi tiu variablo aŭ funkcio ene de la regulo return— la ekzekutrezulto ĉiam estos nul.

La sekva demando nenion resendos al ni kiel rezulto de ekzekuto kaj ĉiam estos malplena:

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

Sed, asigninte la ekzekutrezulton al la magia variablo rezulto, ni vidos, kion redonas al ni ĉi tiu voko:

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

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

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

Uzante la rezultojn de aliaj reguloj

Reguloj en Checkmarx povas esti nomitaj analogaj al funkcioj en regula programlingvo. Verkante regulon, vi povas bone uzi la rezultojn de aliaj demandoj. Ekzemple, ne necesas serĉi ĉiujn metodovokojn en la kodo ĉiufoje, nur voku la deziratan regulon:

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

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

Ĉi tiu aliro permesas vin mallongigi la kodon kaj signife redukti la regultempon de ekzekuto.

Solvo de problemoj

Enhavo

Laborante kun la ilo, foje ne eblas tuj skribi la deziratan demandon kaj vi devas eksperimenti, provante malsamajn opciojn. Por tia kazo, la ilo provizas registradon, kiu nomiĝas jene:

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

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

Sed indas memori, ke ĉi tiu metodo nur akceptas kiel enigo ŝnuro, do ne eblos montri kompletan liston de trovitaj elementoj rezulte de la unua operacio. La dua opcio, kiu estas uzata por senararigado, estas asigni al magia variablo de tempo al tempo result la rezulto de la demando kaj vidu kio okazas. Ĉi tiu aliro ne estas tre oportuna; vi devas certigi, ke ne estas anstataŭigoj aŭ operacioj kun ĉi tio en la kodo post result aŭ simple komentu la kodon sube. Aŭ vi povas, kiel mi, forgesi forigi plurajn tiajn alvokojn de preta regulo kaj scivoli kial nenio funkcias.

Pli oportuna maniero estas voki la metodon return kun la bezonata parametro. En ĉi tiu kazo, la ekzekuto de la regulo finiĝos kaj ni povos vidi kio okazis kiel rezulto de tio, kion ni skribis:

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

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

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

Ensaluta problemo

Estas situacioj, kiam vi ne povas aliri la ilon CxAudit (kiu estas uzata por skribi regulojn). Povas esti multaj kialoj por tio, inkluzive de kraŝoj, subitaj Vindozaj ĝisdatigoj, BSOD kaj aliaj neantaŭviditaj situacioj, kiuj estas ekster nia kontrolo. En ĉi tiu kazo, foje estas nefinita sesio en la datumbazo, kio malhelpas vin ensaluti denove. Por ripari ĝin, vi devas ruli plurajn demandojn:

Por Checkmarx antaŭ 8.6:

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

Por Checkmarx post 8.6:

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

Skribaj reguloj

Nun ni venas al la plej interesa parto. Kiam vi komencas verki regulojn en CxQL, tio, kion vi ofte mankas, estas ne tiom da dokumentaro, kiom da vivaj ekzemploj pri solvado de certaj problemoj kaj priskribado de la procezo de kiel la demandoj funkcias ĝenerale.

Mi provos iom plifaciligi la vivon al tiuj, kiuj komencas plonĝi en la demandlingvon kaj donas plurajn ekzemplojn pri uzado de Propraj Demandoj por solvi iujn problemojn. Kelkaj el ili estas sufiĉe ĝeneralaj kaj povas esti uzataj en via kompanio preskaŭ sen ŝanĝoj, aliaj estas pli specifaj, sed ili ankaŭ povas esti uzataj ŝanĝante la kodon laŭ la specifaĵoj de viaj aplikoj.

Do, jen la problemoj, kiujn ni plej ofte renkontis:

Tasko: Estas pluraj Fluoj en la rezultoj de ekzekuto de la regulo kaj unu el ili estas nestumado de alia, vi devas forlasi unu el ili.

solvo: Efektive, foje Checkmarx montras plurajn datumfluojn kiuj povas interkovri kaj esti mallongigita versio de aliaj. Estas speciala metodo por tiaj kazoj Redukti Fluon. Depende de la parametro, ĝi elektos la plej mallongan aŭ plej longan Fluon:

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

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

Tasko: Pligrandigu la liston de sentemaj datumoj, al kiuj la ilo reagas

solvo: Checkmarx havas bazajn regulojn, kies rezultoj estas uzataj de multaj aliaj demandoj. Kompletigante iujn ĉi tiujn regulojn per datumoj specifaj por via aplikaĵo, vi povas tuj plibonigi viajn skanajn rezultojn. Malsupre estas ekzempla regulo por komenci vin:

Ĝenerala_listo_de_malobservo_de_privateco

Ni aldonu plurajn variablojn, kiuj estas uzataj en nia aplikaĵo por konservi sentemajn informojn:

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

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

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

Tasko: Pligrandigu la liston de variabloj kun pasvortoj

solvo: Mi rekomendus tuj atenti la bazan regulon por difini pasvortojn en kodo kaj aldoni al ĝi liston de variablonomoj, kiuj estas kutime uzataj en via kompanio.

Listo_de_malobservo_de_privateco_de_pasvorto

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

Tasko: Aldonu uzitajn kadrojn, kiuj ne estas subtenataj de Checkmarx

solvo: Ĉiuj demandoj en Checkmarx estas dividitaj laŭ lingvo, do vi devas aldoni regulojn por ĉiu lingvo. Malsupre estas kelkaj ekzemploj de tiaj reguloj.

Se oni uzas bibliotekojn, kiuj kompletigas aŭ anstataŭigas norman funkciecon, oni povas facile aldoni ilin al la baza regulo. Tiam ĉiuj, kiuj uzas ĝin, tuj ekscios pri la novaj enkondukoj. Ekzemple, bibliotekoj por ensaluti en Android estas Timber kaj Loggi. En la baza pako, ne ekzistas reguloj por identigi ne-sistemajn vokojn, do se pasvorto aŭ seanca identigilo eniras la protokolon, ni ne scios pri ĝi. Ni provu aldoni difinojn de tiaj metodoj al la reguloj de Checkmarx.

Ekzemplo de prova kodo, kiu uzas la bibliotekon Timber por registri:

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

Kaj jen ekzemplo de peto por Checkmarx, kiu permesos al vi aldoni difinon pri vokado de Timber-metodoj kiel elirpunkto por datumoj de la aplikaĵo:

TrovuAndroidOutputs

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

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

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

Kaj vi ankaŭ povas aldoni al la najbara regulo, sed ĉi tiu rilatas rekte al ensaluti en Android:

Trovu AndroidLog_Eligojn

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

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

Ankaŭ, se Android-aplikoj uzas WorkManager por nesinkrona laboro, estas bona ideo aldone informi Checkmarx pri tio aldonante metodon por ricevi datumojn de la tasko. getInputData:

Trovu AndroidLegu

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

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

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

Tasko: Serĉante sentemajn datumojn en plist por iOS-projektoj

solvo: iOS ofte uzas specialajn dosierojn kun la etendo .plist por konservi diversajn variablojn kaj valorojn. Stoki pasvortojn, ĵetonojn, ŝlosilojn kaj aliajn sentemajn datumojn en ĉi tiuj dosieroj ne estas rekomendita, ĉar ili povas esti ĉerpitaj el la aparato sen problemoj.

Plist-dosieroj havas trajtojn, kiuj ne estas evidentaj al la nuda okulo, sed estas gravaj por Checkmarx. Ni skribu regulon, kiu serĉos la datumojn, kiujn ni bezonas kaj diros al ni, ĉu pasvortoj aŭ ĵetonoj estas menciitaj ie.

Ekzemplo de tia dosiero, kiu enhavas ĵetonon por komunikado kun la backend-servo:

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

Kaj regulo por Checkmarx, kiu havas plurajn nuancojn, kiujn oni devas konsideri dum skribado:

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

Tasko: Trovi informojn en XML

solvo: Checkmarx havas tre oportunajn funkciojn por labori kun XML kaj serĉi valorojn, etikedojn, atributojn kaj pli. Sed, bedaŭrinde, estis eraro en la dokumentado pro kiu eĉ ne unu ekzemplo funkcias. Malgraŭ tio, ke ĉi tiu difekto estis forigita en la plej nova versio de la dokumentaro, atentu se vi uzas pli fruajn versiojn de dokumentoj.

Jen malĝusta ekzemplo el la dokumentado:

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

Kiel rezulto de la ekzekutoprovo, ni ricevos eraron, ke All ne ekzistas tia metodo... Kaj tio estas vera, ĉar ekzistas speciala, aparta objektospaco por uzi funkciojn por labori kun XML - cxXPath. Jen kiel aspektas la ĝusta demando por trovi agordon en Android, kiu permesas la uzon de HTTP-trafiko:

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

Ni rigardu ĝin iom pli detale, ĉar la sintakso por ĉiuj funkcioj estas simila, post kiam vi eltrovis unu, tiam vi nur bezonas elekti tiun, kiun vi bezonas. Do, sinsekve laŭ la parametroj:

  • "*.xml"— masko de serĉendaj dosieroj

  • 8 — id de la lingvo por kiu la regulo estas aplikata

  • "cleartextTrafficPermitted"— atributonomo en xml

  • "true" — la valoro de ĉi tiu eco

  • false — uzo de regula esprimo dum serĉado

  • true — signifas, ke la serĉo estos farita ignorante majusklojn, tio estas, majusklojn

Ekzemple, ni uzis regulon, kiu identigas malĝustajn, el sekureca vidpunkto, retajn konektojn en Android, kiuj ebligas komunikadon kun la servilo per la HTTP-protokolo. Ekzemplo de agordo enhavanta atributon cleartextTrafficPermitted kun signifo 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>

Tasko: Limigu rezultojn per dosiernomo/pado

solvo: En unu el la grandaj projektoj rilataj al la disvolviĝo de poŝtelefona aplikaĵo por Android, ni renkontis falsajn pozitivojn de la regulo, kiu determinas la malklarigadon. La fakto estas, ke la regulo el la skatolo serĉas en la dosiero build.gradle agordo respondeca por aplikado de malklarigreguloj por la eldonversio de la aplikaĵo.

Sed en grandaj projektoj foje estas infanaj dosieroj build.gradle, kiuj rilatas al la bibliotekoj inkluzivitaj en la projekto. La propreco estas, ke eĉ se ĉi tiuj dosieroj ne indikas la bezonon de malklarigado, la agordoj de la gepatra kunigdosiero estos aplikataj dum kompilo.

Tiel, la tasko estas fortranĉi ellasilon en infanaj dosieroj, kiuj apartenas al bibliotekoj. Ili povas esti identigitaj per la ĉeesto de la linio apply 'com.android.library'.

Ekzempla kodo el dosiero build.gradle, kiu determinas la bezonon de malklarigado:

apply plugin: 'com.android.application'

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

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

dependencies {
  ...
}

Dosiera ekzemplo build.gradle por biblioteko inkluzivita en la projekto, kiu ne havas ĉi tiun agordon:

apply plugin: 'android-library'

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

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

Kaj la regulo por Checkmarx:

ProGuardObfuscationNotInUse

// Поиск метода release среди всех методов в Gradle файлах
CxList releaseMethod = Find_Gradle_Method("release");

// Все объекты из файлов build.gradle
CxList gradleBuildObjects = Find_Gradle_Build_Objects();

// Поиск того, что находится внутри метода "release" среди всех объектов из файлов build.gradle
CxList methodInvokesUnderRelease = gradleBuildObjects.FindByType(typeof(MethodInvokeExpr)).GetByAncs(releaseMethod);

// Ищем внутри gradle-файлов строку "com.android.library" - это значит, что данный файл относится к библиотеке и его необходимо исключить из правила
CxList android_library = gradleBuildObjects.FindByName("com.android.library");

// Инициализация пустого массива
List<string> libraries_path = new List<string> {};

// Проходим через все найденные "дочерние" файлы
foreach(CxList library in android_library)
{
    // Получаем путь к каждому файлу
	string file_name_library = library.GetFirstGraph().LinePragma.FileName;
    
    // Добавляем его в наш массив
	libraries_path.Add(file_name_library);
}

// Ищем все вызовы включения обфускации в релизных настройках
CxList minifyEnabled = methodInvokesUnderRelease.FindByShortName("minifyEnabled");

// Получаем параметры этих вызовов
CxList minifyValue = gradleBuildObjects.GetParameters(minifyEnabled, 0);

// Ищем среди них включенные
CxList minifyValueTrue = minifyValue.FindByShortName("true");

// Немного магии, если не нашли стандартным способом :D
if (minifyValueTrue.Count == 0) {
	minifyValue = minifyValue.FindByAbstractValue(abstractValue => abstractValue is TrueAbstractValue);
} else {
    // А если всё-таки нашли, то предыдущий результат и оставляем
	minifyValue = minifyValueTrue;	
}

// Если не нашлось таких методов
if (minifyValue.Count == 0)
{
    // Для более корректного отображения места срабатывания в файле ищем или buildTypes или android
	CxList tempResult = All.NewCxList();
	CxList buildTypes = Find_Gradle_Method("buildTypes");
	if (buildTypes.Count > 0) {
		tempResult = buildTypes;
	} else {
		tempResult = Find_Gradle_Method("android");
	}
	
	// Для каждого из найденных мест срабатывания проходим и определяем, дочерний или основной файлы сборки
	foreach(CxList res in tempResult)
	{
        // Определяем, в каком файле был найден buildType или android методы
		string file_name_result = res.GetFirstGraph().LinePragma.FileName;
        
        // Если такого файла нет в нашем списке "дочерних" файлов - значит это основной файл и его можно добавить в результат
		if (libraries_path.Contains(file_name_result) == false){
			result.Add(res);
		}
	}
}

Ĉi tiu aliro povas esti sufiĉe universala kaj utila ne nur por Android-aplikoj, sed ankaŭ por aliaj kazoj, kiam vi bezonas determini ĉu rezulto apartenas al specifa dosiero.

Tasko: Aldonu subtenon por triaparta biblioteko se la sintakso ne estas plene subtenata

solvo: La nombro da diversaj kadroj, kiuj estas uzataj en la procezo de skribado de kodo, estas simple ekster la furorlisto. Kompreneble, Checkmarx ne ĉiam scias pri ilia ekzisto, kaj nia tasko estas instrui ĝin kompreni, ke iuj metodoj apartenas specife al ĉi tiu kadro. Foje tio estas malfaciligita pro la fakto ke kadroj uzas funkcionomojn kiuj estas tre oftaj kaj estas neeble malambigue determini la rilaton de aparta voko al specifa biblioteko.

La malfacilaĵo estas, ke la sintakso de tiaj bibliotekoj ne ĉiam estas ĝuste rekonita kaj vi devas eksperimenti por eviti ricevi grandan nombron da falsaj pozitivoj. Estas pluraj ebloj por plibonigi la precizecon de skanado kaj solvi la problemon:

  • La unua opcio, ni certe scias, ke la biblioteko estas uzata en specifa projekto kaj povas apliki la regulon je la teamo. Sed se la teamo decidas preni alian aliron aŭ uzas plurajn bibliotekojn en kiuj funkcionomoj interkovras, ni povas akiri ne tre agrablan bildon de multaj falsaj pozitivoj.

  • La dua opcio estas serĉi dosierojn en kiuj la biblioteko estas klare importita. Kun ĉi tiu aliro, ni povas esti certaj, ke la biblioteko, kiun ni bezonas, estas ĝuste uzata en ĉi tiu dosiero.

  • Kaj la tria opcio estas uzi la du suprajn alirojn kune.

Ekzemple, ni rigardu bibliotekon konatan en mallarĝaj rondoj glata por la programlingvo Scala, nome, la funkcieco Splisado de Laŭvortaj Valoroj. Ĝenerale, por transdoni parametrojn al SQL-demando, vi devas uzi la operatoron $, kiu anstataŭigas datumojn en antaŭformitan SQL-demandon. Tio estas, fakte, ĝi estas rekta analogo de Prepared Statement en Java. Sed, se vi bezonas dinamike konstrui SQL-demandon, ekzemple, se vi bezonas transdoni tabelnomojn, vi povas uzi la operatoron #$, kiu rekte anstataŭigos la datumojn en la demandon (preskaŭ kiel ĉenkunkateniĝo).

Ekzempla kodo:

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

Checkmarx ankoraŭ ne scias kiel detekti la uzon de Splicing Literal Values ​​kaj preterlasas funkciigistojn #$, do ni provu instrui ĝin identigi eblajn SQL-injektojn kaj reliefigi la ĝustajn lokojn en la kodo:

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

Tasko: Serĉu uzatajn vundeblajn funkciojn en Malfermfontaj bibliotekoj

solvo: Multaj kompanioj uzas Malfermfontajn monitorajn ilojn (OSA-praktiko) por detekti la uzon de vundeblaj versioj de bibliotekoj en evoluintaj aplikoj. Kelkfoje ne eblas ĝisdatigi tian bibliotekon al sekura versio. En iuj kazoj estas funkciaj limigoj, en aliaj tute ne ekzistas sekura versio. En ĉi tiu kazo, kombinaĵo de SAST kaj OSA-praktikoj helpos determini, ke la funkcioj, kiuj kondukas al ekspluatado de la vundebleco, ne estas uzataj en la kodo.

Sed foje, precipe kiam oni konsideras JavaScript, tio eble ne estas tute bagatela tasko. Malsupre estas solvo, eble ne ideala, sed tamen funkcianta, uzante la ekzemplon de vundeblecoj en la komponanto lodash en metodoj template и *set.

Ekzemploj de prova eble vundebla kodo en JS-dosiero:

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

Kaj kiam vi konektas rekte en html:

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

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

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

Ni serĉas ĉiujn niajn vundeblajn metodojn, kiuj estas listigitaj en vundeblecoj:

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

Tasko: Serĉante atestojn enigitajn en la aplikaĵo

solvo: Ne estas malofte, ke aplikaĵoj, precipe moveblaj, uzas atestilojn aŭ ŝlosilojn por aliri diversajn servilojn aŭ kontroli SSL-Pinning. De sekureca perspektivo, stoki tiajn aferojn en kodo ne estas la plej bona praktiko. Ni provu skribi regulon, kiu serĉos similajn dosierojn en la deponejo:

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

Tasko: Trovi kompromititajn ĵetonojn en la aplikaĵo

solvo: Ofte necesas revoki kompromititajn ĵetonojn aŭ aliajn gravajn informojn, kiuj ĉeestas en la kodo. Kompreneble, konservi ilin ene de la fontkodo ne estas bona ideo, sed situacioj varias. Danke al CxQL-demandoj, trovi tiajn aferojn estas sufiĉe facila:

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

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

konkludo

Mi esperas, ke ĉi tiu artikolo estos utila al tiuj, kiuj ekkonas la ilon Checkmarx. Eble tiuj, kiuj jam delonge skribas siajn proprajn regulojn, ankaŭ trovos ion utilan en ĉi tiu gvidilo.

Bedaŭrinde, nuntempe mankas rimedo kie novaj ideoj povus esti kolektitaj dum la evoluo de reguloj por Checkmarx. Tial ni kreis deponejo sur Github, kie ni afiŝos nian laboron por ke ĉiuj, kiuj uzas CxQL, trovu ion utilan en ĝi, kaj ankaŭ havu la ŝancon dividi sian laboron kun la komunumo. La deponejo estas en procezo de plenigo kaj strukturado de enhavo, do kontribuantoj estas bonvenaj!

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

fonto: www.habr.com

Aldoni komenton