Как да напишете правила за Checkmarx и да не полудете

Здравей, Хабр!

В нашата работа, компанията ни често се сблъсква с различни инструменти за статичен анализ на код (SAST). От тая гледна точка те всички работят средно. Разбира се, всичко зависи от проекта и използваните в него технологии, както и от това, колко добре тези технологии са покрити с правила за анализ. Според мен, едно от най-важните критерии при избора на инструмент SAST е възможността да го настройвате спрямо особеностите на вашите приложения, а именно писането и изменянето на правила за анализ или, както по-често ги наричат, Custom Queries.

Как да напишете правила за Checkmarx и да не полудете

Най-често използваме Checkmarx — много интересен и мощен анализатор на код. В тази статия ще споделя своя опит в писането на правила за анализ за него.

Съдържание

Въведение

За начало, бих искал да препоръча една от малкото статии на български език относно особеностите на написването на заявки за Checkmarx. Тя беше публикувана в Хабра в края на 2019 година под заглавието: „Здравей, Checkmarx!“. Как да напишем заявка за Checkmarx SAST и да открием страхотни уязвимости.

В нея подробно е разгледано как да напишете първите заявки на езика CxQL (Checkmarx Query Language) за едно тестово приложение и са показани основните принципи на работа на правилата за анализ.

Няма да повтарям това, което е написано в нея, въпреки че някои пресичания все пак ще присъстват. В моята статия ще се опитам да съставя някакъв „сборник с рецепти“, списък с решения на конкретни задачи, с които се сблъсквах по време на работата си с Checkmarx. Много от тези задачи наистина ми костваха много размисли. Понякога липсваха данни в документацията, а понякога беше изключително трудно да се разбере как да се направи това, което се изисква. Надявам се, че опитът ми и безсънните нощи няма да отидат напразно и този „сборник с рецепти за Custom Queries“ ще ви спести няколко часа или поне няколко нервни клетки. И така, нека започнем!

Обща информация по правилата

Нека започнем с няколко основни понятия и процеса на работа с правилата, за по-добро разбиране на това, което ще се случи по-нататък. И още, защото в документацията по това не е споменато или е силно размазано из структурата, което не е много удобно.

  1. Правилата се прилагат при сканиране в зависимост от избрания при старта пресет (набор активни правила). Можете да създадете неограничен брой пресети и как точно да ги структурирате зависи от особеностите на вашия процес. Можете да ги групирате по езици или да отделите пресети за всеки проект. Броят на активните правила влияе на скоростта и точността на сканирането.

    Как да напишете правила за Checkmarx и да не полудетеНастройка на Preset в интерфейса на Checkmarx

  2. Правилата се редактират в специален инструмент, наречен CxAuditor. Това е десктоп приложение, което се свързва със сървъра на Checkmarx. Този инструмент има два режима на работа: редактиране на правила и анализ на резултатите от вече проведеното сканиране.

    Как да напишете правила за Checkmarx и да не полудетеИнтерфейс CxAudit

  3. Правилата в Checkmarx са разделени по езици, тоест за всеки език съществува свой набор от заявки. Съществуват и някои общи правила, които се прилагат независимо от езика, наричани основни заявки. В повечето случаи основните заявки съдържат търсене на информация, която се използва от други правила.

    Как да напишете правила за Checkmarx и да не полудетеРазделяне на правилата по езици

  4. Правилата могат да бъдат “Executable” и “Non-Executable” (Изпълними и Неизпълними). Не съвсем коректно название, на мое мнение, но така е. Същността е, че резултатът от прилагането на “Executable” правилата ще бъде показан в резултатите от сканирането в UI, докато “Non-Executable” правилата са нужни само за използване на техните резултати в други заявки (по същество — просто функция).

    Как да напишете правила за Checkmarx и да не полудетеОпределение на типа правило при създаване

  5. Можете да създавате нови правила или да допълвате/преписвате съществуващите. За да препишете правило, трябва да го намерите в дървото, да кликнете с десния бутон и в падащото меню да изберете опцията “Override“. Важно е да запомните, че новите правила първоначално не са включени в пресетите и не са активни. За да започнете да ги използвате, трябва да ги активирате в менюто “Preset Manager” в инструмента. Преписаните правила запазват своите настройки, тоест, ако правилото е било активно, такова то и ще остане и ще се прилага веднага.

    Как да напишете правила за Checkmarx и да не полудетеПример на ново правило в интерфейса Preset Manager

  6. По време на изпълнението се изгражда "дърво" от запитвания, което от какво зависи. Най-напред се изпълняват правилата, които събират информация, а след това тези, които я използват. Резултатът от изпълнението се кешира, така че ако има възможност да се използва резултатът от съществуващото правило, по-добре е да се направи така, тъй като това ще намали времето за сканиране.

  7. Правилата могат да се прилагат на различни нива:

  • За цялата система — ще се използва за всяко сканиране на всеки проект

  • На ниво отбор (Team) — ще се прилага само за сканиране на проекти в избрания отбор.

  • На ниво проект — ще се прилага в конкретен проект

    Как да напишете правила за Checkmarx и да не полудетеОпределение на нивото, на което ще се прилага правилото

