چگونه برای چکمارکس قوانین بنویسیم بدون اینکه دیوانه شویم

هی هابر!

در کار ما، شرکت ما اغلب با ابزارهای مختلف تحلیل کد استاتیک (SAST) سر و کار دارد. خارج از جعبه همه آنها به طور متوسط ​​کار می کنند. البته همه چیز بستگی به پروژه و فناوری های به کار رفته در آن و همچنین میزان پوشش این فناوری ها توسط قوانین تحلیل دارد. به نظر من، یکی از مهم‌ترین معیارها هنگام انتخاب ابزار SAST، توانایی شخصی‌سازی آن با ویژگی‌های برنامه‌های کاربردی شما، یعنی نوشتن و تغییر قوانین تجزیه و تحلیل یا، همانطور که اغلب به آنها، درخواست سفارشی می‌گویند، است.

چگونه برای چکمارکس قوانین بنویسیم بدون اینکه دیوانه شویم

ما اغلب از Checkmarx استفاده می کنیم - یک تحلیلگر کد بسیار جالب و قدرتمند. در این مقاله در مورد تجربه خود از نوشتن قوانین تجزیه و تحلیل برای آن صحبت خواهم کرد.

فهرست مندرجات

ورود

برای شروع، من می خواهم یکی از معدود مقالات به زبان روسی را در مورد ویژگی های نوشتن پرس و جو برای Checkmarx توصیه کنم. در پایان سال 2019 در هابره با این عنوان منتشر شد: "سلام چکمارکس!" چگونه یک پرس و جو Checkmarx SAST بنویسیم و آسیب پذیری های جالب را پیدا کنیم.

این به طور مفصل نحوه نوشتن اولین پرس و جوها را در CxQL (زبان پرس و جوی چکمارکس) برای برخی از برنامه های آزمایشی بررسی می کند و اصول اولیه نحوه عملکرد قوانین تجزیه و تحلیل را نشان می دهد.

آنچه در آن شرح داده شده است را تکرار نمی کنم، اگرچه برخی از تقاطع ها همچنان وجود خواهند داشت. در مقاله خود سعی خواهم کرد نوعی "مجموعه دستور العمل ها" را گردآوری کنم، فهرستی از راه حل ها برای مشکلات خاصی که در طول کار خود با Checkmarx با آن مواجه شدم. من مجبور شدم برای بسیاری از این مشکلات مغزم را جمع و جور کنم. گاهی اوقات اطلاعات کافی در اسناد وجود نداشت و حتی گاهی اوقات درک نحوه انجام کارهای مورد نیاز دشوار بود. امیدوارم تجربه و شب های بی خوابی من بیهوده نباشد و این "مجموعه دستور العمل های سوالات سفارشی" چند ساعت یا چند سلول عصبی شما را نجات دهد. بنابراین، بیایید شروع کنیم!

اطلاعات عمومی در مورد قوانین

