Tere, Habr!
Oma töös puutume tihti kokku erinevate staatilise koodianalüüsi tööriistadega (SAST). Need töötavad välja pakendist keskmiselt. Loomulikult sõltub kõik projektist ja kasutatavatest tehnoloogiatest, samuti sellest, kui hästi need tehnoloogiad on analüüsireeglitega kaetud. Minu arvates on üks tähtsamaid kriteeriume SAST tööriista valimisel selle kohandamise võimalus vastavalt oma rakenduste eripäradele, nimelt analüüsireeglite kirjutamine ja muutmine, või, nagu neid sagedamini nimetatakse, kohandatud päringud.

Me kasutame kõige sagedamini Checkmarxi — väga huvitav ja võimas koodi analüsaator. Selles artiklis räägin oma kogemustest reeglite kirjutamisel selle jaoks.
Sisukord
Sissejuhatus
Alustuseks soovitaksin ühte vähestest artiklitest vene keeles, mis käsitleb Checkmarxi päringute kirjutamise omadusi. See avaldati Habr's lõpus 2019. aastal pealkirja all: .
Selles käsitletakse, kuidas kirjutada esimesi päringuid CxQL (Checkmarx Query Language) keeles mõne testrakenduse jaoks ning tutvustatakse analüüsireeglite põhialuseid.
Ma ei hakka kordama seda, mis seal on kirjas, kuigi mõningane kattumine aset leiab. Oma artiklis püüan koostada teatud „retseptide kogumi“, loetelu konkreetsetest lahendustest, millega olen kokku puutunud oma töös Checkmarxiga. Paljude nende probleemide lahendamiseks pidin tõsiselt vaeva nägema. Mõnikord polnud dokumentatsioonis piisavalt andmeid, mõnikord oli aga tõeliselt raske mõista, kuidas teha seda, mis on vajalik. Loodan, et minu kogemus ja unetud ööd ei lähe raisku ning see „Custom Queries retseptide kogumik“ säästab teile paar tundi või mitmeid närvirakke. Nii et alustame!
Üksikasjalik teave reeglite kohta
Alustuseks vaatame üle mõned põhikontseptsioonid ja reeglite tööprotsessi, et paremini mõista, mis edaspidi toimuma hakkab. Samuti seetõttu, et dokumentatsioonis pole sellest piisavalt selgelt räägitud või on see struktuuris liiga hajutatud, mis ei ole väga mugav.
Reeglid kehtivad skaneerimise käigus sõltuvalt valitud algsest presetist (aktiivsete reeglite komplektist). Presette saab luua piiramatult, ja nende struktuur sõltub teie tööprotsessi omadustest. Need võivad olla grupeeritud keelte kaupa või määratud igale projekti eraldi. Aktiivsete reeglite arv mõjutab skaneerimise kiirus ja täpsust.
Preset'i seadistamine Checkmarx'i liidesesReegleid redigeeritakse spetsiaalses tööriistas nimega CxAuditor. See on lauaarvuti rakendus, mis ühendub Checkmarx'i serveriga. Sellel tööriistal on kaks töörežiimi: reeglite redigeerimine ja juba toimunud skaneerimise tulemuste analüüs.
CxAudit'i liidesCheckmarx'i reeglid on jagatud keelte kaupa, st iga keele jaoks on oma päringute kogum. Samuti on mõningaid üldisi reegleid, mis on rakendatavad olenemata keelest, need on nn põhiküsimused. Enamasti sisaldavad põhiküsimused teavet, mida kasutavad muud reeglid.
Reeglite jagamine keelte kaupaReeglid võivad olla 'Executable' ja 'Non-Executable' (Täidetavad ja Mitte Täidetavad). Minu arvates ei ole see täielikult täpne nimetus, aga mis seal ikka. Oluline on, et 'Executable' reeglite täitmise tulemus kuvatakse skaneerimise tulemustes kasutajaliideses, samas kui 'Non-Executable' reeglid on vajalikud ainult nende tulemuste kasutamiseks muudes päringutes (essentsiaalselt — lihtsalt funktsioon).
Reegli tüübi määramine loomiselUute reeglite loomine või olemasolevate täiendamine\/ümberkirjutamine on võimalik. Reegli ümberkirjutamiseks tuleb see leida puust, paremklõpsata sellel ja valida avanevast menüüst punkt „Override“. Oluline on meeles pidada, et uued reeglid ei ole algselt presetsidesse kaasatud ja ei ole aktiivsed. Nende kasutamiseks tuleb need aktiveerida „Preset Manager“ menüüs tööriistades. Ümber kirjutatud reeglid säilitavad oma seaded, seega, kui reegel oli aktiivne, jääb see selliseks ja rakendatakse kohe.
Uue reegli näide Preset Manageri liidesesTäideviimise ajal moodustatakse „puu“ päringutest, mis omavahel seotud on. Esiteks täidetakse reeglid, mis koguvad teavet, seejärel need, kes seda kasutavad. Täitmise tulemus vaheldub, seega, kui on võimalik kasutada olemasoleva reegli tulemusi, on parem seda teha, see vähendab skaneerimise aega.
Reegleid saab rakendada erinevatel tasanditel:
Kogu süsteem — kasutatakse igasuguste projektide igasuguste skaneerimiste jaoks
Meeskonna tasemel — rakendatakse ainult valitud meeskonnas projektide skannimiseks.
Projektitasemel — rakendatakse konkreetses projektis.
Reegli rakendamise taseme määramine.
„Sõnastik“ algajale
Alustan mitmest asjast, mis on mind kummitanud, ja näitan mitmeid nippe, mis oluliselt lihtsustavad elu.
Tegevused loenditega
- üksteise lahutamine (list2 - list1)
* loendite ristumine (list1 * list2)
+ loendite liitmine (list1 + list2)
& (loogiline JA) - ühendab loendeid vastavusest (list1 & list2), sarnaselt ristumisega (list1 * list2)
| (loogiline VÕI) - ühendab loendeid laias otsingus (list1 | list2)
Loenditega ei toimi: ^ && || % \/ Kõik leitud elemendid
Skannitava keele raames saab saada täieliku nimekirja kõigist elementidest, mille on määratlenud Checkmarx (stringid, funktsioonid, klassid, meetodid jne). See on objekti ruum, mida saab aadressida läbi Kõik. Seega, et otsida objekti, millel on konkreetne nimi searchMe, saab otsida näiteks nime alusel kõigilt leitud objektidelt:
// Такой запрос выдаст все элементы
result = All;
// Такой запрос выдаст все элементы, в имени которых присутствует “searchMe“
result = All.FindByName("searchMe");Kuid kui on vaja otsida teises keeles, mis mingil põhjusel ei sisaldunud skannimises (nt groovy Androidi projektis), saame laiendada meie objekti ruumi muutuja kaudu:
result = AllMembers.All.FindByName("searchMe");Flow analüüsi funktsioonid
Need funktsioonid on kasutusel paljudes reeglites ja siin on väike nö. abimaterjal, mida nad tähendavad:
// Какие данные second влияют на first.
// Другими словами - ТО (second) что влияет на МЕНЯ (first).
result = first.DataInfluencedBy(second);
// Какие данные first влияют на second.
// Другими словами - Я (first) влияю на ТО (second).
result = first.DataInfluencingOn(second);Faili nime/tee saamine
On mitmeid atribuute, mida saab küsitluse tulemuste seast (failinimi, kus esinemine leiti, rida jne), kuid kuidas neid saada ja dokumentatsioonis kasutada pole öeldud. Nii et selleks, et seda teha, tuleb pöörduda omaduse LinePragma poole, ja seal sees on vajalikud objektid:
// Для примера найдем все методы
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);Tasub arvesse võtta, et FileName sisaldab tegelikult faili teed, kuna oleme kasutanud meetodit GetFirstGraph.
Tegevuse tulemus
CxQL-is on ette nähtud eriline muutuja tulemus, mis tagastab teie kirjutatud reegli täitmise tulemuse. See on kohe algatatud ja temasse saab salvestada vahepealseid tulemusi, muutes ja täpsustades neid töö käigus. Kuid kui reeglis ei ole seda muutujat või funktsiooni määratud, return— teostuse tulemus on alati null.
Järgmine päring ei anna meile kunagi midagi tagastuseks ja jääb alati tühjaks:
// Находим элементы foo
CxList libraries = All.FindByName("foo");Kuid kui omistame teostuse tulemuse maagilisele muutuja result — näeme, mida see kutsung meile tagastab:
// Находим элементы foo
CxList libraries = All.FindByName("foo");
// Выводим, как результат выполнения правила
result = libraries
// Или еще короче
result = All.FindByName("foo");Muude reeglite tegevuse tulemuste kasutamine
Checkmarxi reegleid võib võrrelda tavalistes programmeerimiskeeltes olevate funktsioonidega. Reegli kirjutamisel võite kasutada teiste päringute tulemusi. Näiteks ei ole vaja iga kord otsida kõiki meetodi kutsungeid koodis, piisab, kui kutsuda vajalik reegel:
// Получаем результат выполнения другого правила
CxList methods = Find_Methods();
// Ищем внутри метод foo.
// Второй параметр false означает, что ищем без чувствительности к регистру
result = methods.FindByShortName("foo", false);Selline lähenemine võimaldab koodi lühendada ja vähendab reegli täitmise aega oluliselt.
Probleemide lahendamine
Logimine
Tööriista kasutamisel ei pruugi inimestel kohe õiget päringut kirjutada ja vahel tuleb katsetada, proovides erinevaid variante. Sel juhul on tööriistas logimine, mida kutsutakse järgmiselt:
// Находим что-то
CxList toLog = All.FindByShortName("log");
// Формируем строку и отправляем в лог
cxLog.WriteDebugMessage (“number of DOM elements =” + All.Count);Aga tasub meeles pidada, et see meetod võtab sisendiks ainult stringi, seega ei saa esitada täielikku lehte leitud elementide jaoks esimese toimingu tulemuses. Teine variant, mida kasutatakse tõrkeotsinguks, on aeg-ajalt määrata maagilisele muutujale tulemus päringu tulemuse ja vaadata, mis juhtub. Selline lähenemine ei ole eriti mugav, tuleb olla kindel, et koodis ei toimu hiljem üle määramist või toimingute tegemist selle tulemus või lihtsalt kommenteerida allpool asuvat koodi. Või nagu mina, unustada, et lõpetatud reeglist eemaldada paar sellist kutset ja imestada, miks see ei tööta.
Mugavam viis on kutsuda meetod return vajaliku parameetriga. Sellisel juhul lõppeb reegli täitmine ja me saame näha, mis meie kirjutatud tulemusena välja tuli:
// Находим что-то
CxList toLog = All.FindByShortName("log");
// Выводим результат выполнения
return toLog
//Все, что написано дальше не будет выполнено
result = All.DataInfluencedBy(toLog)Sisselogimise probleem
Juhtub, et ei saa sisse logida tööriista CxAudit (mida kasutatakse reeglite kirjutamiseks). Põhjuseid võib olla palju: ootamatu tarkvara sulgemine, Windowsi äkiline värskendus, BSOD ning teised ettenägematud olukorrad, millele me ei saa vastu. Sellisel juhul võib andmebaasis jääda lõpetamata seanss, mis takistab uuesti sisselogimist. Probleemi lahendamiseks on vajalik sooritada mitmeid päringuid:
Checkmarx versioonides kuni 8.6:
// Проверяем, что есть залогиненые пользователи, выполнив запрос в БД
SELECT COUNT(*) FROM [CxDB].[dbo].LoggedinUser WHERE [ClientType] = 6;
// Если что-то есть, а на самом деле даже если и нет, попробовать выполнить запрос
DELETE FROM [CxDB].[dbo].LoggedinUser WHERE [ClientType] = 6;
Checkmarx versioonides pärast 8.6:
// Проверяем, что есть залогиненые пользователи, выполнив запрос в БД
SELECT COUNT(*) FROM LoggedinUser WHERE (ClientType = 'Audit');
// Если что-то есть, а на самом деле даже если и нет, попробовать выполнить запрос
DELETE FROM [CxDB].[dbo].LoggedinUser WHERE (ClientType = 'Audit');Reeglite kirjutamine
Nüüd jõuame kõige huvitavama osa juurde. CxQL reeglite kirjutamisel tunnevad paljud, et vajatakse mitte ainult dokumentatsiooni, vaid ka elavaid näiteid teatud ülesannete lahendamiseks ja päringute tööprotsessi üldkirjeldust.
Proovin veidi lihtsustada elu neile, kes alles alustavad päringute keelde sukeldumist, ja toon välja mõned näited kohandatud päringute kasutamisest teatud ülesannete lahendamiseks. Mõned neist on piisavalt üldised ja neid saab teie ettevõttes kasutada praktiliselt ilma muudatusteta, teised on spetsiifilisemad, kuid neid saab samuti kohandada vastavalt teie rakenduste eripäradele.
Siin on kõige sagedamini ette tulnud ülesanded:
Ülesanne: Reeglite täitmise tulemustes on mitu voogu ja üks neist on teise sees, seetõttu on vajalik jätta neist üks.
Lahenduseks on: Tõepoolest, mõnikord näitab Checkmarx mitut andmevoogu, mis võivad kattuda ja olla lühem versioon teistest. Selliste juhtumite jaoks on erimeetod: ReduceFlow. Sõltuvalt parameetrist valib ta lühema või pikema voo:
// Оставить только длинные Flow
result = result.ReduceFlow(CxList.ReduceFlowType.ReduceSmallFlow);
// Оставить только короткие Flow
result = result.ReduceFlow(CxList.ReduceFlowType.ReduceBigFlow);Ülesanne: Laiendada tundlike andmete loetelu, millele tööriist reageerib.
Lahenduseks on: Checkmarxis on olemas baasreeglid, mille täitmise tulemusi kasutatakse paljudes teistes päringutes. Täiendades mõningaid neist reeglitest teie rakendusele spetsiifiliste andmetega, saab skaneerimise tulemusi kohe parandada. Allpool on näide reeglist, millega alustada:
General_privacy_violation_list
Lisame mõned muutujad, mida meie rakenduses tundliku teabe salvestamiseks kasutatakse:
// Получаем результат выполнения базового правила
result = base.General_privacy_violation_list();
// Ищем элементы, которые попадают под простые регулярные выражения. Можно дополнить характерными для вас паттернами.
CxList personalList = All.FindByShortNames(new List<string> {
"*securityToken*", "*sessionId*"}, false);
// Добавляем к конечному результату
result.Add(personalList);Ülesanne: Laiendada paroolidega muutujaid.
Lahenduseks on: Soovitan kohe tähelepanu pöörata paroolide määratlemise põhireeglile koodis ja lisada sellele nimekiri muutujatest, mida teie ettevõttes tavaliselt kasutatakse.
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*"));
// Täiendame vaikimisi muutuja nimekirja
List < string > pswdIncludeList = new List<string>{"*password*", "*psw", "psw*", "pwd*", "*pwd", "*authKey*", "pass*", "cipher*", "*cipher", "pass", "parool", "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);
}
}Ülesanne: Lisage Checkmarxi toetust mittesüsteemsete raamistikute jaoks.
Lahenduseks on: Kõik Checkmarxi päringud on jaotatud keeltesse, seega tuleb reegleid täiendada iga keele jaoks eraldi. Alljärgnev on mõned näited sellistest reeglitest.
Kui kasutatakse teeke, mis täiendavad või asendavad standardset funktsionaalsust – on neid lihtne lisada põhireeglisse. Nii saavad kõik, kes seda kasutavad, kohe uusi teadlikkusi. Näiteks Androidi logimise teegid – Timber ja Loggi. Põhireeglites ei ole määratletud mitte-süsteemseid väljakutseid, seega kui parool või sessiooni ID satub logisse, siis me sellest ei saa teada. Proovime lisada Checkmarxi reeglitesse selliste meetodite määratlemise.
Koodinäide, mis kasutab Timberi teeki logimiseks:
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("Veateade");
Timber.d("Kinnitusteade");
Timber.tag("Mõni erinev sild").e("Ja veateade");
}
}Siin on näide päringust Checkmarxile, mis võimaldab lisada Timberi meetodite väljakutse määratlemise rakenduse andmeväljumise kohana:
FindAndroidOutputs
// Получаем результат выполнения базового правила
result = base.Find_Android_Outputs();
// Дополняем вызовами, которые приходят из библиотеки Timber
CxList timber = All.FindByExactMemberAccess("Timber.*") +
All.FindByShortName("Timber").GetMembersOfTarget();
// Добавляем к конечному результату
result.Add(timber);Samuti on võimalik täiendada naaberreeglit, kuid see seondub juba otseselt logimisega Androidis:
FindAndroidLog_Outputs
// Получаем результат выполнения базового правила
result = base.Find_Android_Log_Outputs();
// Дополняем вызовами, которые приходят из библиотеки Timber
result.Add(
All.FindByExactMemberAccess("Timber.*") +
All.FindByShortName("Timber").GetMembersOfTarget()
);Kui Androidi rakendustes kasutatakse ka asünkroonseks töötamiseks, on hea lisada sellele ka Checkmarxile teavet, lisades meetodi, mis saadab andmeid ülesanne getInputData:
FindAndroidRead
// Получаем результат выполнения базового правила
result = base.Find_Android_Read();
// Дополняем вызовом функции getInputData, которая используется в WorkManager
CxList getInputData = All.FindByShortName("getInputData");
// Добавляем к конечному результату
result.Add(getInputData.GetMembersOfTarget());Ülesanne: Otsimine tundlike andmete järele plist failides iOS projektides
Lahenduseks on: Sageli kasutatakse iOSis erinevate muutuja ja väärtuste salvestamiseks spetsiaalseid .plist laiendiga faile. Paroolide, tokenite, võtmete ja teiste tundlike andmete salvestamine neis failides ei ole soovitatav, kuna neid saab kergesti seadmest välja võtta.
Plist failidel on omadused, mis ei ole palja silmaga ilmsed, kuid on Checkmarxile olulised. Kirjutame reegli, mis otsib vajalikke andmeid ja teavitab meid, kui kuskil mainitakse paroole või tokenite.
Näide sellisest failist, kuhu on sisse kirjutatud token backendiga suhtlemiseks:
DeviceDictionary
phone
iPhone 6s
privatekey
MIICXAIBAAKBgQCqGKukO1De7zhZj6+Ja Checkmarx'i jaoks on reegel, milles on mitu nüanssi, mida tuleb kirjutamisel arvesse võtta:
// Используем результат выполнения правила по поиску файлов 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);Ülesanne: Teabe otsimine XML-is
Lahenduseks on: Checkmarxis on väga mugavad funktsioonid XML-iga töötamiseks ja väärtuste, siltide, atribuutide jms otsimiseks. Kuid kahjuks on dokumentatsioonis viga, mille tõttu ei toimi ükski näide. Kuigi viimases versioonis on see puudus kõrvaldatud, olge ettevaatlik, kui kasutate varasemaid dokumentide versioone.
Siin on vale näide dokumentatsioonist:
// Код работать не будет
result = All.FindXmlAttributesByNameAndValue("*.app", 8, “id”, "error- section", false, true);Proovi täitmise tulemusel saame vea, et Kõik sellist meetodit ei eksisteeri... Ja see on tõsi, kuna XML-iga töötamiseks mõeldud funktsioonide kasutamiseks on olemas spetsiaalne ja eraldi objektide ruum — cxXPath. Nii näeb välja õige päring Androidis, mis lubab HTTP liikluse kasutamist:
// Правильный вариант с использованием cxXPath
result = cxXPath.FindXmlAttributesByNameAndValue("*.xml", 8, "cleartextTrafficPermitted", "true", false, true);Vaatame lähemalt, kuna kõigi funktsioonide süntaks on sarnane. Kui oled ühega tutvunud, tuleb edaspidi vaid valida sobiv. Järgnevalt läheneme sellele parameetrite kaupa:
"*.xml"— failide mask, mille alusel otsingut teha8— keele id, mille jaoks reegel kehtib"cleartextTrafficPermitted"— atribuutide nimi xml-s"true"— selle attribuudi väärtusfalse— regulaaravaldiste kasutamine otsingustrue— tähendab, et otsingut teostatakse suur- ja väiketähtede erinevust arvesse võtmata, st case-insensitive
Näiteks on kasutusel reegel, mis määratleb, et Androidis on turvapaigutustes ebaõige seadistused, mis lubavad serveriga suhtlemist HTTP protokolli kaudu. Näide seadistusest, mis sisaldab atribuuti cleartextTrafficPermitted väärtusega true:
example.com
secure.example.comÜlesanne: Piirata tulemusi faili nime/tee järgi
Lahenduseks on: Ühes suuremas projektis, mis on seotud Androidi mobiilirakenduse arendamisega, seisime silmitsi valehäiretega reegli osas, mis määrab obfuskeeringu seaded. Fakt on see, et reegel vaikimisi otsib failist build.gradle seadet, mis vastutab obfuskeeringureeglite rakendamise eest rakenduse versiooni puhul.
Kuid suurtes projektides kohtab vahel tütarfailide build.gradle, mis kuuluvad projekti kaasatud raamatukogude alla. Eriti huvitav on see, et isegi kui nende failide sees ei ole obfuskeeringu vajadust, rakendatakse kompileerimise käigus vanema ehitusfaili seadeid.
Seega seisneb ülesanne tütarfailidelt, mis kuuluvad raamatukogude alla, valehäirete vältimises. Need on määratletavad stringi järgi apply 'com.android.library'.
Näide koodist failist build.gradle, mis määratleb obfuskeeringu vajaduse:
apply plugin: 'com.android.application'
android {
compileSdkVersion 24
buildToolsVersion "24.0.2"
defaultConfig {
...
}
buildTypes {
release {
minifyEnabled true
...
}
}
}
dependencies {
...
}Näide failist build.gradle raamatukogust, mis on projektis ja ei oma sellist seadet:
rakenda plugin: 'android-library'
dependencies {
compile 'com.android.support:support-v4:18.0.+'
}
android {
compileSdkVersion 14
buildToolsVersion '17.0.0'
...
}Ja reegel Checkmarxile:
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);
}
}
}See lähenemine võib olla piisavalt universaalne ja osutuda kasulikuks mitte ainult Androidi rakenduste puhul, vaid ka muudes olukordades, kus tuleb määrata tulemuse kuuluvus kindlale failile.
Ülesanne: Lisage toe välistele raamatukogudele, kui süntaksit ei toetata täielikult
Lahenduseks on: Füüsiliselt on olemas tohutult erinevaid raamistikku, mida kasutatakse koodi kirjutamise protsessis. Loomulikult ei pruugi Checkmarx alati nende olemasolust teadlik olla ja meie ülesanne on õpetada teda mõistma, et teatud meetodid kuuluvad just sellesse raamistikku. Mõnikord raskendab seda asjaolu, et raamistikud kasutavad funktsioonide nimesid, mis on laialdaselt levinud ja ei võimalda üheselt määrata, kuidas igasugused nimetused seonduvad teatud raamatukoguga.
Probleem seisab selles, et selliste raamatukogude süntaksit ei pruugita alati õigesti tuvastada, mistõttu tuleb katsetada, et vältida paljusid valehäireid. On mitu võimalust, kuidas skaneerimise täpsust parandada ja üritatud ülesanne lahendada:
Esimene võimalus on, et me teame täpselt, et raamatukogu kasutatakse konkreetses projektis, ja saame rakendada reegli meeskonna tasandil. Kuid juhul, kui meeskond otsustab kasutada teistsugust lähenemist või kasutab mitmeid raamatukogusid, kus funktsioonide nimed kattuvad, võime saada väga ebameeldiva pildi arvukatest valehäiretest.
Teine võimalus on rakendada otsingut failide järgi, kus raamatukogu on selgelt imporditud. Selle lähenemise korral saame olla kindlad, et antud failis rakendatakse vajalikku raamatukogu.
Ja kolmas võimalus on kahe eespool nimetatud lähenemise ühiselt kasutamine.
Näiteks analüüsime kitsastes ringkondades tuntud raamatukogu Scala programmeerimiskeele jaoks, täpsemalt funktsionaalsust . Üldiselt, et edastada parameetreid SQL-päringusse, tuleb kasutada operaatorit $, mis asendab andmed eelnevalt vormistatud SQL-päringus. See on tegelikult otsene analoog Prepared Statement'ile Java-s. Kuid kui on vaja dünaamiliselt konstrueerida SQL-päringut, näiteks kui on vajalik edastada tabelite nimesid, võib kasutada operaatorit #$, mis asendab andmed päringusse otse (praktiliselt nagu stringide konkateneerimine).
Koodinäide:
// В общем случае - значения, контролируемые пользователем
val table = "coffees"
sql"select * from #$table where name = $name".as[Coffee].headOptionCheckmarx ei oska veel tuvastada Splicing Literal Values kasutamist ja jätab operaatorid #$, seega proovime õpetada seda tuvastama võimalikke SQL-süstimise kohti ja esile tuua vajalikud kohad koodis:
// Находим все импорты
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));
}Ülesanne: Otsime kasutatavaid haavatavaid funktsioone avatud koodiga raamatukogudes
Lahenduseks on: Paljuski ettevõtted kasutavad Open-Source'i kontrollimise tööriistu (OSA praktika), mis võimaldavad kindlaks teha haavatavate versioonide raamatukogude kasutamist arendatavates rakendustes. Mõnikord ei ole võimalik sellist raamatukogu uuendada turvalisse versiooni. Mõnel juhul on funktsionaalsed piirangud, teistes aga turvalist versiooni ei eksisteeri. Sellises olukorras võib abiks olla SAST ja OSA praktikate kombinatsioon, mis võimaldab tuvastada, et koodis ei kasutata haavatavuse ekspluateerimist võimaldavaid funktsioone.
Kuid mõnikord, eriti kui arvestada JavaScripti, pole see sugugi triviaalne ülesanne. Allpool on esitatud lahendus, mis ei pruugi olla ideaalne, kuid siiski toimib, haavatavuste näitel komponendis lodash meetodites template ja *set.
Näited potentsiaalselt haavatavast testikoodist JS failis:
/**
* 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!'Ja otse HTML-i lisamisel:
<!DOCTYPE html>
<html>
<head>
<title>Lodashi õpetus</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>Otsime kõik meie haavatavad meetodid, mis on loetletud haavatavustes:
// Ищем все строки: в которых встречается строка 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));Ülesanne: Otsing rakendusesse embeditud sertifikaatide jaoks
Lahenduseks on: Tihti kasutavad rakendused, eriti mobiilirakendused, sertifikaate või võtmeid erinevate serverite juurde pääsemiseks või SSL-pinningu kontrollimiseks. Kui vaadata turvalisuse aspektist — ei ole nende hoidmine koodis parim praktikate näide. Proovime kirjutada reegli, mis otsib selliseid faile hoidlas:
// Найдем все сертификаты по маске файла
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;Ülesanne: Kompromiteeritud tokenite otsimine rakenduses
Lahenduseks on: Tihti tuleb tagasi kutsuda kompromiteeritud tokeneid või muud olulist teavet, mis on koodis sees. Loomulikult ei ole nende hoidmine lähtekoodis kõige parem idee, kuid olukordi on erinevaid. Tänu CxQL päringutele on selliste asjade leidmine piisavalt lihtne:
// Получаем все строки, которые содержатся в коде
CxList strings = base.Find_Strings();
// Ищем среди всех строк нужное нам значение. В примере токен в виде строки "qwerty12345"
result = strings.FindByShortName("qwerty12345");Kokkuvõte
Loodan, et see artikkel on kasulik neile, kes alustavad oma tutvust Checkmarxiga. Võib-olla leiavad ka need, kes juba ammu oma reegleid kirjutavad, sellest juhendist midagi väärtuslikku.
Kahjuks on praegu suur puudus ressurssidest, kust saaks uut inspiratsiooni Checkmarxi reeglite arendamise käigus. Seetõttu lõime , kus jagame oma arendusi, et igaühel, kes kasutab CxQL-i, oleks võimalus leida sealt midagi kasulikku ning jagada oma töid kogukonnaga. Repositsioon on sisu täiendamise ja struktureerimise protsessis, seega panustajad on oodatud!
Aitäh tähelepanu eest!
Allikas: habr.com

Preset'i seadistamine Checkmarx'i liideses
CxAudit'i liides
Reeglite jagamine keelte kaupa
Reegli tüübi määramine loomisel
Uue reegli näide Preset Manageri liideses
Reegli rakendamise taseme määramine.