„Речник“ за начинаещи

И ще започна с няколко неща, които предизвикаха у мен въпроси, както и ще покажа редица трикове, които значително ще улеснят живота.

Операции със списъци

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

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

Списъците не работят с:  ^  &&  ||  %  \/ 

Всички намерени елементи

В рамките на сканирания език може да се получи списък на абсолютно всички елементи, които е определил Checkmarx (редове, функции, класове, методи и т.н.). Това е някакво пространство от обекти, до което може да се обърнете чрез All. Тоест, за търсене на обект с конкретно име searchMe, можете да извършите търсене, например, по име сред всички намерени обекти:

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

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

Но, ако трябва да се извърши търсене по друг език, който по някакви причини не е влязъл в сканирането (например groovy в проект за Android), можете да разширите нашето пространство от обекти чрез променлива:

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

Функции за анализ на Flow

Тези функции се използват в много правила и ето малка шпаргалка, какво означават:

// Какие данные 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 има специална променлива резултат, която връща резултата от изпълнението на написаното от вас правило. Тя е инициализирана веднага и можете да записвате в нея междинни резултати, като ги променяте и уточнявате в процеса на работа. Но, ако в правилото не се присвои на тази променлива или функция return— резултатът от изпълнението винаги ще бъде нулев.

Следващият заявка няма да ни върне нищо в резултат и винаги ще бъде празен:

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

Но, присвоявайки резултата от изпълнението на магическата променлива result — ще видим какво ни дава този повик:

// Находим элементы 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);

Но имайте предвид, че този метод приема само строка, така че не можете да изведете пълния списък на намерените елементи в резултат на първата операция. Вторият вариант, който се използва за отстраняване на грешки — е от време на време да присвоявате магическата променлива резултат резултат от заявката и да наблюдавате какво ще се получи. Този подход не е много удобен, нужно е да сте сигурни, че в кода след това няма преназначаване или операции с него резултат или просто да коментирате разположения по-долу код. А можете, както аз, да забравите да премахнете няколко такива повиквания от готовото правило и да се учудвате, защо нищо не работи.

По-удобен начин — е да извикате метода return с необходимия параметър. В този случай, изпълнението на правилото ще завърши и ще можем да видим какво е резултатът от написаното от нас:

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

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

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

Проблем с входа

Има ситуации, когато не можете да влезете в инструмента CxAudit (който се използва за писане на правила). Причините за това могат да бъдат много — аварийно прекратяване на работа, неочаквано обновление на Windows, BSOD и други непредвидени обстоятелства, които не можем да предвидим. В такъв случай понякога остава незавършена сесия в базата данни, която не позволява повторен вход. За да се поправи, е необходимо да се изпълнят няколко запитвания:

За Checkmarx до 8.6:

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

За Checkmarx след 8.6:

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

Писане на правила

Стигнахме до най-интересната част. Когато започнете да пишете правила на CxQL, често не достига не толкова документация, а живи примери за решения на определени задачи и обяснение на процеса на работа с запитванията като цяло.

Ще се постарая да улесня живота на онези, които започват да се запознават с езика на запитванията, и ще дам няколко примера за използване на Custom Queries за решаване на определени задачи. Някои от тях са достатъчно общи и могат да се приложат в компанията ви почти без промени, докато други са по-специфични, но също могат да се използват, като се променят кодовете в съответствие с особеностите на вашите приложения.

И така, ето с какви задачи най-често сме се сблъсквали:

Задача: В резултатите от изпълнението на правилото има няколко Flow, един от които е вложение на друг, и е необходимо да се запази само един от тях.

Решение: Наистина, понякога Checkmarx показва няколко Flow на движението на данни, които могат да се пресичат и да бъдат съкратена версия на други. За такива случаи има специален метод ReduceFlow. В зависимост от параметъра той ще избере най-краткия или най-дългия Flow:

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

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

Задача: Да се разшири списъкът на чувствителните данни, на които реагира инструментът

Решение: В Checkmarx съществуват основни правила, чиито резултати се използват от много други запитвания. Като добавите някои от тези правила с данни, специфични за вашето приложение, можете веднага да подобрите резултатите от сканирането. По-долу е пример на правило, с което можете да започнете:

General_privacy_violation_list

Нека добавим няколко променливи, които се използват в нашето приложение за съхранение на чувствителна информация:

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

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

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

Задача: Да се разшири списъкът на променливите с пароли

Решение: Препоръчвам веднага да обърнете внимание на основното правило за определяне на пароли в кода и да добавите списък с имена на променливи, които са приети във вашата компания.

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  pswdIncludeList = new List{"*password*", "*psw", "psw*", "pwd*", "*pwd", "*authKey*", "pass*", "cipher*", "*cipher", "pass", "адекватна_парола", "потребителско_име", "шая", "ключ", "парола", "конфиденциален_код", "код", "паролата", "вход", "секрет", "кодекс", "график", "препратка", "control"};

