كيفية كتابة قواعد Checkmarx دون أن تصاب بالجنون

يا هبر!

في عملنا، تتعامل شركتنا في كثير من الأحيان مع العديد من أدوات تحليل التعليمات البرمجية الثابتة (SAST). خارج الصندوق، جميعهم يعملون بشكل متوسط. وبطبيعة الحال، كل هذا يتوقف على المشروع والتقنيات المستخدمة فيه، وكذلك مدى تغطية هذه التقنيات بقواعد التحليل. في رأيي، أحد أهم المعايير عند اختيار أداة SAST هو القدرة على تخصيصها وفقًا لتفاصيل تطبيقاتك، أي كتابة قواعد التحليل وتغييرها أو، كما يطلق عليها غالبًا، الاستعلامات المخصصة.

كيفية كتابة قواعد Checkmarx دون أن تصاب بالجنون

غالبًا ما نستخدم Checkmarx - وهو محلل أكواد مثير للاهتمام وقوي للغاية. وفي هذا المقال سأتحدث عن تجربتي في كتابة قواعد التحليل لها.

جدول المحتويات

دخول

في البداية، أود أن أوصي بإحدى المقالات القليلة باللغة الروسية حول ميزات كتابة الاستعلامات الخاصة بـ Checkmarx. تم نشره على حبري نهاية عام 2019 تحت عنوان: "مرحبًا تشيماركس!" كيفية كتابة استعلام Checkmarx SAST والعثور على نقاط الضعف الرائعة.

وهو يفحص بالتفصيل كيفية كتابة الاستعلامات الأولى في CxQL (لغة استعلام Checkmarx) لبعض تطبيقات الاختبار ويوضح المبادئ الأساسية لكيفية عمل قواعد التحليل.

ولن أكرر ما ورد فيه، رغم أن بعض التقاطعات ستظل موجودة. سأحاول في مقالتي تجميع نوع من "مجموعة الوصفات"، وقائمة من الحلول لمشاكل محددة واجهتها أثناء عملي مع Checkmarx. اضطررت إلى التفكير في العديد من هذه المشاكل. في بعض الأحيان لم تكن هناك معلومات كافية في الوثائق، وفي بعض الأحيان كان من الصعب فهم كيفية القيام بما هو مطلوب. أتمنى ألا تذهب تجربتي وليالي الأرق سدى، وستوفر لك "مجموعة وصفات الاستعلامات المخصصة" هذه بضع ساعات أو بضع خلايا عصبية. لذلك، دعونا نبدأ!

معلومات عامة عن القواعد

