Checkmarx ережелерін ақылсыз түрде қалай жазуға болады

Эй Хабр!

Біздің жұмысымызда біздің компания әртүрлі статикалық кодты талдау құралдарымен (SAST) жиі айналысады. Қораптан тыс олардың барлығы орташа жұмыс істейді. Әрине, бәрі жобаға және онда қолданылатын технологияларға, сондай-ақ бұл технологиялар талдау ережелерімен қаншалықты қамтылғанына байланысты. Менің ойымша, SAST құралын таңдаған кездегі ең маңызды критерийлердің бірі - оны қолданбаларыңыздың ерекшеліктеріне қарай теңшеу мүмкіндігі, атап айтқанда, талдау ережелерін жазу және өзгерту немесе оларды жиі атайтындай, Custom Queries.

Checkmarx ережелерін ақылсыз түрде қалай жазуға болады

Біз Checkmarx-ті жиі қолданамыз - өте қызықты және қуатты код анализаторы. Бұл мақалада мен оған талдау ережелерін жазу тәжірибесі туралы айтатын боламын.

Мазмұны

кіру

Алдымен мен Checkmarx сұрауларын жазу мүмкіндіктері туралы орыс тіліндегі бірнеше мақалалардың бірін ұсынғым келеді. Ол Habré сайтында 2019 жылдың соңында келесі тақырыппен жарияланды: «Сәлеметсіз бе, Чекмаркс!» Checkmarx SAST сұрауын қалай жазуға және керемет осалдықтарды табуға болады.

Ол кейбір сынақ қолданбалары үшін CxQL (Checkmark Query Language) тілінде бірінші сұрауларды қалай жазу керектігін егжей-тегжейлі қарастырады және талдау ережелерінің жұмыс істеуінің негізгі принциптерін көрсетеді.

Мен онда сипатталғанды ​​қайталамаймын, дегенмен кейбір қиылыстар әлі де болады. Мен өз мақаламда Checkmarx-пен жұмыс істеу кезінде кездесетін нақты мәселелердің шешімдерінің тізімін, «рецепттер жинағын» құруға тырысамын. Маған осы мәселелердің көпшілігінде миымды жинауға тура келді. Кейде құжаттамада ақпарат жеткіліксіз болды, ал кейде талап етілетін нәрсені қалай орындау керектігін түсіну қиын болды. Менің тәжірибем мен ұйқысыз түндерім бекер болмайды деп үміттенемін және бұл «Таңдамалы сұраулар рецептерінің жинағы» сізге бірнеше сағат немесе бірнеше жүйке жасушаларын үнемдейді. Ендеше, бастайық!

Ережелер туралы жалпы мәліметтер

