Kā uzrakstīt noteikumus Checkmarx, nekļūstot trakam

Čau Habr!

Mūsu uzņēmums ļoti bieži nodarbojas ar dažādiem statiskā koda analīzes rīkiem (SAST). No kastes tie visi strādā vidēji. Protams, viss ir atkarīgs no projekta un tajā izmantotajām tehnoloģijām, kā arī no tā, cik labi uz šīm tehnoloģijām attiecas analīzes noteikumi. Manuprāt, viens no svarīgākajiem kritērijiem, izvēloties SAST rīku, ir iespēja to pielāgot savu aplikāciju specifikai, proti, rakstīt un mainīt analīzes kārtulas jeb, kā tos biežāk dēvē, Custom Queries.

Kā uzrakstīt noteikumus Checkmarx, nekļūstot trakam

Visbiežāk mēs izmantojam Checkmarx - ļoti interesantu un jaudīgu kodu analizatoru. Šajā rakstā es runāšu par savu pieredzi, rakstot analīzes noteikumus.

Satura

Ieraksts

Sākumā es vēlos ieteikt vienu no nedaudzajiem rakstiem krievu valodā par vaicājumu rakstīšanas iespējām Checkmarx. Tas tika publicēts vietnē Habré 2019. gada beigās ar nosaukumu: "Sveiks, Čemarks!" Kā uzrakstīt Checkmarx SAST vaicājumu un atrast lieliskas ievainojamības.

Tajā sīki apskatīts, kā rakstīt pirmos vaicājumus CxQL (Checkmarx vaicājumu valodā) dažām testa lietojumprogrammām, un parādīti analīzes kārtulu darbības pamatprincipi.

Es neatkārtošu tajā aprakstīto, lai gan daži krustojumi joprojām būs. Savā rakstā es mēģināšu sastādīt sava veida “recepšu kolekciju”, risinājumu sarakstu konkrētām problēmām, ar kurām saskāros, strādājot ar Checkmarx. Man nācās salauzt smadzenes par daudzām no šīm problēmām. Dažreiz dokumentācijā nebija pietiekami daudz informācijas, un dažreiz bija pat grūti saprast, kā to izdarīt. Ceru, ka mana pieredze un negulētās naktis nebūs veltīgas, un šī “Pielāgoto vaicājumu recepšu kolekcija” ietaupīs dažas stundas vai pāris nervu šūnas. Tātad, sāksim!

Vispārīga informācija par noteikumiem