أولاً، دعونا نلقي نظرة على بعض المفاهيم الأساسية وعملية العمل مع القواعد، من أجل فهم أفضل لما سيحدث بعد ذلك. وأيضًا لأن الوثائق لا تقول شيئًا عن هذا أو أنها منتشرة جدًا في البنية، وهو أمر غير مناسب جدًا.

  1. يتم تطبيق القواعد أثناء المسح اعتمادًا على الإعداد المسبق المحدد في البداية (مجموعة من القواعد النشطة). يمكنك إنشاء عدد غير محدود من الإعدادات المسبقة، وتعتمد كيفية تنظيمها بالضبط على تفاصيل العملية الخاصة بك. يمكنك تجميعها حسب اللغة أو تحديد الإعدادات المسبقة لكل مشروع. يؤثر عدد القواعد النشطة على سرعة ودقة المسح.

    كيفية كتابة قواعد Checkmarx دون أن تصاب بالجنونإعداد الإعداد المسبق في واجهة Checkmarx

  2. يتم تحرير القواعد في أداة خاصة تسمى CxAuditor. هذا هو تطبيق سطح المكتب الذي يتصل بخادم يقوم بتشغيل Checkmarx. تحتوي هذه الأداة على وضعين للتشغيل: تحرير القواعد وتحليل نتائج الفحص الذي تم إجراؤه بالفعل.

    كيفية كتابة قواعد Checkmarx دون أن تصاب بالجنونواجهة CxAudit

  3. يتم تقسيم القواعد في Checkmarx حسب اللغة، أي أن كل لغة لديها مجموعة الاستعلامات الخاصة بها. هناك أيضًا بعض القواعد العامة التي تنطبق بغض النظر عن اللغة، وهي ما يسمى بالاستعلامات الأساسية. في معظم الأحيان، تتضمن الاستعلامات الأساسية البحث عن المعلومات التي تستخدمها القواعد الأخرى.

    كيفية كتابة قواعد Checkmarx دون أن تصاب بالجنونتقسيم القواعد حسب اللغة

  4. القواعد "قابلة للتنفيذ" و"غير قابلة للتنفيذ" (المنفذة وغير المنفذة). ليس الاسم الصحيح تمامًا، في رأيي، ولكن هذا هو الأمر. خلاصة القول هي أن نتيجة تنفيذ القواعد "القابلة للتنفيذ" سيتم عرضها في نتائج الفحص في واجهة المستخدم، وأن القواعد "غير القابلة للتنفيذ" مطلوبة فقط لاستخدام نتائجها في طلبات أخرى (في جوهرها، مجرد وظيفة).

    كيفية كتابة قواعد Checkmarx دون أن تصاب بالجنونتحديد نوع القاعدة عند الإنشاء

  5. يمكنك إنشاء قواعد جديدة أو استكمال/إعادة كتابة القواعد الحالية. من أجل إعادة كتابة القاعدة، تحتاج إلى العثور عليها في الشجرة، ثم انقر بزر الماوس الأيمن وحدد "تجاوز" من القائمة المنسدلة. من المهم أن تتذكر هنا أن القواعد الجديدة لم يتم تضمينها في البداية في الإعدادات المسبقة وليست نشطة. لبدء استخدامها، يتعين عليك تنشيطها في قائمة "مدير الإعداد المسبق" في الأداة. تحتفظ القواعد المعاد كتابتها بإعداداتها، أي إذا كانت القاعدة نشطة، فستظل كذلك وسيتم تطبيقها على الفور.

    كيفية كتابة قواعد Checkmarx دون أن تصاب بالجنونمثال لقاعدة جديدة في واجهة Preset Manager

  6. أثناء التنفيذ، يتم إنشاء "شجرة" من الطلبات، والتي تعتمد على ماذا. يتم تنفيذ القواعد التي تجمع المعلومات أولاً، ومن يستخدمها ثانيًا. يتم تخزين نتيجة التنفيذ مؤقتًا، لذا إذا كان من الممكن استخدام نتائج قاعدة موجودة، فمن الأفضل القيام بذلك، سيؤدي ذلك إلى تقليل وقت المسح.

  7. يمكن تطبيق القواعد على مستويات مختلفة:

  • للنظام بأكمله - سيتم استخدامه لأي مسح لأي مشروع

  • على مستوى الفريق (الفريق) - سيتم استخدامه فقط لمسح المشاريع في الفريق المحدد.

  • على مستوى المشروع - سيتم تطبيقه في مشروع محدد

    كيفية كتابة قواعد Checkmarx دون أن تصاب بالجنونتحديد المستوى الذي سيتم تطبيق القاعدة عليه

"قاموس" للمبتدئين

وسأبدأ ببعض الأشياء التي أثارت تساؤلاتي، وسأعرض أيضًا عددًا من التقنيات التي من شأنها تبسيط الحياة بشكل كبير.

العمليات مع القوائم

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

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

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

جميع العناصر التي تم العثور عليها

ضمن اللغة الممسوحة ضوئيًا، يمكنك الحصول على قائمة بجميع العناصر التي حددها Checkmarx (السلاسل، والوظائف، والفئات، والأساليب، وما إلى ذلك). هذه هي بعض المساحة من الكائنات التي يمكن الوصول إليها من خلالها All. أي البحث عن كائن باسم محدد searchMe، يمكنك البحث، على سبيل المثال، بالاسم عبر جميع الكائنات التي تم العثور عليها:

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

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

ولكن، إذا كنت بحاجة إلى البحث بلغة أخرى لم يتم تضمينها في الفحص لسبب ما (على سبيل المثال، رائعة في مشروع Android)، فيمكنك توسيع مساحة الكائن الخاصة بنا من خلال متغير:

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

وظائف لتحليل التدفق

تُستخدم هذه الوظائف في العديد من القواعد، وإليك ورقة غش صغيرة لما تعنيه:

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

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

الحصول على اسم الملف/المسار