Алдымен не болатынын жақсы түсіну үшін бірнеше негізгі ұғымдарды және ережелермен жұмыс істеу процесін қарастырайық. Сондай-ақ, құжаттамада бұл туралы ештеңе айтылмаған немесе құрылымда өте таралған, бұл өте ыңғайлы емес.

  1. Ережелер бастапқыда таңдалған алдын ала орнатуға (белсенді ережелер жинағы) байланысты сканерлеу кезінде қолданылады. Сіз алдын ала орнатулардың шектеусіз санын жасай аласыз және оларды қалай құрылымдау сіздің процесіңіздің ерекшеліктеріне байланысты. Оларды тіл бойынша топтастыруға немесе әрбір жоба үшін алдын ала орнатуларды таңдауға болады. Белсенді ережелер саны сканерлеу жылдамдығы мен дәлдігіне әсер етеді.

    Checkmarx ережелерін ақылсыз түрде қалай жазуға боладыCheckmarx интерфейсінде Preset орнату

  2. Ережелер CxAuditor деп аталатын арнайы құралда өңделеді. Бұл Checkmarx іске қосылған серверге қосылатын жұмыс үстелі қолданбасы. Бұл құралдың екі жұмыс режимі бар: ережелерді өңдеу және орындалған сканерлеу нәтижелерін талдау.

    Checkmarx ережелерін ақылсыз түрде қалай жазуға боладыCxAudit интерфейсі

  3. Checkmarx ережелері тіл бойынша бөлінген, яғни әрбір тілде өз сұраулар жинағы болады. Сондай-ақ тілге қарамастан қолданылатын кейбір жалпы ережелер бар, олар негізгі сұраулар деп аталады. Көп жағдайда негізгі сұраулар басқа ережелер пайдаланатын ақпаратты іздеуді қамтиды.

    Checkmarx ережелерін ақылсыз түрде қалай жазуға боладыЕрежелерді тіл бойынша бөлу

  4. Ережелер «Орындалатын» және «Орындалатын емес» (Орындалған және Орындалмаған). Менің ойымша, бұл дұрыс атау емес, бірақ бұл солай. Қорытындысы: «Орындалатын» ережелерді орындау нәтижесі UI-де сканерлеу нәтижелерінде көрсетіледі, ал «Орындалмайтын» ережелер олардың нәтижелерін басқа сұрауларда (мәні бойынша жай функция) пайдалану үшін ғана қажет.

    Checkmarx ережелерін ақылсыз түрде қалай жазуға боладыҚұру кезінде ереже түрін анықтау

  5. Сіз жаңа ережелер жасай аласыз немесе барларын толықтыра/қайта жаза аласыз. Ережені қайта жазу үшін оны ағаштан тауып, тінтуірдің оң жақ түймешігімен басып, ашылмалы мәзірден «Алдын ала анықтау» тармағын таңдау керек. Бұл жерде жаңа ережелер бастапқыда алдын ала орнатуларға қосылмағанын және белсенді емес екенін есте ұстаған жөн. Оларды пайдалануды бастау үшін құралдағы «Алдын ала орнату менеджері» мәзірінде белсендіру қажет. Қайта жазылған ережелер өз параметрлерін сақтайды, яғни ереже белсенді болса, ол сол күйінде қалады және бірден қолданылады.

    Checkmarx ережелерін ақылсыз түрде қалай жазуға боладыPreset Manager интерфейсіндегі жаңа ереженің мысалы

  6. Орындау кезінде сұраныстардың «ағашы» салынады, ол неге байланысты. Ақпаратты жинайтын ережелер бірінші орында, ал оны пайдаланатындар екінші орында. Орындау нәтижесі кэштелген, сондықтан егер бар ереженің нәтижелерін пайдалану мүмкін болса, оны жасаған дұрыс, бұл сканерлеу уақытын қысқартады.

  7. Ережелер әртүрлі деңгейлерде қолданылуы мүмкін:

  • Бүкіл жүйе үшін - кез келген жобаны кез келген сканерлеу үшін пайдаланылады

  • Команда деңгейінде (Команда) – таңдалған топтағы жобаларды сканерлеу үшін ғана пайдаланылады.

  • Жоба деңгейінде - Нақты жобада қолданылатын болады

    Checkmarx ережелерін ақылсыз түрде қалай жазуға боладыЕреженің қолданылатын деңгейін анықтау

Жаңадан бастаушыларға арналған «Сөздік».

Мен сұрақ тудырған бірнеше нәрселерден бастаймын, сонымен қатар өмірді айтарлықтай жеңілдететін бірқатар әдістерді көрсетемін.

Тізімдермен операциялар

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

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

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

Барлық табылған заттар

Сканерленген тілде Checkmarx анықтаған мүлде барлық элементтердің тізімін алуға болады (жолдар, функциялар, сыныптар, әдістер және т.б.). Бұл арқылы қол жеткізуге болатын кейбір нысандар кеңістігі All. Яғни, белгілі бір атаумен нысанды іздеу searchMe, мысалы, барлық табылған нысандар бойынша аты бойынша іздеуге болады:

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

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