Vispirms apskatīsim dažus pamatjēdzienus un darba procesu ar noteikumiem, lai labāk izprastu, kas notiks tālāk. Un arī tāpēc, ka dokumentācijā par to nekas nav teikts vai tas ir ļoti izkliedēts struktūrā, kas nav īpaši ērti.

  1. Noteikumi tiek piemēroti skenēšanas laikā atkarībā no sākumā atlasītā sākotnējā iestatījuma (aktīvo noteikumu kopa). Varat izveidot neierobežotu skaitu sākotnējo iestatījumu, un tieši to strukturēšana ir atkarīga no jūsu procesa specifikas. Varat tos grupēt pēc valodas vai katram projektam atlasīt sākotnējos iestatījumus. Aktīvo kārtulu skaits ietekmē skenēšanas ātrumu un precizitāti.

    Kā uzrakstīt noteikumus Checkmarx, nekļūstot trakamPreset iestatīšana Checkmarx saskarnē

  2. Noteikumi tiek rediģēti īpašā rīkā ar nosaukumu CxAuditor. Šī ir darbvirsmas lietojumprogramma, kas izveido savienojumu ar serveri, kurā darbojas Checkmarx. Šim rīkam ir divi darbības režīmi: kārtulu rediģēšana un jau veiktās skenēšanas rezultātu analīze.

    Kā uzrakstīt noteikumus Checkmarx, nekļūstot trakamCxAudit saskarne

  3. Checkmarx noteikumi ir sadalīti pēc valodas, tas ir, katrai valodai ir savs vaicājumu kopums. Ir arī daži vispārīgi noteikumi, kas tiek piemēroti neatkarīgi no valodas, tie ir tā sauktie pamata vaicājumi. Lielākoties pamata vaicājumi ietver informācijas meklēšanu, ko izmanto citi noteikumi.

    Kā uzrakstīt noteikumus Checkmarx, nekļūstot trakamNoteikumu sadalīšana pēc valodas

  4. Noteikumi ir “Izpildāmi” un “Neizpildāmi” (Izpildīti un Neizpildīti). Manuprāt, ne gluži pareizais nosaukums, bet tā tas ir. Rezultāts ir tāds, ka izpildāmo kārtulu izpildes rezultāts tiks parādīts lietotāja saskarnes skenēšanas rezultātos, un kārtulas, kas nav izpildāmas, ir nepieciešamas tikai, lai izmantotu to rezultātus citos pieprasījumos (patiesībā tās ir tikai funkcija ).

    Kā uzrakstīt noteikumus Checkmarx, nekļūstot trakamNoteikumu veida noteikšana, veidojot

  5. Varat izveidot jaunus noteikumus vai papildināt/pārrakstīt esošos. Lai pārrakstītu noteikumu, tas jāatrod kokā, ar peles labo pogu noklikšķiniet un nolaižamajā izvēlnē atlasiet “Ignorēt”. Šeit ir svarīgi atcerēties, ka jaunie noteikumi sākotnēji nav iekļauti sākotnējos iestatījumos un nav aktīvi. Lai sāktu tos lietot, tie jāaktivizē instrumenta izvēlnē “Preset Manager”. Pārrakstītās kārtulas saglabā savus iestatījumus, tas ir, ja kārtula bija aktīva, tā paliks tāda un tiks piemērota nekavējoties.

    Kā uzrakstīt noteikumus Checkmarx, nekļūstot trakamJauna noteikuma piemērs iepriekš iestatīto pārvaldnieka saskarnē

  6. Izpildes laikā tiek izveidots pieprasījumu “koks”, kas ir atkarīgs no tā, ko. Noteikumi, kas apkopo informāciju, tiek izpildīti vispirms, bet pēc tam tie, kas to izmanto. Izpildes rezultāts tiek saglabāts kešatmiņā, tādēļ, ja ir iespējams izmantot esošas kārtulas rezultātus, labāk to darīt, tādējādi samazināsies skenēšanas laiks.

  7. Noteikumus var piemērot dažādos līmeņos:

  • Visai sistēmai - tiks izmantota jebkura projekta skenēšanai

  • Komandas līmenī (Komanda) - tiks izmantots tikai projektu skenēšanai izvēlētajā komandā.

  • Projekta līmenī - Tiks pielietots konkrētā projektā

    Kā uzrakstīt noteikumus Checkmarx, nekļūstot trakamLīmeņa noteikšana, kurā noteikums tiks piemērots

"Vārdnīca" iesācējiem

Un es sākšu ar dažām lietām, kas man radīja jautājumus, un es parādīšu arī vairākas metodes, kas ievērojami vienkāršos dzīvi.

Operācijas ar sarakstiem

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

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

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

Visas atrastās preces

Skenētajā valodā varat iegūt sarakstu ar pilnīgi visiem elementiem, kurus Checkmarx ir identificējis (virknes, funkcijas, klases, metodes utt.). Šī ir daļa objektu, kuriem var piekļūt All. Tas ir, lai meklētu objektu ar noteiktu nosaukumu searchMe, varat meklēt, piemēram, pēc nosaukuma visos atrastajos objektos:

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

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

Bet, ja jums ir nepieciešams meklēt citā valodā, kas kāda iemesla dēļ nav iekļauta skenēšanā (piemēram, groovy Android projektā), varat paplašināt mūsu objektu telpu, izmantojot mainīgo:

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

Plūsmas analīzes funkcijas

Šīs funkcijas tiek izmantotas daudzos noteikumos, un šeit ir neliela informācija par to nozīmi:

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

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

Notiek faila nosaukuma/ceļa iegūšana

Ir vairāki atribūti, ko var iegūt no vaicājuma rezultātiem (faila nosaukums, kurā tika atrasts ieraksts, virkne utt.), taču dokumentācijā nav teikts, kā tos iegūt un izmantot. Tātad, lai to izdarītu, jums ir jāpiekļūst LinePragma īpašumam, un mums nepieciešamie objekti atradīsies tajā:

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

Ir vērts to paturēt prātā FileName faktiski satur ceļu uz failu, jo mēs izmantojām šo metodi GetFirstGraph.

Izpildes rezultāts