List  pswdExcludeList = new List{"*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 определения за такива методи.

Тестов пример на код, който използва библиотеката Timber за логване:

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

Ето пример на запит за Checkmarx, който позволява добавяне на определение на извикванията на методи Timber, като изходна точка за данни от приложението:

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

Задача: Търсене на чувствителни данни в plist за iOS проекти

Решение: Често за съхранение на различни променливи и стойности в iOS се използват специални файлове с разширение .plist. Съхранението на пароли, токени, ключове и други чувствителни данни в тези файлове не се препоръчва, тъй като без особени затруднения могат да бъдат извлечени от устройството.

Файловете plist имат особености, които не са очевидни на пръв поглед, но са важни за Checkmarx. Нека пишем правило, което ще търси необходимите ни данни и ще ни уведомява, ако някъде се споменават пароли или токени.

Пример за такъв файл, в който е вписан токен за комуникация с сервиз backend:

DeviceDictionary
	
		phone
		iPhone 6s
		
	privatekey
	MIICXAIBAAKBgQCqGKukO1De7zhZj6+

И правило за 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 — id на езика, за който се прилага правилото

  • "cleartextTrafficPermitted"— името на атрибута в xml

  • "true" — стойността на този атрибут

  • неверно — използване на регулярни изрази при търсенето

  • истинно — означава, че търсенето ще се извърши без да се отчита регистра, тоест case-insensitive

За пример е използвано правило, което определя неправилни настройки на мрежовото свързване в Android, които позволяват комуникация със сървера чрез протокол HTTP. Пример за настройка, съдържаща атрибута cleartextTrafficPermitted со значением истинно:

example.com
        
            
        
        
            secure.example.com

Задача: Ограничаване на резултатите по име/път на файла

Решение: В един от големите проекти, свързани с разработката на мобилно приложение за Android, се сблъскахме с фалшиви сработвания на правилото, което определя настройката за обфускация. Факт е, че правилото по подразбиране търси в файла build.gradle настройката, отговаряща за прилагането на правила за обфускация за релийната версия на приложението.

Но в големи проекти понякога се срещат дъщерни файлове build.gradle, които принадлежат на библиотеки, включени в проекта. Особеността е, че дори и в тези файлове да не е посочена необходимостта от обфускация, при компилацията ще се прилагат настройките от родителския файл за сборка.

Така задачата е да се отсекат сработванията в дъщерните файлове, които принадлежат на библиотеки. Определянето им е възможно по наличието на реда apply 'com.android.library'.

Пример на код от файла build.gradle, определящ необходимостта от обфускация:

приложи плагин: 'com.android.application'

андроид {
    compileSdkVersion 24
    buildToolsVersion "24.0.2"
    defaultConfig {
        ...
    }

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

dependencies {
  ...
}

Пример файл build.gradle за библиотеката, включена в проекта и без такава настройка:

приложи плагин: 'android-library'

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

андроид {
  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 не винаги знае за тяхното съществуване и наша задача е да го научим да разбира, че определени методи принадлежат именно на този фрейм. Понякога това е затруднено от факта, че фреймовете използват имена на функции, които са много разпространени и не може да се определи еднозначно връзката между конкретен повик на конкретна библиотека.

Сложността се състои в това, че синтаксисът на такива библиотеки не винаги се разпознава правилно и трябва да експериментираме, за да не получим голямо количество фалшиви срабатывания. Съществуват няколко варианта, за да се подобри точността на сканирането и да се реши поставената задача:

  • Първият вариант е, когато точно знаем, че библиотеката се използва в определен проект и можем да приложим правилото на ниво екип. Но в случай, че екипът реши да използва друг подход или използва няколко библиотеки, в които се пресичат имената на функциите, можем да получим неприятна картина от многобройни фалшиви срабатывания.

  • Вторият вариант е да се приложи търсене по файлове, в които явно се импортира библиотеката. При такъв подход можем да сме сигурни, че в този файл наистина се прилага търсената библиотека.

  • И третият вариант е да използваме двата изброени по-горе подхода заедно.

Като пример ще разгледаме известната в тесни кръгове библиотека slick за езика за програмиране Scala, а именно, функционалността на Splicing Literal Values. В общия случай, за предаване на параметри в SQL запит, е необходимо да се използва операторът $, който вмъква данни в предварително формулиран SQL запит. Тоест, по същество е пряк аналог на Prepared Statement в Java. Но, в случай на нужда динамично да се конструира SQL запит, например, ако трябва да се предават имена на таблици, е възможно да се използва оператор #$, който директно ще вмъкне данните в запита (практически, като конкатенация на низове).

Примерен код:

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

Checkmarx все още не може да определя използването на Splicing Literal Values и пропуска операторите #$, така че ще опитаме да го научим да определя потенциални 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));
}

Задача: Търсене на използвани уязвими функции в Open-Source библиотеки

Решение: В много компании се използват инструменти за контрол на Open-Source (практика 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 Урок</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, да намери нещо полезно, а също така да има възможност да сподели своите трудове с общността. Репозиторият е в процес на попълване и структуриране на съдържанието, така че contributors are welcome!

Благодаря за вниманието!

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster