تحسين عملية مراجعة الشفرة البرمجية

عبدالله العريني
24/03/2024


مقدمة

بسم الله الرحمن الرحيم وبه نستعين, في هذه المقالة سنستعرض أساسيات عملية مراجعة النص أو الشفرة البرمجية (Source Code Review) وكيفية بداية أي مشروع لمراجعة أي نص برمجي لأي تطبيق.

قبل التطرق لشرح هذه العملية يجب علينا ابتداءً أن نفهم ما المقصود بمراجعة النص البرمجي وهي العملية المعنية بفحص الشفرة من قِبل شخص أو أشخاص خارج فريق التطوير وذلك لضمان نزاهة عملية المراجعة.

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



معرفة الأساسيات

في الوقت الحالي, هنالك العديد من لغات البرمجة ويتفرع من هذا اللغات العديد من أطر العمل(frameworks) والمكتبات (libraries) وهي بالكثرة اللتي تجعل من المستحيل على شخص واحد أن يتقنها كلها بشكل كامل. لكن من الجيد معرفة أن العديد من لغات البرمجة وإطر العمل تتشارك في أغلب المفاهيم فبالتالي ينصب تركيز الشخص على معرفة المفاهيم البرمجية العامة المشتركة ومن ثم يكون جل تركيزه على لغة معينة بحيث يبحر في تعلمها. ضع في الحسبان أثناء تعلم لغة معينة أنه من الأفضل التركيز على احتياج السوق والتركيز على اللغات الشائعة حالياً مثل: C#, Java, PHP .

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

1-الثغرات العامة الموجودة في جميع التطبيقات كثغرات الحقن (injections) أو الثغرات المتعلقة بالتصميم غير الآمن.

2-الثغرات المرتبطة بالإعدادات المتعلقة بالخادم المستضيف للتطبيق كالاتصال عبر قنوات غير مشفرة.(Server Misconfigurations)

3-الثغرات المتعلقة بنوع معين من لغات البرمجة أو أطر العمل. على سبيل المثال ثغرات Buffer-over-flow الموجودة غالباً في لغات الـ C,C++ . يجدر التنويه هنا أنه في بعض الأحيان يجب التركيز على إصدار اللغة أو الإطار لأن بعض الإصدارات أو التحديثات اللتي تطرأ عليها قد تغير من طبيعة عمل البرنامج نفسه مما يؤدي إلى حدوث ثغرة جديدة قد لا يدركها المطور.



منهجية مراجعة الشفرة البرمجية

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

فهم البرنامج

قبل البداية في عملية مراجعة الشفرة يجب علينا قراءة الملفات المتعلقة بذلك التطبيق (Software Requirements Specification (SRS), Process Flow, and Usage Documents,) حيث تمكننا هذا الملفات من فهم البرنامج ومعرفة الغاية منه وكيفية عمله.

كخطوة إضافية, الإجتماع مع المطورين للبرنامج قد يساعد في فهم البرنامج أيضاً ومنها أيضاً قد يتم التطرق في الاجتماع إلى بعض الأسئلة لفهم ما هي الخطوات المستخدمة من قبلهم للحماية من الثغرات ومدى إدراكهم للجانب الأمني.

من النقاط المهمة اللتي يجب معرفتها أيضاً هو هيكلة الملفات للبرنامج وما هو الغرض من كل مجلد في البرنامج.

بعدما أخذنا نظرة عن فكرة البرنامج وما هي الوظائف الموجودة فيه وآلية عمله نستطيع الآن البدء بأخذ ملاحظات عن المخاطر المحتملة حيث تستطيع أن تعتبر هذه الخطوة عبارة عن نمذجة التهديدات (Threat Modeling). دعنا نوضح هذا الكلام بالمثال التالي: لنفرض أن لدينا تطبيق أو موقع فيه صفحة (تواصل معنا) كما هو موضح في الصورة التالية:
كما هو ملاحظ في الصورة السابقة حيث أنه هناك أكثر من مدخل من قِبل المستخدم فبالتالي أول ما قد يخطر في بالنا هو التحقق من ثغرات الحقن كالتالي: SQL injection , XSS. أيضاً في بعض الحالات قد يكون البرنامج يقبل مدخلات أكثر من المعروضة في الواجهة للمستخدم وتحدث نسياناً من المطور فقد نضعها بالحسبان هنا لنتحقق من عدم وجودها وتسمى هذه الثغرة بـ Mass assignment.



أيضاً, بما أنه يأخذ مدخلات أو بيانات من المستخدم فمعنى هذا أنه يتعامل مع قواعد بيانات معينة فيتبادر هنا إلينا مسألة الهجمات المتعلقة بملء قواعد البيانات ببياناتٍ وهمية وتسمى بـ database flooding attacks.

بناءً على كل ما سبق نستطيع الآن أن نستنتج بعض الأخطار المحتملة لهذه الصفحة أو الخاصية في التطبيق:

أ.هل البرنامج يقوم بالتحقق من مدخلات المستخدم قبل معالجتها أو تمريرها لدوال المعالجة؟

ب.هل البرنامج يقوم بعرض مدخلات المستخدم في مكان آخر من البرنامج؟ وهل يقوم بمعالجتها بشكل صحيح قبل عرضها (output encoding) ؟