ابتدا، اجازه دهید به چند مفهوم اساسی و روند کار با قوانین نگاه کنیم تا درک بهتری از آنچه در آینده رخ خواهد داد. و همچنین به این دلیل که اسناد چیزی در این مورد نمی گویند یا در ساختار بسیار گسترده است، که خیلی راحت نیست.

  1. این قوانین در حین اسکن بسته به پیش تنظیم انتخاب شده در شروع (مجموعه ای از قوانین فعال) اعمال می شوند. شما می‌توانید تعداد نامحدودی از پیش‌تنظیمات ایجاد کنید، و نحوه ساختار دقیق آنها به ویژگی‌های فرآیند شما بستگی دارد. می توانید آنها را بر اساس زبان گروه بندی کنید یا از پیش تنظیم ها برای هر پروژه انتخاب کنید. تعداد قوانین فعال بر سرعت و دقت اسکن تأثیر می گذارد.

    چگونه برای چکمارکس قوانین بنویسیم بدون اینکه دیوانه شویمتنظیم Preset در واسط Checkmarx

  2. قوانین در یک ابزار ویژه به نام CxAuditor ویرایش می شوند. این یک برنامه دسکتاپ است که به سروری که Checkmarx را اجرا می کند متصل می شود. این ابزار دارای دو حالت کار است: ویرایش قوانین و تجزیه و تحلیل نتایج یک اسکن قبلا انجام شده.

    چگونه برای چکمارکس قوانین بنویسیم بدون اینکه دیوانه شویمرابط CxAudit

  3. قوانین در Checkmarx بر اساس زبان تقسیم می شوند، یعنی هر زبان مجموعه ای از پرس و جوهای خاص خود را دارد. همچنین برخی از قوانین کلی وجود دارد که صرف نظر از زبان اعمال می شود، اینها به اصطلاح پرس و جوهای اساسی هستند. در بیشتر موارد، پرس و جوهای اساسی شامل جستجوی اطلاعاتی است که قوانین دیگر استفاده می کنند.

    چگونه برای چکمارکس قوانین بنویسیم بدون اینکه دیوانه شویمتقسیم قواعد بر اساس زبان

  4. قوانین "اجرا" و "غیر قابل اجرا" (اجرا شده و اجرا نشده) هستند. به نظر من نام کاملاً درستی نیست، اما همین است. نکته اصلی این است که نتیجه اجرای قوانین "اجرا" در نتایج اسکن در رابط کاربری نمایش داده می شود و قوانین "غیر اجرایی" فقط برای استفاده از نتایج آنها در سایر درخواست ها (در اصل فقط یک تابع) مورد نیاز است.

    چگونه برای چکمارکس قوانین بنویسیم بدون اینکه دیوانه شویمتعیین نوع قانون هنگام ایجاد

  5. می توانید قوانین جدیدی ایجاد کنید یا قوانین موجود را تکمیل یا بازنویسی کنید. برای بازنویسی یک قانون، باید آن را در درخت پیدا کنید، راست کلیک کرده و از منوی کشویی گزینه «Override» را انتخاب کنید. در اینجا مهم است که به یاد داشته باشید که قوانین جدید در ابتدا در تنظیمات از پیش تنظیم نشده اند و فعال نیستند. برای شروع استفاده از آنها باید آنها را در منوی "Preset Manager" در ابزار فعال کنید. قوانین بازنویسی شده تنظیمات خود را حفظ می کنند، یعنی اگر قانون فعال بود، به همان صورت باقی می ماند و بلافاصله اعمال می شود.

    چگونه برای چکمارکس قوانین بنویسیم بدون اینکه دیوانه شویممثالی از یک قانون جدید در رابط مدیریت از پیش تعیین شده

  6. در حین اجرا، یک "درخت" از درخواست ها ساخته می شود که به چه چیزی بستگی دارد. قوانینی که اطلاعات را جمع آوری می کنند ابتدا اجرا می شوند و در مرحله دوم کسانی که از آن استفاده می کنند. نتیجه اجرا در حافظه پنهان است، بنابراین اگر امکان استفاده از نتایج یک قانون موجود وجود دارد، بهتر است این کار را انجام دهید، این باعث کاهش زمان اسکن می شود.

  7. قوانین را می توان در سطوح مختلف اعمال کرد:

  • برای کل سیستم - برای هر اسکن هر پروژه استفاده می شود

  • در سطح تیم (تیم) - فقط برای اسکن پروژه ها در تیم انتخاب شده استفاده می شود.

  • در سطح پروژه - در یک پروژه خاص اعمال خواهد شد

    چگونه برای چکمارکس قوانین بنویسیم بدون اینکه دیوانه شویمتعیین سطحی که قانون در آن اعمال خواهد شد

"فرهنگ لغت" برای مبتدیان

و من با چند چیز شروع خواهم کرد که برای من سؤال ایجاد کرد، و همچنین تعدادی تکنیک را نشان خواهم داد که به طور قابل توجهی زندگی را ساده می کند.

عملیات با لیست

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

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

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

همه موارد پیدا شده

در زبان اسکن شده، می‌توانید فهرستی از تمام عناصری را که Checkmarx شناسایی کرده است (رشته‌ها، توابع، کلاس‌ها، متدها و غیره) دریافت کنید. این فضایی از اشیاء است که از طریق آن می توان به آن دسترسی داشت All. یعنی برای جستجوی یک شی با نام خاص searchMe، می توانید به عنوان مثال، با نام در تمام اشیاء یافت شده جستجو کنید:

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

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

اما، اگر نیاز به جستجو به زبان دیگری دارید که به دلایلی در اسکن گنجانده نشده است (به عنوان مثال، groovy در یک پروژه اندروید)، می توانید فضای شی ما را از طریق یک متغیر گسترش دهید:

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 (که برای نوشتن قوانین استفاده می شود) دسترسی پیدا کنید. دلایل زیادی برای این وجود دارد، از جمله خرابی، به روز رسانی ناگهانی ویندوز، 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 می کنید، چیزی که اغلب فاقد آن هستید، مستندات زیادی نیست، بلکه برخی از نمونه های زنده حل مشکلات خاص و توصیف فرآیند عملیات پرس و جو به طور کلی است.

من سعی خواهم کرد زندگی را برای کسانی که شروع به فرو رفتن در زبان پرس و جو کرده اند کمی آسان تر کنم و چندین مثال از استفاده از پرس و جوهای سفارشی برای حل مشکلات خاص ارائه کنم. برخی از آنها کاملاً عمومی هستند و می توانند در شرکت شما عملاً بدون تغییر استفاده شوند، برخی دیگر خاص تر هستند، اما همچنین می توان با تغییر کد متناسب با ویژگی های برنامه های خود از آنها استفاده کرد.

بنابراین، در اینجا مشکلاتی که اغلب با آن مواجه می شویم آورده شده است:

چالش: چندین فلو در نتایج اجرای قانون وجود دارد که یکی از آنها تودرتوی دیگری است که باید یکی از آنها را ترک کنید.

راه حل: در واقع، گاهی اوقات Checkmarx چندین جریان داده را نشان می دهد که ممکن است با هم همپوشانی داشته باشند و نسخه کوتاه شده دیگری باشند. برای چنین مواردی روش خاصی وجود دارد ReduceFlow. بسته به پارامتر، کوتاه ترین یا طولانی ترین جریان را انتخاب می کند:

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

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

چالش: فهرست داده های حساسی را که ابزار به آنها واکنش نشان می دهد، گسترش دهید

راه حل: چکمارکس قوانین اساسی دارد که نتایج آن توسط بسیاری از پرس و جوهای دیگر استفاده می شود. با تکمیل برخی از این قوانین با داده های خاص برنامه خود، می توانید بلافاصله نتایج اسکن خود را بهبود بخشید. در زیر یک قانون مثال برای شروع شما آورده شده است:

فهرست_نقض_حریم_خصوصی_عمومی

بیایید چندین متغیر را اضافه کنیم که در برنامه ما برای ذخیره اطلاعات حساس استفاده می شود:

// Получаем результат выполнения базового правила
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 بر اساس زبان تقسیم می شوند، بنابراین باید قوانینی را برای هر زبان اضافه کنید. در زیر چند نمونه از این قوانین آورده شده است.

اگر از کتابخانه هایی استفاده شود که مکمل یا جایگزین عملکرد استاندارد باشند، به راحتی می توان آنها را به قانون اساسی اضافه کرد. سپس همه کسانی که از آن استفاده می کنند بلافاصله با معرفی جدید آشنا می شوند. به عنوان مثال، کتابخانه هایی برای ورود به سیستم اندروید Timber و Loggi هستند. در بسته اصلی، هیچ قانونی برای شناسایی تماس‌های غیر سیستمی وجود ندارد، بنابراین اگر رمز عبور یا شناسه جلسه وارد گزارش شود، ما از آن مطلع نخواهیم شد. بیایید سعی کنیم تعاریفی از این روش ها را به قوانین چکمارکس اضافه کنیم.

نمونه کد آزمایشی که از کتابخانه 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);

و همچنین می توانید به قانون همسایه اضافه کنید، اما این یکی مستقیماً به ورود به سیستم اندروید مربوط می شود:

FindAndroidLog_Outputs

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

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

همچنین در صورت استفاده از برنامه های اندروید مدیر کار برای کارهای ناهمزمان، ایده خوبی است که با افزودن روشی برای دریافت داده از کار، چکمارکس را نیز در این مورد مطلع کنید. 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 است:

<?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 فراهم می کند. مثالی از یک تنظیم حاوی یک ویژگی 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>

چالش: نتایج را بر اساس نام/مسیر فایل محدود کنید

راه حل: در یکی از پروژه های بزرگ مربوط به توسعه اپلیکیشن موبایل برای اندروید، با موارد مثبت کاذب قانون تعیین کننده تنظیمات مبهم مواجه شدیم. واقعیت این است که قانون خارج از جعبه در فایل جستجو می کند 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'
  ...
}

و قانون چکمارکس:

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

این رویکرد می تواند نه تنها برای برنامه های اندروید، بلکه برای موارد دیگری که باید تعیین کنید آیا یک نتیجه متعلق به یک فایل خاص است، کاملاً جهانی و مفید باشد.

چالش: اگر نحو به طور کامل پشتیبانی نمی شود، پشتیبانی از یک کتابخانه شخص ثالث را اضافه کنید