Бірақ қандай да бір себептермен сканерлеуге қосылмаған басқа тілде іздеу қажет болса (мысалы, Android жобасындағы керемет), сіз айнымалы арқылы объект кеңістігін кеңейте аласыз:

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

Ағынды талдауға арналған функциялар

Бұл функциялар көптеген ережелерде қолданылады және мұнда олардың нені білдіретінін көрсететін шағын парақ берілген:

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

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

Файл атауы/жолы алынуда

Сұрау нәтижелерінен алуға болатын бірнеше атрибуттар бар (жазба табылған файлдың аты, жол және т.б.), бірақ құжаттамада оларды алу және пайдалану жолы айтылмаған. Сонымен, мұны істеу үшін сізге LinePragma қасиетіне қол жеткізу керек және бізге қажет нысандар оның ішінде орналасады:

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

Осыны есте ұстаған жөн FileName файлға жолды қамтиды, өйткені біз әдісті қолдандық GetFirstGraph.

Орындау нәтижесі

CxQL ішінде арнайы айнымалы бар result, ол жазбаша ережені орындау нәтижесін қайтарады. Ол бірден инициализацияланады және оған аралық нәтижелерді жаза аласыз, жұмыс барысында оларды өзгертіп, нақтылай аласыз. Бірақ, ереже ішінде осы айнымалыға немесе функцияға тағайындау болмаса return— орындау нәтижесі әрқашан нөлге тең болады.

Келесі сұрау орындау нәтижесінде бізге ештеңе қайтармайды және әрқашан бос болады:

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

Бірақ орындалу нәтижесін сиқырлы айнымалы нәтижеге тағайындап, біз бұл қоңыраудың бізге не қайтаратынын көреміз:

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

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

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

Басқа ережелердің нәтижелерін пайдалану

Checkmarx ережелерін кәдімгі бағдарламалау тіліндегі функцияларға ұқсас деп атауға болады. Ережені жазу кезінде сіз басқа сұраулардың нәтижелерін пайдалана аласыз. Мысалы, кодтағы барлық әдіс шақыруларын әр уақытта іздеудің қажеті жоқ, жай ғана қажетті ережені шақырыңыз:

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

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

Бұл тәсіл кодты қысқартуға және ереженің орындалу уақытын айтарлықтай қысқартуға мүмкіндік береді.

Ақауларды жою

Тіркеу

Құралмен жұмыс істегенде, кейде қажетті сұранысты бірден жазу мүмкін емес және әртүрлі опцияларды қолданып, тәжірибе жасауға тура келеді. Мұндай жағдайда құрал журналға жазуды қамтамасыз етеді, ол келесідей аталады:

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

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

Бірақ бұл әдіс тек кіріс ретінде қабылданатынын есте ұстаған жөн жол, сондықтан бірінші операцияның нәтижесінде табылған элементтердің толық тізімін көрсету мүмкін болмайды. Түзету үшін пайдаланылатын екінші опция мезгіл-мезгіл сиқырлы айнымалыға тағайындау болып табылады result сұрау нәтижесі және не болатынын қараңыз. Бұл тәсіл өте ыңғайлы емес; кодта онымен қайта анықтау немесе операциялар жоқ екеніне сенімді болуыңыз керек. result немесе жай ғана төмендегі кодты түсіндіріңіз. Немесе сіз, мен сияқты, дайын ережеден осындай бірнеше қоңырауларды алып тастауды ұмытып, неге ештеңе жұмыс істемейтініне таң қалуыңыз мүмкін.

Неғұрлым ыңғайлы әдіс - әдісті шақыру return қажетті параметрмен. Бұл жағдайда ереженің орындалуы аяқталады және біз жазғандарымыздың нәтижесінде не болғанын көре аламыз:

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

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

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

Жүйеге кіру мәселесі