ت.هل البرنامج يطبق نوع من أنواع الحماية على قواعد البيانات لمنع ملئها بالبيانات الوهمية ؟

ث.إلخ.

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





الفحص الآلي (Automated Scan)

الخطوة الثانية هي القيام بعمل فحص آلي للشفرة عن طريق الآدوات المخصصة لهذا. يمكن تفصيل هذا المرحلة على النحو التالي:



القيام بالفحص الآلي للشفرة

باستخدام الأدوات وخصوصاً التجارية كـ: SonarQube, Fortify SCA, Bandit, GitHub, Checkmarx وغيرها. هذه العملية تسمى ب Static Application Security Testing (SAST). يجدر التنويه على أن بعض من الأدوات المذكورة آنفاً وغيرها قد تتطلب أن يكون البرنامج قابل للتجميع (compile) لأن بعضها يقوم بتحويل البرنامج إلى لغة وسيطة يسهل التعامل معها فمثلاً الأداة Fortify SCA تقوم بتحويل البرنامج إلى صيغة أخرى تسمى Fortify Project Results (FPR) قبل بدء عملية التحليل.
غالباً نتائج الفحص الآلي للشفرة يتنج فيها أخطاء أو ما تسمى (False-Positive) واللتي يجب علينا أن تحقق منها حيث أن بعضاً من هذه الأخطاء تتطلب جهداً ووقتاً أكبر للتحقق منها كثغرات المرتبطة بالحقن أو ضعف التشفير(Injection or Weak Cryptorgraphy) بينما بعضها على النقيض حيث يمكن التحقق منها بسرعة كثغرات وجود كلمات سر في الشفرة (Hard-coded credentials):
فحص مكونات او اعتماديات البرنامج Software Composition Analysis (SCA)

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

نظراً لأن هذه العملية تستهلك الوقت فهنالك بعض الأدوات التجارية وأيضاً المفتوحة المصدر اللتي تقوم بتأديتها عنك فعلى سبيل المثال:

1.Snyk Open Source

2.Checkmarx SCA

3.OWASP dependency-check

4.Safety or dependency-check-py for pythons

5.Bundler-audit for ruby



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



الفحص اليدوي للشفرة Manual Code Review

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

تختلف الطرق المستخدمة لتحليل الشفرة يدوياً اعتماداً على عدة عوامل أهمها تفضيل المراجع للشفرة نفسه.



Source and Sink

دعنا نفهم المقصود ب " source and sink" ابتداءً. Source عادةً يقصد فيها النقطة التي يتم فيه استقبال المدخلات بينما Sink هي المكان اللذي سيتم معالجة تلك البيانات فيه. لنفرض أنه لدينا صفحة تسجيل مستخدم جديد ومن ضمن الحقول فيها حقل الدولة " Country" وحقل المدينة "City ". عندما يقوم المستخدم باختيار دولة ما من القائمة, سيتم ارسال طلب للخادم للاستعلام عن جميع المدن في تلك الدولة. في هذا المثال اسم الحقل للدولة هو 'CountryID' :
في هذه الحالة نعتبر حقل الدولة "CountryID" هو الـ Source حيث سيتم ارسال طلب الاستعلام للخادم بالشكل التالي:

http://test.com/getCities?CountryID=8

وعلى مستوى الشفرة البرمجية قد نجد مشابه لما هو آت:

  http.HandleFunc("/getCities", func(w http.ResponseWriter, r *http.Request) {
		id := r.URL.Query().Get("CountryID") // This is the source. 
            val, err := getCities(id)  
  })
بينما "sink" في هذه الحالة هي الدالة التي ستقوم بالاستعلام مباشرة من قاعدة البيانات:

func getCities(id string) ([]city, error) {
    rows, err := db.Query("SELECT cities FROM country WHERE country_Id = ?", id)
      // Do whatever you need to complete the query and return the value … 
}

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

Source -> Sink: هذه الطريقة تساعد على اكتشاف أكبر عدد من الثغرات ذات الإحتمالية العالية والخطورة المنخفضة في الغالب وليس شرطاً.

Sink -> Source: عدد الثغرات المكتشفة أقل وتكون احتمالية حدوثها قليلة لكن خطورتها عالية. كما قلنا مسبقاً ليس شرطاً ولكن نتكلم بشكل عام.



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





أظهر جهدك – مرحلة التقرير

هذه تعتبر آخر مرحلة من عملية مراجعة الشفرة حيث يجب كتابة تقرير مفصل يحوي جميع الثغرات التي تم اكتشافها حيث يطلع عليها المسؤول عن البرنامج أو التطبيق.

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

جزئية الشفرة التي فيها الثغرة.

مسار الملف المصاب بالثغرة كاملاً مع اسمه.

إذا أمكن تحديد رقم السطر سبب المشكلة.

شرح تفصيلي واضح للثغرة.

خطوات تفصيلية لحل الثغرة.

تقييم خطورة الثغرة

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

KeePass Code Review Results Report



ختاماً

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

نسأل الله أن يكتب فيها النفع والخير للناس.



المراجع

OWASP Code Review Guide

Advanced Web Attacks and Exploitation

Certified Application Security Engineer (CASE)

Micro Focus Fortify Static Code Analyzer User Guide





لمشاركة هذه المدونة
تابعنا
طور مهاراتك من خلال متابعة آخر منشورات فريقنا
مدونات آخرى