CxQL iekšpusē ir īpašs mainīgais result, kas atgriež jūsu rakstītās kārtulas izpildes rezultātu. Tas tiek nekavējoties inicializēts, un jūs varat tajā ierakstīt starprezultātus, mainot un uzlabojot tos darba laikā. Bet, ja kārtulā nav piešķirts šis mainīgais vai funkcija return— izpildes rezultāts vienmēr būs nulle.

Šis vaicājums izpildes rezultātā mums neko neatgriezīs un vienmēr būs tukšs:

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

Bet, piešķirot izpildes rezultātu maģiskā mainīgā rezultātam, mēs redzēsim, ko šis zvans mums atgriež:

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

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

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

Izmantojot citu noteikumu rezultātus

Checkmarx noteikumus var saukt par analogiem funkcijām parastajā programmēšanas valodā. Rakstot noteikumu, varat izmantot citu vaicājumu rezultātus. Piemēram, katru reizi kodā nav jāmeklē visi metožu izsaukumi, vienkārši izsauciet vajadzīgo noteikumu:

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

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

Šī pieeja ļauj saīsināt kodu un ievērojami samazināt kārtulas izpildes laiku.

Problēmu risināšana

Mežizstrāde

Strādājot ar rīku, dažreiz nav iespējams uzreiz uzrakstīt vēlamo vaicājumu un nākas eksperimentēt, izmēģinot dažādas iespējas. Šādā gadījumā rīks nodrošina reģistrēšanu, ko sauc šādi:

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

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

Bet ir vērts atcerēties, ka šī metode tiek pieņemta tikai kā ievade virkne, tāpēc pirmās darbības rezultātā nebūs iespējams parādīt pilnu atrasto elementu sarakstu. Otra iespēja, kas tiek izmantota atkļūdošanai, ir laiku pa laikam piešķirt kādu maģisku mainīgo result vaicājuma rezultātu un redzēt, kas notiek. Šī pieeja nav īpaši ērta; jums ir jābūt pārliecinātam, ka pēc tam kodā nav neviena ignorēšanas vai darbības ar to result vai vienkārši komentējiet zemāk esošo kodu. Vai arī varat, tāpat kā es, aizmirst noņemt vairākus šādus zvanus no gatavā noteikuma un brīnīties, kāpēc nekas nedarbojas.

Ērtāks veids ir izsaukt metodi return ar nepieciešamo parametru. Šajā gadījumā noteikuma izpilde beigsies, un mēs varēsim redzēt, kas noticis mūsu rakstītā rezultātā:

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

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

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

Pieteikšanās problēma

Pastāv situācijas, kad nevarat piekļūt rīkam CxAudit (kas tiek izmantots kārtulu rakstīšanai). Tam var būt daudz iemeslu, tostarp avārijas, pēkšņi Windows atjauninājumi, BSOD un citas neparedzētas situācijas, kuras mēs nevaram ietekmēt. Šajā gadījumā dažkārt datu bāzē ir nepabeigta sesija, kas neļauj atkārtoti pieteikties. Lai to labotu, jums ir jāizpilda vairāki vaicājumi:

Checkmarx pirms 8.6:

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

Checkmarx pēc 8.6:

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

Rakstīšanas noteikumi

Tagad mēs nonākam pie visinteresantākās daļas. Kad sākat rakstīt noteikumus CxQL, jums bieži pietrūkst ne tik daudz dokumentācijas, cik daži dzīvi piemēri noteiktu problēmu risināšanai un vaicājuma darbības procesa aprakstīšanai kopumā.

Mēģināšu nedaudz atvieglot dzīvi tiem, kuri sāk ienirt vaicājumu valodā, un sniegšu vairākus piemērus, kā izmantot pielāgotos vaicājumus noteiktu problēmu risināšanai. Daži no tiem ir diezgan vispārīgi un praktiski bez izmaiņām ir izmantojami jūsu uzņēmumā, citi ir specifiskāki, taču tos var izmantot arī, mainot kodu, lai tas atbilstu jūsu aplikāciju specifikai.

Tātad, šeit ir problēmas, ar kurām mēs saskārāmies visbiežāk:

Uzdevums: Noteikuma izpildes rezultātos ir vairākas plūsmas, un viena no tām ir cita ligzdošana, viena no tām ir jāatstāj.

šķīdums: Patiešām, dažreiz Checkmarx parāda vairākas datu plūsmas, kas var pārklāties un būt citu saīsinātas versijas. Šādiem gadījumiem ir īpaša metode ReduceFlow. Atkarībā no parametra tas izvēlēsies īsāko vai garāko plūsmu:

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

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