CxAudit құралына (ережелерді жазу үшін пайдаланылады) қол жеткізе алмайтын жағдайлар бар. Мұның көптеген себептері болуы мүмкін, соның ішінде апаттар, кенеттен Windows жаңартулары, BSOD және біздің бақылауымыздан тыс басқа да күтпеген жағдайлар. Бұл жағдайда кейде дерекқорда қайта кіруге кедергі болатын аяқталмаған сеанс болады. Оны түзету үшін сізге бірнеше сұрауларды орындау қажет:

8.6 алдындағы Checkmarx үшін:

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

8.6 кейін Checkmarx үшін:

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

Жазу ережелері

Енді біз ең қызықты бөлікке жетеміз. CxQL-де ережелерді жазуды бастағанда, сізге көбінесе құжаттама емес, белгілі бір мәселелерді шешудің кейбір тірі мысалдары және жалпы сұраулардың қалай жұмыс істейтінін сипаттау жетіспейді.

Мен сұрау тіліне еніп жатқандар үшін өмірді жеңілдетуге тырысамын және белгілі бір мәселелерді шешу үшін Теңшелетін сұрауларды пайдаланудың бірнеше мысалдарын келтіремін. Олардың кейбіреулері айтарлықтай жалпы және сіздің компанияңызда іс жүзінде өзгеріссіз қолданылуы мүмкін, басқалары нақтырақ, бірақ оларды қолданбаларыңыздың ерекшеліктеріне сәйкес кодты өзгерту арқылы да пайдалануға болады.

Сонымен, біз жиі кездесетін мәселелер:

Сынақ: Ережені орындау нәтижелерінде бірнеше Ағындар бар және олардың бірі екіншісінің ұясы болып табылады, олардың біреуін қалдыру керек.

шешім: Шынында да, кейде Checkmarx бір-біріне сәйкес келуі және басқалардың қысқартылған нұсқасы болуы мүмкін бірнеше деректер ағындарын көрсетеді. Мұндай жағдайлар үшін арнайы әдіс бар Ағынды азайту. Параметрге байланысты ол ең қысқа немесе ең ұзын Ағынды таңдайды:

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

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

Сынақ: Құрал әрекет ететін құпия деректер тізімін кеңейтіңіз

шешім: Checkmarx негізгі ережелері бар, олардың нәтижелері көптеген басқа сұраулармен пайдаланылады. Осы ережелердің кейбірін қолданбаңызға арнайы деректермен толықтыра отырып, сканерлеу нәтижелерін бірден жақсартуға болады. Төменде сізге бастау үшін үлгі ереже берілген:

Жалпы_құпиялық_бұзушылық_тізімі

Құпия ақпаратты сақтау үшін қолданбамызда қолданылатын бірнеше айнымалыларды қосамыз:

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

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

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

Сынақ: Құпия сөздермен айнымалылар тізімін кеңейтіңіз

шешім: Мен кодта құпия сөздерді анықтаудың негізгі ережесіне дереу назар аударуды және оған сіздің компанияңызда жиі қолданылатын айнымалы атаулардың тізімін қосуды ұсынамын.

Құпиясөз_құпиялық_бұзушылық_тізімі

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

Сынақ: Checkmarx қолдамайтын пайдаланылған жақтауларды қосыңыз

шешім: Checkmarx ішіндегі барлық сұраулар тіл бойынша бөлінген, сондықтан әрбір тіл үшін ережелерді қосу керек. Төменде осындай ережелердің кейбір мысалдары берілген.

Егер стандартты функцияларды толықтыратын немесе ауыстыратын кітапханалар пайдаланылса, оларды негізгі ережеге оңай қосуға болады. Содан кейін оны қолданатын әрбір адам жаңа енгізулер туралы бірден біледі. Мысал ретінде Android жүйесіне кіруге арналған кітапханалар Timber және Loggi болып табылады. Негізгі пакетте жүйелік емес қоңырауларды анықтау ережелері жоқ, сондықтан пароль немесе сеанс идентификаторы журналға енсе, біз бұл туралы білмейміз. Checkmarx ережелеріне осындай әдістердің анықтамаларын қосуға тырысайық.