هناك العديد من السمات التي يمكن الحصول عليها من نتائج الاستعلام (اسم الملف الذي تم العثور على الإدخال فيه، والسلسلة، وما إلى ذلك)، ولكن الوثائق لا توضح كيفية الحصول عليها واستخدامها. لذا، للقيام بذلك، تحتاج إلى الوصول إلى خاصية LinePragma وستكون الكائنات التي نحتاجها موجودة بداخلها:

// Для примера найдем все методы
CxList methods = Find_Methods();

// В методах найдем по имени метод scope
CxList scope = methods.FindByName("scope");

// Таким образом можо получить путь к файлу
string current_filename = scope.GetFirstGraph().LinePragma.FileName;

// А вот таким - строку, где нашлось срабатывание
int current_line = scope.GetFirstGraph().LinePragma.Line;

// Эти параметры можно использовать по разному
// Например получить все объекты в файле
CxList inFile = All.FindByFileName(current_filename);

// Или найти что происходит в конкретной строке
CxList inLine = inFile.FindByPosition(current_line);

ومن الجدير أن نأخذ في الاعتبار ذلك FileName يحتوي فعليًا على المسار إلى الملف، نظرًا لأننا استخدمنا الطريقة GetFirstGraph.

نتيجة التنفيذ

يوجد متغير خاص داخل CxQL result، والتي تُرجع نتيجة تنفيذ القاعدة المكتوبة. تتم تهيئته على الفور ويمكنك كتابة نتائج وسيطة فيه وتغييرها وتحسينها أثناء عملك. ولكن، إذا لم يكن هناك إسناد لهذا المتغير أو الوظيفة داخل القاعدة return— ستكون نتيجة التنفيذ دائمًا صفرًا.

الاستعلام التالي لن يعيد إلينا أي شيء نتيجة للتنفيذ وسيكون فارغًا دائمًا:

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

لكن بعد تخصيص نتيجة التنفيذ للمتغير السحري، سنرى ما سيعود به هذا الاستدعاء إلينا:

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

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

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

استخدام نتائج القواعد الأخرى

يمكن تسمية القواعد في Checkmarx بأنها مشابهة للوظائف الموجودة في لغة برمجة عادية. عند كتابة قاعدة، يمكنك استخدام نتائج الاستعلامات الأخرى. على سبيل المثال، ليست هناك حاجة للبحث عن كافة استدعاءات الأساليب في الكود في كل مرة، فقط اتصل بالقاعدة المطلوبة:

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

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

يتيح لك هذا الأسلوب تقصير التعليمات البرمجية وتقليل وقت تنفيذ القاعدة بشكل كبير.

حل المشكلات

تسجيل

عند العمل باستخدام الأداة، في بعض الأحيان لا يكون من الممكن كتابة الاستعلام المطلوب على الفور ويجب عليك التجربة وتجربة خيارات مختلفة. في مثل هذه الحالة، توفر الأداة التسجيل، وهو ما يسمى على النحو التالي:

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

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

ولكن تجدر الإشارة إلى أن هذه الطريقة تقبل فقط كمدخلات سلسلةلذلك لن يكون من الممكن عرض قائمة كاملة بالعناصر التي تم العثور عليها نتيجة للعملية الأولى. الخيار الثاني، والذي يستخدم لتصحيح الأخطاء، هو تعيين متغير سحري من وقت لآخر result نتيجة الاستعلام ونرى ما سيحدث. هذا الأسلوب ليس مناسبًا جدًا، عليك التأكد من عدم وجود تجاوزات أو عمليات بهذا في الكود بعد ذلك result أو ببساطة قم بالتعليق على الكود أدناه. أو يمكنك، مثلي، أن تنسى إزالة العديد من هذه الاستدعاءات من القاعدة الجاهزة وتتساءل لماذا لا يعمل شيء.

الطريقة الأكثر ملاءمة هي استدعاء الطريقة return مع المعلمة المطلوبة وفي هذه الحالة سينتهي تنفيذ القاعدة وسنتمكن من رؤية ما حدث نتيجة لما كتبناه:

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

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

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

مشكلة في تسجيل الدخول