Uzdevums: Paplašiniet to sensitīvo datu sarakstu, uz kuriem rīks reaģē

šķīdums: Checkmarx ir pamatnoteikumi, kuru rezultātus izmanto daudzi citi vaicājumi. Papildinot dažus no šiem noteikumiem ar datiem, kas raksturīgi jūsu lietojumprogrammai, varat nekavējoties uzlabot skenēšanas rezultātus. Tālāk ir sniegts noteikuma piemērs, lai sāktu darbu.

General_privacy_violation_list

Pievienosim vairākus mainīgos, kas tiek izmantoti mūsu lietojumprogrammā, lai saglabātu sensitīvu informāciju:

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

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

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

Uzdevums: Paplašiniet mainīgo sarakstu ar parolēm

šķīdums: Es ieteiktu nekavējoties pievērst uzmanību pamatnoteikumam paroļu definēšanai kodā un pievienot tam sarakstu ar mainīgo nosaukumiem, kas parasti tiek lietoti jūsu uzņēmumā.

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

Uzdevums: Pievienojiet lietotos ietvarus, kurus Checkmarx neatbalsta

šķīdums: Visi vaicājumi programmā Checkmarx ir sadalīti pēc valodas, tāpēc katrai valodai ir jāpievieno noteikumi. Tālāk ir sniegti daži šādu noteikumu piemēri.

Ja tiek izmantotas bibliotēkas, kas papildina vai aizstāj standarta funkcionalitāti, tās var viegli pievienot pamatnoteikumam. Tad visi, kas to izmantos, uzreiz uzzinās par jaunajiem ievadiem. Piemēram, bibliotēkas pieteikšanās operētājsistēmā Android ir Timber un Loggi. Pamata pakotnē nav noteikumu nesistēmas zvanu identificēšanai, tāpēc, ja žurnālā nokļūst parole vai sesijas identifikators, mēs par to neuzzināsim. Mēģināsim pievienot šādu metožu definīcijas Checkmarx noteikumiem.

Testa koda piemērs, kas mežizstrādei izmanto kokmateriālu bibliotēku:

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

Un šeit ir Checkmarx pieprasījuma piemērs, kas ļaus jums pievienot definīciju Timber metožu izsaukšanai kā datu izejas punktam no lietojumprogrammas:

Atrodiet Android Outputs

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

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

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

Varat arī pievienot blakus esošajam noteikumam, taču tas ir tieši saistīts ar pieteikšanos Android:

Atrodiet AndroidLog_Outputs

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

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

Arī tad, ja izmanto Android lietojumprogrammas Darba vadītājs asinhronam darbam ieteicams par to papildus informēt Checkmarx, pievienojot metodi datu iegūšanai no uzdevuma getInputData:

Atrast AndroidRead

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

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

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

Uzdevums: Sensitīvu datu meklēšana iOS projektu sarakstā

šķīdums: iOS bieži izmanto īpašus failus ar paplašinājumu .plist, lai saglabātu dažādus mainīgos un vērtības. Šajos failos nav ieteicams glabāt paroles, žetonus, atslēgas un citus sensitīvus datus, jo tos var bez problēmām izvilkt no ierīces.

Plist failiem ir funkcijas, kas nav acīmredzamas ar neapbruņotu aci, bet ir svarīgas Checkmarx. Uzrakstīsim noteikumu, kas meklēs mums nepieciešamos datus un pateiks, vai kaut kur ir minētas paroles vai marķieri.

Šāda faila piemērs, kurā ir marķieris saziņai ar aizmugures pakalpojumu:

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

Un noteikums par Checkmarx, kuram ir vairākas nianses, kas jāņem vērā, rakstot:

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

Uzdevums: Informācijas atrašana XML formātā

šķīdums: Checkmarx ir ļoti ērtas funkcijas darbam ar XML un vērtību, tagu, atribūtu un citu meklēšanu. Bet diemžēl dokumentācijā bija kļūda, kuras dēļ nedarbojas neviens piemērs. Neskatoties uz to, ka jaunākajā dokumentācijas versijā šis defekts ir novērsts, esiet piesardzīgs, ja izmantojat vecākas dokumentu versijas.

Šeit ir nepareizs piemērs no dokumentācijas:

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