Тіркеу үшін ағаш кітапханасын пайдаланатын сынақ кодының мысалы:

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

Мұнда қосымшадан деректер үшін шығу нүктесі ретінде Timber әдістерін шақыру анықтамасын қосуға мүмкіндік беретін Checkmarx сұрауының мысалы келтірілген:

FindAndroidOutputs

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

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

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

Сондай-ақ көрші ережеге қосуға болады, бірақ бұл Android жүйесіне кіруге тікелей қатысты:

FindAndroidLog_Outputs

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

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

Сондай-ақ, егер Android қолданбалары пайдаланылса WorkManager асинхронды жұмыс үшін тапсырмадан деректерді алу әдісін қосу арқылы бұл туралы Checkmarx-ке қосымша хабарлау жақсы идея. getInputData:

FindAndroidRead

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

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

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

Сынақ: iOS жобаларына арналған plist ішіндегі құпия деректерді іздеу

шешім: iOS жүйесі әртүрлі айнымалылар мен мәндерді сақтау үшін .plist кеңейтімі бар арнайы файлдарды жиі пайдаланады. Құпия сөздерді, таңбалауыштарды, кілттерді және басқа құпия деректерді осы файлдарда сақтау ұсынылмайды, өйткені оларды құрылғыдан еш қиындықсыз шығаруға болады.

Plist файлдарында қарапайым көзге көрінбейтін мүмкіндіктер бар, бірақ Checkmarx үшін маңызды. Бізге қажетті деректерді іздейтін ереже жазайық және парольдер немесе токендер бір жерде айтылғанын айтады.

Мұндай файлдың мысалы, серверлік қызметпен байланысу үшін таңбалауышы бар:

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

Жазу кезінде ескеру қажет бірнеше нюанстары бар Checkmarx ережесі:

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

Сынақ: XML-де ақпаратты табу

шешім: Checkmarx XML-мен жұмыс істеу және мәндерді, тегтерді, атрибуттарды және т.б. іздеу үшін өте ыңғайлы функцияларға ие. Бірақ, өкінішке орай, құжаттамада қате болды, соның салдарынан бірде-бір мысал жұмыс істемейді. Құжаттаманың соңғы нұсқасында бұл ақау жойылғанына қарамастан, құжаттардың бұрынғы нұсқаларын пайдалансаңыз, абай болыңыз.

Міне, құжаттамадан дұрыс емес мысал:

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

Орындау әрекетінің нәтижесінде біз қатені аламыз All мұндай әдіс жоқ... Және бұл дұрыс, өйткені XML-мен жұмыс істеу үшін функцияларды пайдалану үшін арнайы, бөлек нысан кеңістігі бар - cxXPath. Android жүйесінде HTTP трафигін пайдалануға мүмкіндік беретін параметрді табу үшін дұрыс сұрау осылай көрінеді:

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

Оны егжей-тегжейлі қарастырайық, өйткені барлық функциялардың синтаксисі ұқсас, біреуін анықтағаннан кейін сізге қажет нәрсені таңдау керек. Сонымен, параметрлер бойынша дәйекті түрде:

  • "*.xml"— ізделетін файлдар маскасы

  • 8 — ереже қолданылатын тілдің идентификаторы

  • "cleartextTrafficPermitted"— xml-дегі атрибут атауы

  • "true" — осы атрибуттың мәні

  • false — іздеу кезінде тұрақты тіркесті қолдану

  • true — іздеу регистрді, яғни регистрді ескермей орындалатынын білдіреді