هناك مواقف لا يمكنك فيها الوصول إلى أداة CxAudit (التي تُستخدم لكتابة القواعد). يمكن أن يكون هناك العديد من الأسباب لذلك، بما في ذلك الأعطال وتحديثات Windows المفاجئة وشاشة الموت الزرقاء وغيرها من المواقف غير المتوقعة الخارجة عن سيطرتنا. في هذه الحالة، أحيانًا تكون هناك جلسة غير مكتملة في قاعدة البيانات، مما يمنعك من تسجيل الدخول مرة أخرى. لإصلاح ذلك، تحتاج إلى تشغيل عدة استعلامات:

بالنسبة إلى 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 أحيانًا العديد من تدفقات البيانات التي قد تتداخل وتكون نسخة مختصرة من تدفقات أخرى. هناك طريقة خاصة لمثل هذه الحالات تقليل التدفق. اعتمادًا على المعلمة، سيتم تحديد التدفق الأقصر أو الأطول:

// Оставить только длинные 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);

المشكلة: قم بتوسيع قائمة المتغيرات بكلمات المرور

الحل: أوصي بالانتباه على الفور إلى القاعدة الأساسية لتحديد كلمات المرور في التعليمات البرمجية وإضافة قائمة بأسماء المتغيرات الشائعة الاستخدام في شركتك.

كلمة المرور_الخصوصية_انتهاك_قائمة

CxList allStrings = All.FindByType("String"); 
allStrings.Add(All.FindByType(typeof(StringLiteral))); 
allStrings.Add(Find_UnknownReference());
allStrings.Add(All.FindByType(typeof (Declarator)));
allStrings.Add(All.FindByType(typeof (MemberAccess)));
allStrings.Add(All.FindByType(typeof(EnumMemberDecl))); 
allStrings.Add(Find_Methods().FindByShortName("get*"));

// Дополняем дефолтный список переменных
List < string > pswdIncludeList = new List<string>{"*password*", "*psw", "psw*", "pwd*", "*pwd", "*authKey*", "pass*", "cipher*", "*cipher", "pass", "adgangskode", "benutzerkennwort", "chiffre", "clave", "codewort", "contrasena", "contrasenya", "geheimcode", "geslo", "heslo", "jelszo", "kennwort", "losenord", "losung", "losungswort", "lozinka", "modpas", "motdepasse", "parol", "parola", "parole", "pasahitza", "pasfhocal", "passe", "passord", "passwort", "pasvorto", "paswoord", "salasana", "schluessel", "schluesselwort", "senha", "sifre", "wachtwoord", "wagwoord", "watchword", "zugangswort", "PAROLACHIAVE", "PAROLA CHIAVE", "PAROLECHIAVI", "PAROLE CHIAVI", "paroladordine", "verschluesselt", "sisma",
                "pincode",
								"pin"};
								
List < string > pswdExcludeList = new List<string>{"*pass", "*passable*", "*passage*", "*passenger*", "*passer*", "*passing*", "*passion*", "*passive*", "*passover*", "*passport*", "*passed*", "*compass*", "*bypass*", "pass-through", "passthru", "passthrough", "passbytes", "passcount", "passratio"};

CxList tempResult = allStrings.FindByShortNames(pswdIncludeList, false);
CxList toRemove = tempResult.FindByShortNames(pswdExcludeList, false);
tempResult -= toRemove;
tempResult.Add(allStrings.FindByShortName("pass", false));

foreach (CxList r in tempResult)
{
	CSharpGraph g = r.data.GetByIndex(0) as CSharpGraph;
	if(g != null && g.ShortName != null && g.ShortName.Length < 50)
	{
		result.Add(r);
	}
}

المشكلة: أضف الأطر المستخدمة التي لا يدعمها Checkmarx

الحل: جميع الاستعلامات في Checkmarx مقسمة حسب اللغة، لذلك تحتاج إلى إضافة قواعد لكل لغة. وفيما يلي بعض الأمثلة على هذه القواعد.

إذا تم استخدام المكتبات التي تكمل أو تحل محل الوظائف القياسية، فيمكن إضافتها بسهولة إلى القاعدة الأساسية. ثم سيتعرف كل من يستخدمه على الفور على المقدمات الجديدة. على سبيل المثال، مكتبات تسجيل الدخول إلى Android هي Timber وLoggi. في الحزمة الأساسية، لا توجد قواعد لتحديد المكالمات غير النظامية، لذلك إذا دخلت كلمة المرور أو معرف الجلسة في السجل، فلن نعرف ذلك. دعونا نحاول إضافة تعريفات لهذه الأساليب إلى قواعد Checkmarx.