راه حل: تعداد فریمورک‌های مختلفی که در فرآیند نوشتن کد استفاده می‌شوند به سادگی خارج از نمودار است. البته چکمارکس همیشه از وجود آنها اطلاعی ندارد و وظیفه ما این است که به او آموزش دهیم تا بفهمد که روش های خاصی به طور خاص به این چارچوب تعلق دارند. گاهی اوقات این امر با این واقعیت پیچیده می شود که فریم ورک ها از نام های توابعی استفاده می کنند که بسیار متداول هستند و تعیین صریح رابطه یک فراخوانی خاص با یک کتابخانه خاص غیرممکن است.

مشکل این است که نحو چنین کتابخانه هایی همیشه به درستی تشخیص داده نمی شود و شما باید برای جلوگیری از دریافت تعداد زیادی از موارد مثبت کاذب آزمایش کنید. چندین گزینه برای بهبود دقت اسکن و حل مشکل وجود دارد:

  • گزینه اول، ما مطمئن هستیم که کتابخانه در یک پروژه خاص استفاده می شود و می تواند قانون را در سطح تیم اعمال کند. اما اگر تیم تصمیم بگیرد رویکرد متفاوتی داشته باشد یا از چندین کتابخانه استفاده کند که نام توابع در آنها همپوشانی دارند، می‌توانیم تصویری نه چندان خوشایند از موارد مثبت کاذب متعدد به دست آوریم.

  • گزینه دوم جستجو برای فایل هایی است که کتابخانه به وضوح در آنها وارد شده است. با این رویکرد می توان مطمئن بود که کتابخانه مورد نیاز ما دقیقاً در این فایل استفاده شده است.

  • و گزینه سوم استفاده از دو رویکرد بالا با هم است.

به عنوان مثال، بیایید به کتابخانه ای که در محافل باریک شناخته شده است نگاه کنیم نرمی برای زبان برنامه نویسی اسکالا، یعنی عملکرد پیوند ارزش های تحت اللفظی. به طور کلی، برای ارسال پارامترها به یک کوئری SQL، باید از عملگر استفاده کنید $، که داده ها را با یک کوئری SQL از پیش ساخته شده جایگزین می کند. یعنی در واقع یک آنالوگ مستقیم از Prepared Statement در جاوا است. اما، اگر نیاز به ساخت پویا یک پرس و جوی 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));
}

چالش: توابع آسیب پذیر استفاده شده را در کتابخانه های منبع باز جستجو کنید

راه حل: بسیاری از شرکت ها از ابزارهای نظارت منبع باز (عمل OSA) برای تشخیص استفاده از نسخه های آسیب پذیر کتابخانه ها در برنامه های کاربردی توسعه یافته استفاده می کنند. گاهی اوقات امکان به روز رسانی چنین کتابخانه ای به نسخه ایمن وجود ندارد. در برخی موارد محدودیت های عملکردی وجود دارد، در برخی دیگر اصلا نسخه ایمن وجود ندارد. در این مورد، ترکیبی از روش‌های SAST و OSA به تعیین اینکه عملکردهایی که منجر به سوءاستفاده از آسیب‌پذیری می‌شوند در کد استفاده نمی‌شوند، کمک می‌کند.

اما گاهی اوقات، به خصوص زمانی که جاوا اسکریپت را در نظر می گیریم، ممکن است این کار کاملاً بی اهمیت نباشد. در زیر یک راه حل است، شاید ایده آل نباشد، اما با این وجود، با استفاده از مثال آسیب پذیری در مؤلفه کار می کند. 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 آغاز می کنند مفید واقع شود. شاید کسانی که برای مدت طولانی قوانین خود را می نویسند نیز در این راهنما چیز مفیدی بیابند.

متأسفانه، در حال حاضر کمبود منبعی وجود دارد که در آن بتوان ایده های جدیدی را در طول توسعه قوانین چکمارکس به دست آورد. به همین دلیل ما ایجاد کردیم مخزن در Github، جایی که ما کار خود را پست خواهیم کرد تا همه کسانی که از CxQL استفاده می کنند بتوانند چیز مفیدی در آن بیابند و همچنین این فرصت را داشته باشند که کار خود را با جامعه به اشتراک بگذارند. مخزن در حال پر کردن و ساختاردهی محتوا است، بنابراین از مشارکت کنندگان استقبال می شود!

با تشکر از شما!

منبع: www.habr.com

اضافه کردن نظر