Izpildes mēģinājuma rezultātā mēs saņemsim kļūdu, kas All tādas metodes nav... Un tā ir taisnība, jo ir īpaša, atsevišķa objektu telpa funkciju izmantošanai darbam ar XML - cxXPath. Šādi izskatās pareizais vaicājums, lai atrastu iestatījumu Android ierīcē, kas ļauj izmantot HTTP trafiku:

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

Apskatīsim to nedaudz sīkāk, jo visu funkciju sintakse ir līdzīga, pēc tam, kad esat izdomājis vienu, jums vienkārši jāizvēlas tā, kas jums nepieciešama. Tātad, secīgi pēc parametriem:

  • "*.xml"— meklējamo failu maska

  • 8 — tās valodas id, kurai piemēro noteikumu

  • "cleartextTrafficPermitted"— atribūta nosaukums xml

  • "true" — šī atribūta vērtība

  • false — regulāras izteiksmes izmantošana meklēšanā

  • true — nozīmē, ka meklēšana tiks veikta, ignorējot reģistru, t.i., reģistrjutīgi

Kā piemēru mēs izmantojām noteikumu, kas no drošības viedokļa identificē nepareizus tīkla savienojuma iestatījumus operētājsistēmā Android, kas ļauj sazināties ar serveri, izmantojot HTTP protokolu. Iestatījuma piemērs, kas satur atribūtu cleartextTrafficPermitted ar nozīmi 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>

Uzdevums: Ierobežojiet rezultātus pēc faila nosaukuma/ceļa

šķīdums: Vienā no lielajiem projektiem, kas saistīti ar Android mobilās lietojumprogrammas izstrādi, mēs saskārāmies ar kļūdaini pozitīvu noteikumu, kas nosaka apmulsināšanas iestatījumu. Fakts ir tāds, ka noteikums ārpus lodziņa meklē failā build.gradle iestatījums, kas atbild par apmulsināšanas noteikumu piemērošanu lietojumprogrammas izlaišanas versijai.

Bet lielos projektos dažreiz ir bērnu faili build.gradle, kas attiecas uz projektā iekļautajām bibliotēkām. Īpatnība ir tāda, ka pat tad, ja šie faili nenorāda uz neskaidrības nepieciešamību, kompilācijas laikā tiks piemēroti vecāku montāžas faila iestatījumi.

Tādējādi uzdevums ir nogriezt trigerus bērnu failos, kas pieder bibliotēkām. Tos var atpazīt pēc līnijas klātbūtnes apply 'com.android.library'.

Koda piemērs no faila build.gradle, kas nosaka neskaidrības nepieciešamību:

apply plugin: 'com.android.application'

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

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

dependencies {
  ...
}

Faila piemērs build.gradle projektā iekļautajai bibliotēkai, kurai nav šī iestatījuma:

apply plugin: 'android-library'

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

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

Un noteikums par 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);
		}
	}
}

Šī pieeja var būt diezgan universāla un noderīga ne tikai Android lietojumprogrammām, bet arī citos gadījumos, kad nepieciešams noteikt, vai rezultāts pieder konkrētam failam.

Uzdevums: Pievienojiet atbalstu trešās puses bibliotēkai, ja sintakse nav pilnībā atbalstīta

šķīdums: Dažādu ietvaru skaits, kas tiek izmantots koda rakstīšanas procesā, vienkārši nav redzams. Protams, Čemarkss ne vienmēr zina par to esamību, un mūsu uzdevums ir iemācīt tam saprast, ka noteiktas metodes pieder tieši šim ietvaram. Dažkārt to sarežģī fakts, ka ietvaros tiek izmantoti ļoti izplatīti funkciju nosaukumi un nav iespējams viennozīmīgi noteikt konkrēta izsaukuma saistību ar konkrētu bibliotēku.

Grūtības ir tādas, ka šādu bibliotēku sintakse ne vienmēr tiek atpazīta pareizi, un jums ir jāeksperimentē, lai izvairītos no liela skaita viltus pozitīvu rezultātu iegūšanas. Ir vairākas iespējas, kā uzlabot skenēšanas precizitāti un atrisināt problēmu:

  • Pirmā iespēja, mēs noteikti zinām, ka bibliotēka tiek izmantota konkrētā projektā un var piemērot noteikumu komandas līmenī. Bet, ja komanda nolemj izvēlēties citu pieeju vai izmanto vairākas bibliotēkas, kurās funkciju nosaukumi pārklājas, mēs varam iegūt ne pārāk patīkamu priekšstatu par daudziem viltus pozitīviem.

  • Otrā iespēja ir meklēt failus, kuros bibliotēka ir skaidri importēta. Izmantojot šo pieeju, mēs varam būt pārliecināti, ka šajā failā ir tieši izmantota mums vajadzīgā bibliotēka.

  • Un trešā iespēja ir izmantot divas iepriekš minētās pieejas kopā.