مثال على رمز الاختبار الذي يستخدم مكتبة 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. لنكتب قاعدة تبحث عن البيانات التي نحتاجها وتخبرنا إذا تم ذكر كلمات المرور أو الرموز المميزة في مكان ما.

مثال على هذا الملف الذي يحتوي على رمز مميز للاتصال بالخدمة الخلفية:

<?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 - يعني أنه سيتم إجراء البحث مع تجاهل حالة الأحرف، أي أنه غير حساس لحالة الأحرف

على سبيل المثال، استخدمنا قاعدة تحدد إعدادات اتصال الشبكة غير الصحيحة، من وجهة نظر أمنية، في Android والتي تسمح بالاتصال بالخادم عبر بروتوكول 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>

المشكلة: تحديد النتائج حسب اسم الملف/المسار

الحل: في أحد المشاريع الكبيرة المتعلقة بتطوير تطبيق جوال لنظام Android، واجهنا إيجابيات خاطئة للقاعدة التي تحدد إعداد التشويش. والحقيقة هي أن القاعدة خارج الصندوق تبحث في الملف build.gradle إعداد مسؤول عن تطبيق قواعد التشويش لإصدار إصدار التطبيق.

ولكن في المشاريع الكبيرة توجد أحيانًا ملفات فرعية build.gradle، والتي تشير إلى المكتبات المدرجة في المشروع. تكمن الخصوصية في أنه حتى لو لم تشير هذه الملفات إلى الحاجة إلى التشويش، فسيتم تطبيق إعدادات ملف التجميع الأصلي أثناء التجميع.

وبالتالي، فإن المهمة هي قطع المشغلات في الملفات الفرعية التي تنتمي إلى المكتبات. ويمكن التعرف عليهم من خلال وجود الخط apply 'com.android.library'.

رمز المثال من الملف build.gradle، والذي يحدد الحاجة إلى التعتيم:

apply plugin: 'com.android.application'

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

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

dependencies {
  ...
}

مثال على ملف build.gradle للمكتبة المضمنة في المشروع والتي لا تحتوي على هذا الإعداد:

apply plugin: 'android-library'

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

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

وقاعدة Checkmarx:

ProGuardObfuscationNotInUse

// Поиск метода release среди всех методов в Gradle файлах
CxList releaseMethod = Find_Gradle_Method("release");

// Все объекты из файлов build.gradle
CxList gradleBuildObjects = Find_Gradle_Build_Objects();

// Поиск того, что находится внутри метода "release" среди всех объектов из файлов build.gradle
CxList methodInvokesUnderRelease = gradleBuildObjects.FindByType(typeof(MethodInvokeExpr)).GetByAncs(releaseMethod);

// Ищем внутри gradle-файлов строку "com.android.library" - это значит, что данный файл относится к библиотеке и его необходимо исключить из правила
CxList android_library = gradleBuildObjects.FindByName("com.android.library");

// Инициализация пустого массива
List<string> libraries_path = new List<string> {};

// Проходим через все найденные "дочерние" файлы
foreach(CxList library in android_library)
{
    // Получаем путь к каждому файлу
	string file_name_library = library.GetFirstGraph().LinePragma.FileName;
    
    // Добавляем его в наш массив
	libraries_path.Add(file_name_library);
}

// Ищем все вызовы включения обфускации в релизных настройках
CxList minifyEnabled = methodInvokesUnderRelease.FindByShortName("minifyEnabled");

// Получаем параметры этих вызовов
CxList minifyValue = gradleBuildObjects.GetParameters(minifyEnabled, 0);

// Ищем среди них включенные
CxList minifyValueTrue = minifyValue.FindByShortName("true");

// Немного магии, если не нашли стандартным способом :D
if (minifyValueTrue.Count == 0) {
	minifyValue = minifyValue.FindByAbstractValue(abstractValue => abstractValue is TrueAbstractValue);
} else {
    // А если всё-таки нашли, то предыдущий результат и оставляем
	minifyValue = minifyValueTrue;	
}