Мысал ретінде біз HTTP протоколы арқылы сервермен байланысуға мүмкіндік беретін Android жүйесінде қауіпсіздік тұрғысынан қате желілік қосылым параметрлерін анықтайтын ережені қолдандық. Атрибутты қамтитын параметрдің мысалы cleartextTrafficPermitted мәнімен бірге 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>

Сынақ: Нәтижелерді файл аты/жолы бойынша шектеңіз

шешім: Android жүйесіне арналған мобильді қосымшаны әзірлеуге қатысты ірі жобалардың бірінде біз бұрмалау параметрін анықтайтын ереженің жалған позитивтеріне тап болдық. Факті мынада, қораптан тыс ереже файлда іздейді build.gradle қолданбаның шығарылым нұсқасы үшін бұрмалау ережелерін қолдануға жауапты параметр.

Бірақ үлкен жобаларда кейде еншілес файлдар болады build.gradle, жобаға енгізілген кітапханаларға сілтеме жасайды. Ерекшелігі мынада, егер бұл файлдар шатастыру қажеттілігін көрсетпесе де, компиляция кезінде ата-аналық жинақ файлының параметрлері қолданылады.

Осылайша, тапсырма кітапханаларға жататын еншілес файлдардағы триггерлерді кесу болып табылады. Оларды сызықтың болуы арқылы анықтауға болады apply 'com.android.library'.

Файлдан алынған мысал код build.gradle, бұл шатастыру қажеттілігін анықтайды:

apply plugin: 'com.android.application'

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

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

dependencies {
  ...
}

Файл мысалы build.gradle бұл параметрі жоқ жобаға енгізілген кітапхана үшін:

apply plugin: 'android-library'

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

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

Және 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);
		}
	}
}

Бұл тәсіл әмбебап және тек Android қолданбалары үшін ғана емес, нәтиженің белгілі бір файлға тиесілілігін анықтау қажет болған басқа жағдайларда да пайдалы болуы мүмкін.

Сынақ: Синтаксиске толық қолдау көрсетілмесе, үшінші тарап кітапханасына қолдау қосыңыз

шешім: Код жазу процесінде қолданылатын әртүрлі фреймворктардың саны диаграммалардан тыс. Әрине, Checkmarx олардың бар екендігі туралы әрқашан біле бермейді, және біздің міндетіміз - белгілі бір әдістердің осы шеңберге арнайы жататынын түсінуге үйрету. Кейде бұл фреймворктердің өте кең таралған функция атауларын пайдалануымен қиындайды және белгілі бір шақырудың белгілі бір кітапханаға қатынасын бір мағыналы анықтау мүмкін емес.

Қиындық мынада, мұндай кітапханалардың синтаксисі әрқашан дұрыс таныла бермейді және көптеген жалған позитивтерді алмау үшін тәжірибе жасау керек. Сканерлеу дәлдігін жақсарту және мәселені шешу үшін бірнеше нұсқа бар:

  • Бірінші нұсқа, біз кітапхананың белгілі бір жобада қолданылатынын және ережені команда деңгейінде қолдана алатынын нақты білеміз. Бірақ егер команда басқа тәсілді қабылдауды шешсе немесе функция атаулары сәйкес келетін бірнеше кітапхананы пайдаланса, біз көптеген жалған позитивтердің өте жағымды емес бейнесін ала аламыз.

  • Екінші нұсқа - кітапхана анық импортталған файлдарды іздеу. Бұл тәсіл арқылы бізге қажет кітапхана дәл осы файлда қолданылғанына сенімді бола аламыз.

  • Ал үшінші нұсқа – жоғарыда аталған екі тәсілді бірге қолдану.