Kā piemēru aplūkosim šaurās aprindās labi zināmu bibliotēku slidens Scala programmēšanas valodai, proti, funkcionalitātei Literālo vērtību savienošana. Parasti, lai SQL vaicājumam nodotu parametrus, ir jāizmanto operators $, kas aizstāj datus iepriekš izveidotā SQL vaicājumā. Faktiski tas ir tiešs Java sagatavotā paziņojuma analogs. Bet, ja jums ir nepieciešams dinamiski izveidot SQL vaicājumu, piemēram, ja jums ir jānodod tabulu nosaukumi, varat izmantot operatoru #$, kas tieši aizstās datus vaicājumā (gandrīz kā virknes savienošana).

Koda piemērs:

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

Checkmarx vēl nezina, kā noteikt burtisku vērtību savienošanu, un izlaiž operatorus #$, tāpēc mēģināsim to iemācīt identificēt iespējamās SQL injekcijas un izcelt pareizās vietas kodā:

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

Uzdevums: Atvērtā pirmkoda bibliotēkās meklējiet izmantotās ievainojamās funkcijas

šķīdums: Daudzi uzņēmumi izmanto atvērtā koda uzraudzības rīkus (OSA praksi), lai atklātu neaizsargātu bibliotēku versiju izmantošanu izstrādātajās lietojumprogrammās. Dažreiz šādu bibliotēku nav iespējams atjaunināt uz drošu versiju. Dažos gadījumos ir funkcionāli ierobežojumi, citos drošas versijas nav vispār. Šajā gadījumā SAST un OSA prakses kombinācija palīdzēs noteikt, ka kodā netiek izmantotas funkcijas, kas izraisa ievainojamības izmantošanu.

Bet dažreiz, it īpaši, ja ņem vērā JavaScript, tas var nebūt pilnīgi triviāls uzdevums. Zemāk ir risinājums, kas, iespējams, nav ideāls, bet tomēr darbojas, izmantojot komponenta ievainojamību piemēru lodash metodēs template и *set.

Potenciāli ievainojama koda testēšanas piemēri JS failā:

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

Un, pieslēdzoties tieši 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>

Mēs meklējam visas mūsu ievainojamās metodes, kas ir norādītas ievainojamībās:

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

Uzdevums: Lietojumprogrammā iegulto sertifikātu meklēšana

šķīdums: Nav nekas neparasts, ka lietojumprogrammas, īpaši mobilās, izmanto sertifikātus vai atslēgas, lai piekļūtu dažādiem serveriem vai pārbaudītu SSL piespraušanu. No drošības viedokļa šādu lietu glabāšana kodā nav labākā prakse. Mēģināsim uzrakstīt noteikumu, kas repozitorijā meklēs līdzīgus failus:

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

Uzdevums: Kompromitētu marķieru atrašana lietojumprogrammā

šķīdums: Bieži vien ir jāatsauc uzlauztās pilnvaras vai cita svarīga informācija, kas atrodas kodā. Protams, to glabāšana avota kodā nav laba ideja, taču situācijas ir dažādas. Pateicoties CxQL vaicājumiem, šādu lietu atrašana ir diezgan vienkārša:

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

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

Secinājums

Ceru, ka šis raksts būs noderīgs tiem, kas sāk iepazīties ar Checkmarx rīku. Iespējams, šajā rokasgrāmatā kaut ko noderīgu atradīs arī tie, kuri jau ilgu laiku ir rakstījuši savus noteikumus.

Diemžēl pašlaik trūkst resursu, kur varētu smelties jaunas idejas, izstrādājot Checkmarx noteikumus. Tāpēc mēs izveidojām krātuve vietnē Github, kurā ievietosim savus darbus, lai ikviens, kurš izmanto CxQL, varētu tajā atrast ko noderīgu, kā arī būtu iespēja dalīties ar saviem darbiem ar sabiedrību. Repozitorijs ir satura aizpildīšanas un strukturēšanas procesā, tāpēc laipni aicināti atbalstītāji!

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

Avots: www.habr.com

Pievieno komentāru