// Если не нашлось таких методов
if (minifyValue.Count == 0)
{
    // Для более корректного отображения места срабатывания в файле ищем или buildTypes или android
	CxList tempResult = All.NewCxList();
	CxList buildTypes = Find_Gradle_Method("buildTypes");
	if (buildTypes.Count > 0) {
		tempResult = buildTypes;
	} else {
		tempResult = Find_Gradle_Method("android");
	}
	
	// Для каждого из найденных мест срабатывания проходим и определяем, дочерний или основной файлы сборки
	foreach(CxList res in tempResult)
	{
        // Определяем, в каком файле был найден buildType или android методы
		string file_name_result = res.GetFirstGraph().LinePragma.FileName;
        
        // Если такого файла нет в нашем списке "дочерних" файлов - значит это основной файл и его можно добавить в результат
		if (libraries_path.Contains(file_name_result) == false){
			result.Add(res);
		}
	}
}

يمكن أن يكون هذا الأسلوب عالميًا ومفيدًا تمامًا ليس فقط لتطبيقات Android، ولكن أيضًا للحالات الأخرى عندما تحتاج إلى تحديد ما إذا كانت النتيجة تنتمي إلى ملف معين.

المشكلة: أضف دعمًا لمكتبة جهة خارجية إذا لم يكن بناء الجملة مدعومًا بشكل كامل

الحل: عدد الأطر المختلفة المستخدمة في عملية كتابة التعليمات البرمجية هو ببساطة خارج المخططات. بالطبع، لا يعرف Checkmarx دائمًا وجودها، ومهمتنا هي تعليمه أن يفهم أن بعض الأساليب تنتمي على وجه التحديد إلى هذا الإطار. في بعض الأحيان يكون الأمر معقدًا بسبب حقيقة أن الأطر تستخدم أسماء وظائف شائعة جدًا ومن المستحيل تحديد علاقة استدعاء معين بمكتبة معينة بشكل لا لبس فيه.

تكمن الصعوبة في أن بناء جملة هذه المكتبات لا يتم التعرف عليه دائمًا بشكل صحيح ويجب عليك التجربة لتجنب الحصول على عدد كبير من النتائج الإيجابية الخاطئة. هناك عدة خيارات لتحسين دقة المسح وحل المشكلة:

  • الخيار الأول، نحن نعلم يقينًا أن المكتبة تُستخدم في مشروع معين ويمكن تطبيق القاعدة على مستوى الفريق. ولكن إذا قرر الفريق اتباع نهج مختلف أو استخدام العديد من المكتبات التي تتداخل فيها أسماء الوظائف، فيمكننا الحصول على صورة غير سارة للغاية للعديد من الإيجابيات الخاطئة

  • الخيار الثاني هو البحث عن الملفات التي يتم استيراد المكتبة فيها بشكل واضح. باستخدام هذا الأسلوب، يمكننا التأكد من أن المكتبة التي نحتاجها مستخدمة تمامًا في هذا الملف.

  • والخيار الثالث هو استخدام الطريقتين المذكورتين أعلاه معًا.

على سبيل المثال، دعونا ننظر إلى مكتبة معروفة في دوائر ضيقة أملس للغة برمجة Scala، وهي الوظيفة الربط بين القيم الحرفية. بشكل عام، لتمرير المعلمات إلى استعلام SQL، يجب عليك استخدام عامل التشغيل $، الذي يستبدل البيانات في استعلام SQL مُشكل مسبقًا. وهذا هو، في الواقع، هو التناظرية المباشرة للبيان المعد في Java. ولكن، إذا كنت بحاجة إلى إنشاء استعلام 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 في تحديد عدم استخدام الوظائف التي تؤدي إلى استغلال الثغرة الأمنية في التعليمات البرمجية.

لكن في بعض الأحيان، خاصة عند التفكير في JavaScript، قد لا تكون هذه مهمة تافهة تمامًا. يوجد أدناه حل، ربما لا يكون مثاليًا، لكنه مع ذلك يعمل، باستخدام مثال الثغرات الأمنية في المكون lodash في الأساليب template и *set.

أمثلة على اختبار التعليمات البرمجية التي يحتمل أن تكون عرضة للخطر في ملف JS:

/**
 * Template example
 */

'use strict';
var _ = require("./node_modules/lodash.js");


// Use the "interpolate" delimiter to create a compiled template.
var compiled = _.template('hello <%= js %>!');
console.log(compiled({ 'js': 'lodash' }));
// => 'hello lodash!'