Мысал ретінде тар шеңберде танымал кітапхананы қарастырайық жіңішке Scala бағдарламалау тілі үшін, атап айтқанда, функционалдылық Литералдық мәндерді қосу. Жалпы, параметрлерді SQL сұрауына беру үшін операторды пайдалану керек $, ол деректерді алдын ала жасалған SQL сұрауына ауыстырады. Яғни, бұл Java тіліндегі Prepared Statement бағдарламасының тікелей аналогы. Бірақ, егер сізге SQL сұрауын динамикалық түрде құру қажет болса, мысалы, кесте атауларын беру қажет болса, операторды пайдалануға болады. #$, ол деректерді сұрауға тікелей ауыстырады (жолды біріктіру сияқты).

Үлгі код:

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

Checkmarx әзірше Splicing Literal мәндерін пайдалануды және операторларды өткізіп жіберуді қалай анықтау керектігін білмейді. #$, сондықтан оны әлеуетті SQL инъекцияларын анықтауға және кодтағы дұрыс орындарды бөлектеуге үйретуге тырысайық:

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

Сынақ: Ашық бастапқы кітапханаларда пайдаланылған осал функцияларды іздеңіз

шешім: Көптеген компаниялар әзірленген қолданбаларда кітапханалардың осал нұсқаларын пайдалануды анықтау үшін ашық бастапқы бақылау құралдарын (OSA тәжірибесі) пайдаланады. Кейде мұндай кітапхананы қауіпсіз нұсқаға жаңарту мүмкін емес. Кейбір жағдайларда функционалдық шектеулер бар, басқаларында қауіпсіз нұсқа мүлдем жоқ. Бұл жағдайда SAST және OSA тәжірибелерінің тіркесімі осалдықты пайдалануға әкелетін функциялардың кодта пайдаланылмайтынын анықтауға көмектеседі.

Бірақ кейде, әсіресе JavaScript-ті қарастырғанда, бұл мүлдем тривиальды тапсырма болмауы мүмкін. Төменде шешім, мүмкін идеалды емес, бірақ құрамдастағы осалдықтардың мысалын пайдалана отырып, жұмыс істейді. lodash әдістерде template и *set.

JS файлындағы ықтимал осал кодты тексеру мысалдары:

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

Ал 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>

Біз осалдықтарда көрсетілген барлық осал әдістерді іздейміз:

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

Сынақ: Қолданбаға ендірілген сертификаттарды іздеу

шешім: Қолданбалардың, әсіресе мобильді құрылғылардың әртүрлі серверлерге кіру немесе SSL-Pinning растау үшін сертификаттарды немесе кілттерді пайдалануы сирек емес. Қауіпсіздік тұрғысынан мұндай нәрселерді кодта сақтау ең жақсы тәжірибе емес. Репозиторийдегі ұқсас файлдарды іздейтін ережені жазуға тырысайық:

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

Сынақ: Қолданбада бұзылған таңбалауыштарды табу

шешім: Көбінесе кодта бар бұзылған таңбалауыштарды немесе басқа маңызды ақпаратты жою қажет. Әрине, оларды бастапқы кодта сақтау жақсы идея емес, бірақ жағдайлар әртүрлі. CxQL сұрауларының арқасында осындай нәрселерді табу өте оңай:

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

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

қорытынды

Бұл мақала Checkmarx құралымен танысуды бастағандар үшін пайдалы болады деп үміттенемін. Мүмкін, ұзақ уақыт бойы өз ережелерін жазып жүргендер де осы нұсқаулықтан пайдалы нәрсе табады.

Өкінішке орай, қазіргі уақытта Checkmarx ережелерін әзірлеу кезінде жаңа идеяларды алуға болатын ресурс жетіспейді. Сондықтан біз құрдық Github репозиторийі, онда біз CxQL қолданатын әрбір адам одан пайдалы нәрсе таба алатындай етіп, сонымен қатар өз жұмысын қауымдастықпен бөлісе алатындай етіп өз жұмысымызды орналастырамыз. Репозиторий мазмұнды толтыру және құрылымдау процесінде, сондықтан үлес қосушыларды шақырамыз!

Назарларыңызға рахмет!

Ақпарат көзі: www.habr.com

пікір қалдыру