// Use the internal `print` function in "evaluate" delimiters.

var compiled = _.template('<% print("hello " + js); %>!');
console.log(compiled({ 'js': 'lodash' }));
// => 'hello lodash!'

وعند الاتصال مباشرة بلغة html:

<!DOCTYPE html>
<html>
<head>
    <title>Lodash Tutorial</title>
    <script src="./node_modules/lodash.js"></script>
    <script type="text/javascript">
  // Lodash chunking array
        nums = [1, 2, 3, 4, 5, 6, 7, 8, 9];

        let c1 = _.template('<% print("hello " + js); %>!');
        console.log(c1);

        let c2 = _.template('<% print("hello " + js); %>!');
        console.log(c2);
    </script>
</head>
<body></body>
</html>

نحن نبحث عن جميع الأساليب الضعيفة لدينا، والمدرجة في نقاط الضعف:

// Ищем все строки: в которых встречается строка lodash (предполагаем, что это объявление импорта библиотеки
CxList lodash_strings = Find_String_Literal().FindByShortName("*lodash*");

// Ищем все данные: которые взаимодействуют с этими строками
CxList data_on_lodash = All.InfluencedBy(lodash_strings);


// Задаем список уязвимых методов
List<string> vulnerable_methods = new List<string> {"template", "*set"};

// Ищем все наши уязвимые методы, которые перечисленны в уязвимостях и отфильтровываем их только там, где они вызывались
CxList vulnerableMethods = All.FindByShortNames(vulnerable_methods).FindByType(typeof(MethodInvokeExpr));

//Находим все данные: которые взаимодействуют с данными методами
CxList vulnFlow = All.InfluencedBy(vulnerableMethods);

// Если есть пересечение по этим данным - кладем в результат
result = vulnFlow * data_on_lodash;

// Формируем список путей по которым мы уже прошли, чтобы фильтровать в дальнейшем дубли
List<string> lodash_result_path = new List<string> {};

foreach(CxList lodash_result in result)
{
    // Очередной раз получаем пути к файлам
	string file_name = lodash_result.GetFirstGraph().LinePragma.FileName;
	lodash_result_path.Add(file_name);
}

// Дальше идет часть относящаяся к html файлам, так как в них мы не можем проследить откуда именно идет вызов
// Формируем массив путей файлов, чтобы быть уверенными, что срабатывания уязвимых методов были именно в тех файлах, в которых объявлен lodash
List<string> lodash_path = new List<string> {};
foreach(CxList string_lodash in lodash_strings)
{
	string file_name = string_lodash.GetFirstGraph().LinePragma.FileName;
	lodash_path.Add(file_name);
}

// Перебираем все уязвимые методы и убеждаемся, что они вызваны в тех же файлах, что и объявление/включение lodash
foreach(CxList method in vulnerableMethods)
{
	string file_name_method = method.GetFirstGraph().LinePragma.FileName;
	if (lodash_path.Contains(file_name_method) == true && lodash_result_path.Contains(file_name_method) == false){
		result.Add(method);
	}
}

// Убираем все UknownReferences и оставляем самый "длинный" из путей, если такие встречаются
result = result.ReduceFlow(CxList.ReduceFlowType.ReduceSmallFlow) - result.FindByType(typeof(UnknownReference));

المشكلة: البحث عن الشهادات المضمنة في التطبيق

الحل: ليس من غير المألوف أن تستخدم التطبيقات، وخاصة تلك المحمولة، شهادات أو مفاتيح للوصول إلى خوادم مختلفة أو التحقق من تثبيت SSL. من منظور أمني، فإن تخزين مثل هذه الأشياء في التعليمات البرمجية ليس هو أفضل ممارسة. دعنا نحاول كتابة قاعدة للبحث عن ملفات مماثلة في المستودع:

// Найдем все сертификаты по маске файла
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. لهذا السبب أنشأنا مستودع على جيثب، حيث سننشر عملنا حتى يتمكن كل من يستخدم CxQL من العثور على شيء مفيد فيه، كما تتاح له الفرصة لمشاركة عمله مع المجتمع. المستودع في طور ملء المحتوى وتنظيمه، لذا نرحب بالمساهمين!

شكرا لك!

المصدر: www.habr.com

إضافة تعليق