مايكل هوارد وكيث براون
تفترض هذه المقالة أنك على دراية بـ C++، وC#، وSQL
ملخص: عندما يتعلق الأمر بالقضايا الأمنية، هناك العديد من المواقف التي يمكن أن تؤدي إلى مشاكل. من المحتمل أنك تثق في كل التعليمات البرمجية التي تعمل على شبكتك، وتمنح جميع المستخدمين إمكانية الوصول إلى الملفات المهمة، ولا تكلف نفسك عناء التحقق لمعرفة ما إذا كانت التعليمات البرمجية الموجودة على أجهزتك قد تغيرت. وقد لا يكون لديك أيضًا برنامج مكافحة فيروسات مثبتًا، أو تفشل في تأمين التعليمات البرمجية الخاصة بك، وتمنح عددًا كبيرًا جدًا من الحسابات أذونات كثيرة جدًا. بل قد تكون مهملاً في استخدام عدد من الوظائف المضمنة التي تسمح بالتطفلات الضارة، وقد تترك منافذ الخادم مفتوحة دون أي مراقبة. من الواضح أنه يمكننا تقديم العديد من الأمثلة. ما هي القضايا المهمة حقًا (أي الأخطاء الأكثر خطورة التي يجب أن تحظى باهتمام فوري لتجنب تعريض بياناتك وأنظمتك للخطر)؟ يقدم خبراء الأمن مايكل هوارد وكيث براون عشر نصائح لمساعدتك.
-------------------------------------------------- ----------------------------------
القضايا الأمنية تنطوي على جوانب عديدة. يمكن أن تأتي المخاطر الأمنية من أي مكان. ربما تكون قد كتبت تعليمات برمجية غير فعالة لمعالجة الأخطاء أو كنت كريمًا جدًا عند منح الأذونات. ربما تكون قد نسيت الخدمات التي تعمل على الخادم الخاص بك. يمكنك قبول جميع مدخلات المستخدم. وهكذا. لإعطائك السبق في حماية الكمبيوتر والشبكة والتعليمات البرمجية، إليك عشر نصائح يمكنك اتباعها للحصول على إستراتيجية شبكة أكثر أمانًا.
1. الثقة في مدخلات المستخدم تعرضك للخطر
حتى لو لم تقرأ الباقي، تذكر هذا: "لا تثق في مدخلات المستخدم". تنشأ المشكلة إذا كنت تفترض دائمًا أن البيانات صحيحة وليست ضارة. تتضمن معظم الثغرات الأمنية مهاجمين يرسلون بيانات مكتوبة بشكل ضار إلى الخوادم.
يمكن أن تؤدي الثقة في صحة الإدخال إلى تجاوز سعة المخزن المؤقت، وهجمات البرمجة النصية عبر المواقع، وهجمات تعليمات برمجية لإدراج SQL، والمزيد.
دعونا نناقش نواقل الهجوم المحتملة هذه بالتفصيل.
2. منع تجاوز سعة المخزن المؤقت
عندما يوفر المهاجم طول بيانات أكبر مما يتوقعه التطبيق، يحدث تجاوز سعة المخزن المؤقت، وتتدفق البيانات إلى مساحة الذاكرة الداخلية. يعد تجاوز سعة المخزن المؤقت مشكلة في C/C++ بشكل أساسي. إنها تشكل تهديدًا، ولكن من السهل عادةً إصلاحها. لقد رأينا فقط اثنين من حالات تجاوز سعة المخزن المؤقت والتي لم تكن واضحة ويصعب إصلاحها. لم يتوقع المطور أن تكون البيانات المقدمة خارجيًا أكبر من المخزن المؤقت الداخلي. ويتسبب التجاوز في إتلاف هياكل البيانات الأخرى في الذاكرة، والتي غالبًا ما يستغلها المهاجمون لتشغيل تعليمات برمجية ضارة. يمكن أن تؤدي أخطاء فهرس المصفوفة أيضًا إلى حدوث تجاوزات وتجاوزات في المخزن المؤقت، ولكن هذا أقل شيوعًا.
ألق نظرة على مقتطف كود C++ التالي:
باطلة DoSomething(char *cBuffSrc, DWORD cbBuffSrc) {
شار cBuffDest[32];
memcpy(cBuffDest,cBuffSrc,cbBuffSrc);
}
ما هي المشكلة؟ وفي الحقيقة لا حرج في هذا الكود إذا كان cBuffSrc وcbBuffSrc يأتيان من مصدر موثوق (مثل الكود الذي لا يثق في البيانات وبالتالي يتحقق من صحتها وحجمها). ومع ذلك، إذا كانت البيانات تأتي من مصدر غير موثوق به ولم يتم التحقق منها، فيمكن للمهاجم (مصدر غير موثوق به) بسهولة جعل cBuffSrc أكبر من cBuffDest وأيضًا تعيين cbBuffSrc ليكون أكبر من cBuffDest. عندما يقوم memcpy بنسخ البيانات إلى cBuffDest، يتغير عنوان الإرجاع من DoSomething، ولأن cBuffDest مجاور لعنوان الإرجاع في إطار مكدس الوظيفة، يمكن للمهاجم تنفيذ بعض العمليات الضارة من خلال التعليمات البرمجية.
طريقة التعويض هي عدم الثقة في مدخلات المستخدم، وعدم الثقة في أي بيانات محمولة في cBuffSrc وcbBuffSrc:
void DoSomething(char *cBuffSrc, DWORD cbBuffSrc) {
const DWORD cbBuffDest = 32;
char cBuffDest[cbBuffDest];
#ifdef_DEBUG
memset(cBuffDest, 0x33, cbBuffSrc);
#endif
memcpy(cBuffDest, cBuffSrc, min(cbBuffDest, cbBuffSrc));
}
توضح هذه الدالة ثلاث خصائص للدالة المكتوبة بشكل صحيح والتي يمكنها تقليل تجاوز سعة المخزن المؤقت. أولاً، يتطلب الأمر من المتصل توفير طول المخزن المؤقت. بالطبع، لا يمكنك أن تثق بهذه القيمة بشكل أعمى! بعد ذلك، في إنشاء تصحيح الأخطاء، يكتشف الكود ما إذا كان المخزن المؤقت كبيرًا بالفعل بدرجة كافية لاستيعاب المخزن المؤقت المصدر. إذا لم يكن الأمر كذلك، فقد يتم تشغيل انتهاك وصول وقد يتم تحميل التعليمات البرمجية في مصحح الأخطاء. عند تصحيح الأخطاء، ستفاجأ بعدد الأخطاء التي تجدها. أخيرًا، والأهم من ذلك، أن الاستدعاءات إلى memcpy هي دفاعية من حيث أنها لا تنسخ بيانات أكثر مما يمكن أن يحتفظ به المخزن المؤقت المستهدف.
كجزء من Windows® Security Push في Microsoft، قمنا بإنشاء قائمة بوظائف معالجة السلسلة الآمنة لمبرمجي C. يمكنك العثور عليها في Strsafe.h: معالجة أكثر أمانًا للسلاسل في لغة C (الإنجليزية).
3. منع البرمجة النصية عبر المواقع
تعد هجمات البرمجة النصية عبر المواقع مشكلة فريدة للويب، حيث يمكن أن تلحق الضرر ببيانات العميل من خلال ثغرة أمنية مخفية في صفحة ويب واحدة. تخيل عواقب مقتطف كود ASP.NET التالي:
<script language=c#>
Response.Write("مرحبًا،" + Request.QueryString("name"));
</script>
كم عدد الأشخاص الذين شاهدوا رمزًا مشابهًا؟ ولكن من المدهش أن لديها مشاكل! عادةً، سيصل المستخدمون إلى هذا الرمز باستخدام عنوان URL مشابه لما يلي:
http://explorationair.com/welcome.aspx?name=Michael
يفترض رمز C# أن البيانات صالحة دائمًا وتحتوي على اسم فقط. ومع ذلك، يمكن للمهاجم إساءة استخدام هذا الرمز من خلال توفير البرنامج النصي وتعليمات HTML كأسماء. إذا قمت بإدخال عنوان URL التالي
http://northwindtraders.com/welcome.aspx?name=<script>alert(' مرحبًا!');
</script>
سوف تحصل على صفحة ويب تحتوي على مربع حوار يقول "مرحبًا!" قد تقول: "وماذا في ذلك؟" تخيل أن أحد المهاجمين يمكنه خداع المستخدم للنقر على رابط مثل هذا، ولكن سلسلة الاستعلام تحتوي على بعض النصوص البرمجية وHTML الخطيرة جدًا، وبالتالي الحصول على ملف تعريف الارتباط الخاص بالمستخدم وإرساله إلى موقع ويب مملوك لـ المهاجم الآن لديه حق الوصول إلى معلومات ملفات تعريف الارتباط الخاصة بك، أو ما هو أسوأ من ذلك.
لتجنب ذلك، هناك طريقتان. الأول هو عدم الثقة في المدخلات وتقييد ما يحتويه اسم المستخدم بشكل صارم. على سبيل المثال، يمكنك استخدام التعبيرات العادية للتأكد من أن الاسم يحتوي فقط على مجموعة فرعية مشتركة من الأحرف وليس كبيرًا جدًا. يوضح مقتطف التعليمات البرمجية التالي في C# كيفية تنفيذ هذه الخطوة:
Regex r = new Regex(@"^[w]{1,40}$");
if (r.Match(strName).Success) {
// جيد! السلسلة على ما يرام
} آخر {
// ليس جيدا! سلسلة غير صالحة
}
يستخدم هذا الرمز تعبيرات عادية للتحقق من أن السلسلة تحتوي على 1 إلى 40 حرفًا أو رقمًا فقط. هذه هي الطريقة الآمنة الوحيدة لتحديد ما إذا كانت القيمة صحيحة أم لا.
لا توجد طريقة يمكن لـ HTML أو البرنامج النصي أن يخدع هذا التعبير العادي! لا تستخدم التعبيرات العادية للبحث عن أحرف غير صالحة ورفض الطلبات إذا تم العثور على أحرف غير صالحة، لأنه من السهل تفويت شيء ما.
الاحتياط الثاني هو ترميز HTML لجميع المدخلات كمخرجات. يؤدي هذا إلى تقليل علامات HTML الخطيرة إلى أحرف هروب أكثر أمانًا. يمكنك استخدام HttpServerUtility.HtmlEncode في ASP.NET، أو Server.HTMLEncode في ASP للهروب من أي سلاسل قد تسبب مشاكل.
4. لا تطلب أذونات SA
آخر هجوم ثقة الإدخال الذي سنناقشه هو إدخال كود SQL. يقوم العديد من المطورين بكتابة التعليمات البرمجية التي تأخذ المدخلات وتستخدم هذا الإدخال لإنشاء استعلامات SQL التي تتواصل مع مخزن بيانات الواجهة الخلفية مثل Microsoft® SQL Server™ أو Oracle.
ألقِ نظرة على مقتطف الكود التالي:
void DoQuery(string Id) {
SqlConnection sql=new SqlConnection(@"مصدر البيانات=localhost;" +
"معرف المستخدم=sa;كلمة المرور=كلمة المرور;");
sql.Open();
sqlstring= "تم شحن التحديد" +
"من الشحن حيث id='" + Id + "'";
SqlCommand cmd = new SqlCommand(sqlstring,sql);
•••
يحتوي هذا الرمز على ثلاثة عيوب خطيرة. أولاً، يقوم بإنشاء اتصال من خدمة الويب إلى SQL Server كحساب مسؤول النظام sa. وسترى قريبا عيوب هذا. النقطة الثانية، انتبه إلى الممارسة الذكية المتمثلة في استخدام "كلمة المرور" ككلمة مرور لحساب sa!
لكن القلق الحقيقي هو تسلسل السلسلة الذي يبني عبارات SQL. إذا قام المستخدم بإدخال 1001 للمعرف، فستحصل على عبارة SQL التالية، وهي صالحة تمامًا.
تم الشحن من الشحن حيث المعرف = '1001'
لكن المهاجمين أكثر إبداعًا من ذلك بكثير. سوف يقومون بإدخال "'1001' DROP table Shipping --" للمعرف، والذي من شأنه تنفيذ استعلام مثل هذا:
تم الشحن من SELECT
معرف الشحن = "1001"
إسقاط شحن الجدول - ';
إنه يغير طريقة عمل الاستعلام. لا يحاول هذا الرمز تحديد ما إذا كان قد تم شحن شيء ما فحسب، بل يتابع أيضًا إسقاط (حذف) جدول الشحن! المشغل - هو عامل التعليق في SQL، مما يسهل على المهاجمين إنشاء سلسلة من عبارات SQL الصالحة ولكن الخطيرة!
في هذا الوقت، قد تتساءل كيف يمكن لأي مستخدم حذف الجدول في قاعدة بيانات SQL Server. بالطبع أنت على حق، فقط المسؤولون هم من يمكنهم القيام بمثل هذه المهمة. ولكن هنا أنت تتصل بقاعدة البيانات كـ sa، ويمكن لـ sa أن يفعل ما يريد في قاعدة بيانات SQL Server. لا تتصل مطلقًا بـ SQL Server كـ sa من أي تطبيق؛ فالأسلوب الصحيح هو استخدام مصادقة Windows المتكاملة، إذا كان ذلك مناسبًا، أو الاتصال كحساب محدد مسبقًا بالأذونات المناسبة.
يعد إصلاح مشكلات كود إدراج SQL أمرًا سهلاً. باستخدام إجراءات ومعلمات SQL المخزنة، يوضح التعليمة البرمجية التالية كيفية إنشاء مثل هذا الاستعلام - وكيفية استخدام التعبيرات العادية للتأكد من صحة الإدخال، حيث تحدد معاملتنا أن معرف الشحنة يمكن أن يتكون من 4 إلى 10 أرقام فقط:
Regex r = new Regex(@"^d{4,10}$");
إذا (!r.Match(Id).Success)
رمي استثناء جديد ("معرف غير صالح")؛
SqlConnection sqlConn= new SqlConnection(strConn);
سلسلة str = "sp_HasShipped"؛
SqlCommand cmd = new SqlCommand(str,sqlConn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("@ID",Id);
تعد تجاوزات المخزن المؤقت والبرمجة النصية عبر المواقع وهجمات تعليمات إدخال SQL كلها أمثلة على مشكلات الإدخال الموثوقة. يتم تخفيف كل هذه الهجمات من خلال آلية تعتبر جميع المدخلات ضارة ما لم يثبت خلاف ذلك.
5. انتبه إلى رمز التشفير!
دعونا نلقي نظرة على شيء قد يفاجئنا. لقد وجدت أن أكثر من ثلاثين بالمائة من رمز الأمان الذي قمنا بفحصه به ثغرات أمنية. ربما تكون الثغرة الأمنية الأكثر شيوعًا هي رمز التشفير الخاص بك، والذي من المحتمل أن يكون عرضة للخطر. لا تقم أبدًا بإنشاء رمز التشفير الخاص بك، فهي مهمة حمقاء. لا تعتقد أنه لمجرد أن لديك خوارزمية التشفير الخاصة بك، فلن يتمكن الآخرون من فكها. يتمتع المهاجمون بإمكانية الوصول إلى مصححات الأخطاء، كما أن لديهم الوقت والمعرفة لتحديد كيفية عمل الأنظمة - وغالبًا ما يعطلونها في غضون ساعات. يجب عليك استخدام Win32® CryptoAPI، حيث توفر مساحة الاسم System.Security.Cryptography عددًا من خوارزميات التشفير الممتازة والمختبرة.
6. تقليل احتمالية التعرض للهجوم
إذا لم يطلبها أكثر من 90% من المستخدمين، فلا ينبغي تثبيت الميزة بشكل افتراضي. يتبع الإصدار 6.0 من خدمات معلومات الإنترنت (IIS) توصية التثبيت هذه، والتي يمكنك أن تقرأ عنها في مقال واين بيري "الابتكارات في خدمات معلومات الإنترنت تتيح لك حماية البيانات الآمنة وعمليات الخادم بشكل محكم"، والذي صدر هذا الشهر. الفكرة وراء استراتيجية التثبيت هذه هي أنك لن تنتبه إلى الخدمات التي لا تستخدمها، وإذا كانت هذه الخدمات قيد التشغيل، فمن الممكن أن يستغلها الآخرون. إذا تم تثبيت الميزة بشكل افتراضي، فيجب تشغيلها وفقًا لمبدأ الترخيص الأقل. وهذا يعني عدم السماح للتطبيقات بالعمل بامتيازات المسؤول إلا إذا كان ذلك ضروريًا. من الأفضل اتباع هذه النصيحة.
7. استخدم مبدأ الترخيص الأقل
لأنظمة التشغيل وأوقات تشغيل اللغة العامة سياسة أمنية لعدة أسباب. يفترض العديد من الأشخاص أن السبب الرئيسي وراء وجود سياسة الأمان هذه هو منع المستخدمين من التسبب في ضرر عمدًا: الوصول إلى الملفات غير المسموح لهم بالوصول إليها، وإعادة تكوين الشبكة لتناسب احتياجاتهم، وغير ذلك من السلوكيات الفظيعة. نعم، هذا النوع من الهجمات الداخلية شائع ويحتاج إلى الحماية منه، ولكن هناك سبب آخر للالتزام بهذه الإستراتيجية الأمنية. وهذا يعني بناء حواجز دفاعية حول التعليمات البرمجية لمنع المستخدمين من إحداث فوضى في الشبكة من خلال إجراءات مقصودة أو (كما يحدث غالبًا) غير مقصودة. على سبيل المثال، المرفق الذي يتم تنزيله عبر البريد الإلكتروني، عند تنفيذه على جهاز أليس، يقتصر على الموارد التي يمكن لأليس الوصول إليها. إذا كان المرفق يحتوي على حصان طروادة، فإن الإستراتيجية الأمنية الجيدة تتمثل في الحد من الضرر الذي يمكن أن يحدثه.
عند تصميم تطبيقات الخادم وإنشائها ونشرها، لا يمكنك افتراض أن جميع الطلبات تأتي من مستخدمين شرعيين. إذا أرسل إليك شخص سيء طلبًا ضارًا (نأمل ألا يكون كذلك) وكان سلوك الكود الخاص بك سيئًا، فأنت تريد أن يتمتع تطبيقك بكل دفاع ممكن للحد من الضرر. ولذلك، نعتقد أن شركتك تنفذ سياسات أمنية ليس فقط لأنها لا تثق بك أو بشفرتك البرمجية، ولكن أيضًا لحماية نفسها من التعليمات البرمجية الخارجية الضارة.
ينص مبدأ الترخيص الأقل على أن الحد الأدنى من الأذونات التي يتطلبها الكود يجب أن يتم منحه في أقل وقت ممكن. ومع ذلك، قم بإقامة أكبر عدد ممكن من الجدران الواقية حول الكود الخاص بك في جميع الأوقات. عندما يحدث شيء سيئ - كما يضمن قانون مورفي - فسوف تكون سعيدًا بوجود تلك الجدران الواقية في مكانها الصحيح. لذلك، إليك بعض الطرق المحددة لتشغيل التعليمات البرمجية باستخدام مبدأ الترخيص الأقل.
اختر بيئة آمنة لرمز الخادم الخاص بك والتي تسمح له فقط بالوصول إلى الموارد اللازمة للقيام بعمله. إذا كانت بعض أجزاء التعليمات البرمجية الخاصة بك تتطلب أذونات عالية، ففكر في عزل هذا الجزء من التعليمات البرمجية وتشغيله بشكل منفصل بأذونات أعلى. لفصل هذا الرمز الذي يتم تشغيله مع معلومات مصادقة نظام تشغيل مختلفة بأمان، فمن الأفضل تشغيل هذا الرمز في عملية منفصلة (تعمل في بيئة آمنة ذات امتيازات أعلى). وهذا يعني أنك ستحتاج إلى اتصال بين العمليات (مثل الاتصال عن بعد بـ COM أو Microsoft .NET)، كما ستحتاج إلى تصميم واجهة لهذا الرمز لتقليل رحلات الذهاب والإياب.
إذا قمت بفصل التعليمات البرمجية إلى تجميعات في بيئة .NET Framework، فضع في اعتبارك مستويات الأذونات المطلوبة لكل جزء من التعليمات البرمجية. ستجد أنها عملية سهلة: قم بفصل التعليمات البرمجية التي تتطلب امتيازات أعلى في تجميع منفصل يمنحها المزيد من الامتيازات، مع ترك معظم التجميعات المتبقية تعمل بامتيازات أقل حتى تتمكن من إضافة المزيد من الحراس حول التعليمات البرمجية الخاصة بك. عند القيام بذلك، لا تنس أنه بسبب مكدس Code Access Security (CAS)، فإنك تحد من الأذونات ليس فقط للتجميع الخاص بك، ولكن أيضًا لأي تجميع تستدعيه.
يبني العديد من الأشخاص تطبيقاتهم الخاصة بحيث يمكن توصيل المكونات الجديدة بمنتجاتهم بعد اختبارها وإتاحتها للعملاء. يعد تأمين هذا النوع من التطبيقات أمرًا صعبًا للغاية لأنه لا يمكنك اختبار كل مسار كود محتمل للعثور على الأخطاء والثغرات الأمنية. ومع ذلك، إذا كان التطبيق الخاص بك مستضافًا، فإن CLR يوفر ميزة ممتازة يمكن استخدامها لإغلاق نقاط القابلية للتوسعة هذه. من خلال الإعلان عن كائن إذن أو مجموعة أذونات واستدعاء PermitOnly أو Deny، فإنك تضيف علامة إلى مجموعتك التي ستمنع منح الأذونات لأي رمز تستدعيه. من خلال القيام بذلك قبل استدعاء مكون إضافي، يمكنك تحديد المهام التي يمكن أن يؤديها المكون الإضافي. على سبيل المثال، لا يتطلب البرنامج الإضافي لحساب دفعات الأقساط أي وصول إلى نظام الملفات. وهذا مجرد مثال آخر على أقل الامتيازات، حيث يمكنك حماية نفسك مسبقًا. تأكد من ملاحظة هذه القيود، وكن على دراية بأن المكونات الإضافية ذات الامتيازات الأعلى يمكنها استخدام عبارات التأكيد للتهرب من هذه القيود.
8. كن على دراية بأنماط الفشل
وتقبلها. يكره الآخرون كتابة تعليمات برمجية لمعالجة الأخطاء مثلك تمامًا. هناك العديد من الأسباب التي قد تؤدي إلى فشل التعليمات البرمجية، وقد يكون مجرد التفكير فيها أمرًا محبطًا. يفضل معظم المبرمجين، بما فيهم نحن، التركيز على مسار التنفيذ العادي. هذا هو المكان الذي يتم فيه إنجاز العمل حقًا. فلنقم بمعالجة الأخطاء هذه بأسرع ما يمكن وبدون ألم قدر الإمكان، ثم ننتقل إلى السطر التالي من التعليمات البرمجية الحقيقية.
لسوء الحظ، هذه المشاعر ليست آمنة. بدلاً من ذلك، نحن بحاجة إلى إيلاء اهتمام وثيق لأنماط الفشل في التعليمات البرمجية لدينا. غالبًا ما تتم كتابة هذا الرمز مع القليل من الاهتمام المتعمق، وغالبًا ما لا يتم اختباره بشكل كامل. هل تتذكر آخر مرة كنت فيها متأكدًا تمامًا من أنك قمت بتصحيح كل سطر من التعليمات البرمجية في إحدى الوظائف، بما في ذلك كل معالج أخطاء صغير فيها؟
غالبًا ما تؤدي التعليمات البرمجية غير المختبرة إلى ثغرات أمنية. هناك ثلاثة أشياء يمكن أن تساعدك على تخفيف هذه المشكلة. أولاً، امنح معالجات الأخطاء الصغيرة هذه نفس الاهتمام الذي تحظى به تعليماتك البرمجية العادية. ضع في اعتبارك حالة النظام عند تنفيذ تعليمات برمجية لمعالجة الأخطاء. هل النظام في حالة فعالة وآمنة؟ ثانيًا، بمجرد كتابة دالة، قم بالتنقل عبرها وتصحيح أخطائها جيدًا عدة مرات، مع التأكد من اختبار كل معالج للأخطاء. لاحظ أنه حتى مع مثل هذه التقنيات، قد لا يتم اكتشاف أخطاء توقيت دقيقة للغاية. قد تحتاج إلى تمرير معلمة خطأ إلى وظيفتك، أو ضبط حالة النظام بطريقة ما للسماح لمعالج الأخطاء الخاص بك بالتنفيذ. من خلال تخصيص الوقت الكافي للتنقل عبر التعليمات البرمجية الخاصة بك، يمكنك إبطاء السرعة والحصول على ما يكفي من الوقت لرؤية التعليمات البرمجية الخاصة بك وحالة نظامك أثناء تشغيله. من خلال التنقل بعناية عبر التعليمات البرمجية الموجودة في مصحح الأخطاء، اكتشفنا العديد من العيوب في منطق البرمجة لدينا. هذه تقنية مجربة. الرجاء استخدام هذه التقنية. وأخيرًا، تأكد من أن مجموعة الاختبار الخاصة بك تتسبب في فشل وظيفتك. حاول أن يكون لديك مجموعة اختبار تفحص كل سطر من التعليمات البرمجية في الوظيفة. يمكن أن يساعدك هذا في اكتشاف الأنماط، خاصة عند أتمتة اختباراتك وتشغيلها في كل مرة تقوم فيها بإنشاء التعليمات البرمجية الخاصة بك.
هناك شيء واحد مهم جدًا يمكن قوله عن أوضاع الفشل. تأكد من أن نظامك في الحالة الأكثر أمانًا الممكنة عند فشل التعليمات البرمجية الخاصة بك. يتم عرض بعض التعليمات البرمجية الإشكالية أدناه:
bool accessGranted = true;
يحاول {
// معرفة ما إذا كان بإمكاننا الوصول إلى c:test.txt
جديد FileStream(@"c:test.txt"،
وضع الملف.فتح،
FileAccess.Read).Close();
}
التقاط (SecurityException x) {
// تم الرفض
accessGranted = false;
}
يمسك (...) {
// حدث شيء آخر
}
على الرغم من أننا نستخدم CLR، إلا أنه لا يزال مسموحًا لنا بالوصول إلى الملف. في هذه الحالة، لا يتم طرح SecurityException. ولكن ماذا لو، على سبيل المثال، قائمة التحكم في الوصول التقديرية (DACL) للملف لا تسمح لنا بالوصول؟ في هذا الوقت، سيتم طرح نوع آخر من الاستثناءات. لكن بسبب الافتراضات المتفائلة في السطر الأول من الكود، لن نعرف ذلك أبدًا.
أفضل طريقة لكتابة هذا الرمز هي توخي الحذر:
bool accessGranted = false;
يحاول {
// معرفة ما إذا كان بإمكاننا الوصول إلى c:test.txt
جديد FileStream(@"c:test.txt"،
وضع الملف.فتح،
FileAccess.Read).Close();
// إذا كنا لا نزال هنا، عظيم!
accessGranted = true;
}
Catch (...) {}
سيكون هذا أكثر استقرارًا لأنه بغض النظر عن مدى فشلنا، فسوف نعود دائمًا إلى الوضع الأكثر أمانًا.
9. انتحال الشخصية معرض للخطر للغاية
عند كتابة تطبيقات الخادم، غالبًا ما تجد نفسك تستخدم، بشكل مباشر أو غير مباشر، ميزة مفيدة في Windows تسمى انتحال الهوية. يسمح الانتحال لكل مؤشر ترابط في العملية بالعمل في بيئة أمان مختلفة، عادةً تلك الخاصة بالعميل. على سبيل المثال، عندما يتلقى معيد توجيه نظام الملفات طلب ملف عبر الشبكة، فإنه يقوم بمصادقة العميل البعيد، والتحقق للتأكد من أن طلب العميل لا ينتهك قائمة التحكم بالوصول الاختيارية (DACL) في المشاركة، ثم يقوم بإرفاق علامة العميل بمؤشر الترابط الذي يعالج الطلب لمحاكاة العميل . يمكن لهذا الخيط بعد ذلك الوصول إلى نظام الملفات المحلي على الخادم باستخدام بيئة الأمان الخاصة بالعميل. يعد هذا مناسبًا نظرًا لأن نظام الملفات المحلي آمن بالفعل. يقوم بإجراء فحص الوصول مع الأخذ في الاعتبار نوع الوصول المطلوب، وقائمة التحكم بالوصول (DACL) الموجودة على الملف، وعلامة الانتحال على مؤشر الترابط. في حالة فشل التحقق من الوصول، يقوم نظام الملفات المحلي بإبلاغ ذلك إلى معيد توجيه نظام الملفات، والذي يرسل بعد ذلك خطأ إلى العميل البعيد. يعد هذا بلا شك مناسبًا لمعيد توجيه نظام الملفات، لأنه ببساطة يمرر الطلب إلى نظام الملفات المحلي ويتيح له إجراء عمليات فحص الوصول الخاصة به، تمامًا كما لو كان العميل محليًا.
يعد هذا جيدًا وجيدًا لبوابة بسيطة مثل معيد توجيه الملفات. لكن عمليات المحاكاة تُستخدم غالبًا في تطبيقات أخرى أكثر تعقيدًا. خذ تطبيق ويب كمثال. إذا قمت بكتابة برنامج ASP كلاسيكي غير مُدار أو ملحق ISAPI أو تطبيق ASP.NET ولديك المواصفات التالية في ملف Web.config الخاص به
<identity impersonate='true'>
، فستحتوي بيئة التشغيل الخاصة بك على بيئتين أمان مختلفتين: بشكل عام، سيتم استخدام علامة العملية وعلامة الموضوع للتحقق من الوصول (انظر الشكل 3). بافتراض أنك تكتب تطبيق ISAPI يتم تشغيله في عملية خادم ويب، وبافتراض أن معظم الطلبات لم تتم مصادقتها، فقد تكون علامة الموضوع الخاصة بك هي IUSR_MACHINE، ولكن علامة العملية الخاصة بك هي SYSTEM! لنفترض أن الكود الخاص بك يمكن استغلاله من قبل ممثل سيء عبر تجاوز سعة المخزن المؤقت. هل تعتقد أنه سيكون راضيًا عن تشغيل IUSR_MACHINE فقط؟ بالطبع لا. من المرجح أن رمز الهجوم الخاص به يستدعي RevertToSelf لإزالة علامة الانتحال على أمل زيادة مستوى الامتياز الخاص به. وفي هذه الحالة سوف ينجح بسهولة. يمكنه أيضًا الاتصال بـ CreateProcess. ولا يقوم بنسخ علامة العملية الجديدة من علامة الانتحال، ولكن من علامة العملية بحيث يمكن تشغيل العملية الجديدة كـ SYSTEM.
فكيف حل هذه المشكلة الصغيرة؟ بالإضافة إلى ضمان عدم حدوث تجاوزات في المخزن المؤقت في المقام الأول، تذكر مبدأ الترخيص الأقل. إذا كانت التعليمات البرمجية الخاصة بك لا تتطلب امتيازات كبيرة مثل SYSTEM، فلا تقم بتكوين تطبيق الويب الخاص بك ليتم تشغيله في عملية خادم الويب. إذا قمت ببساطة بتكوين تطبيق الويب الخاص بك ليعمل في بيئة عزل متوسطة أو عالية، فستكون علامة العملية الخاصة بك هي IWAM_MACHINE. ليس لديك في الواقع أي أذونات، لذلك ليس لهذا الهجوم أي تأثير تقريبًا. لاحظ أنه في IIS 6.0 (الذي سيصبح قريبًا أحد مكونات Windows .NET Server)، لن يتم تشغيل التعليمات البرمجية المكتوبة بواسطة المستخدم كـ SYSTEM بشكل افتراضي. استنادًا إلى إدراك أن المطورين يرتكبون أخطاء، فإن أي مساعدة يمكن أن يقدمها خادم الويب لتقليل الأذونات الممنوحة للتعليمات البرمجية تكون مفيدة في حالة وجود مشكلة أمنية في التعليمات البرمجية.
هنا مأزق آخر قد يواجهه مبرمجو COM. لدى COM ميل سيئ لتجاهل سلاسل الرسائل. إذا قمت باستدعاء خادم COM قيد التشغيل ولم يتطابق طراز مؤشر الترابط الخاص به مع طراز مؤشر ترابط الاستدعاء، فسيقوم COM بإجراء الاستدعاء على مؤشر ترابط آخر. لا يقوم COM بنشر علامة الانتحال على مؤشر ترابط المتصل، وبالتالي تكون النتيجة تنفيذ الاستدعاء في سياق الأمان الخاص بالعملية بدلاً من سياق الأمان الخاص بمؤشر ترابط الاستدعاء. يا لها من مفاجأة!
وإليك مثال آخر على مخاطر المحاكاة. افترض أن الخادم الخاص بك يقبل الطلبات المرسلة عبر الأنابيب المسماة، أو DCOM، أو RPC. يمكنك مصادقة العملاء وانتحال شخصيتهم، وفتح كائنات kernel نيابة عنهم من خلال انتحال الهوية. ونسيت إغلاق أحد الكائنات (مثل ملف) عند قطع اتصال العميل. عندما يأتي العميل التالي، تقوم بتوثيقه وانتحال شخصيته، وتخمن ماذا سيحدث؟ لا يزال بإمكانك الوصول إلى الملفات التي "فقدها" العميل السابق، حتى لو لم يتمكن العميل الجديد من الوصول إلى الملف. لأسباب تتعلق بالأداء، تقوم النواة فقط بإجراء اختبارات الوصول على الكائن عند فتحه لأول مرة. لا يزال بإمكانك الوصول إلى هذا الملف حتى إذا قمت بتغيير بيئة الأمان الخاصة بك لاحقًا لأنك تنتحل شخصية مستخدم آخر.
جميع المواقف المذكورة أعلاه تذكرك بأن المحاكاة توفر الراحة لمطوري الخوادم، ولكن هذه الراحة لها مخاطر خفية كبيرة. عند تشغيل برنامج بعلامة وهمية، تأكد من إيلاء اهتمام دقيق للتعليمات البرمجية الخاصة بك.
10. اكتب التطبيقات التي يمكن للمستخدمين غير الإداريين استخدامها فعليًا،
وهذه في الواقع نتيجة طبيعية لمبدأ الترخيص الأقل. إذا استمر المبرمجون في تطوير التعليمات البرمجية التي تتطلب وجود مسؤول ليعمل بشكل صحيح على Windows، فلا يمكننا أن نتوقع تحسين أمان النظام. يتمتع Windows بمجموعة قوية جدًا من ميزات الأمان، ولكن لا يمكن للمستخدمين الاستفادة منها إذا كان عليهم أن يكونوا مسؤولين لتشغيلها.
كيف يمكنك التحسن؟ أولاً، جربه بنفسك، دون تشغيله كمسؤول. ستتعلم قريبًا مدى صعوبة استخدام برنامج لم يتم تصميمه مع وضع الأمان في الاعتبار. في أحد الأيام، قمت (كيث) بتثبيت جزء من البرنامج الذي قدمته الشركة المصنعة لجهازي المحمول والذي يقوم بمزامنة البيانات بين جهاز الكمبيوتر المكتبي الخاص بي وجهازي المحمول. كالعادة، قمت بتسجيل الخروج من حساب المستخدم العادي الخاص بي، وقمت بتسجيل الدخول مرة أخرى باستخدام حساب المسؤول المدمج، وقمت بتثبيت البرنامج، ثم قمت بتسجيل الدخول مرة أخرى إلى حسابي العادي، وحاولت تشغيل البرنامج. ونتيجة لذلك، ينبثق التطبيق مربع حوار يفيد بأنه لا يمكن الوصول إلى ملف البيانات المطلوب، ثم يعطي رسالة انتهاك الوصول. أيها الأصدقاء، هذا منتج برمجي لشركة كبرى مصنعة للأجهزة المحمولة. فهل هناك أي مبرر لهذا الخطأ؟
بعد تشغيل FILEMON من http://sysinternals.com (باللغة الإنجليزية) اكتشفت بسرعة أن التطبيق كان يحاول فتح ملف بيانات للوصول للكتابة والذي تم تثبيته في نفس الدليل مثل الوسيط القابل للتنفيذ للتطبيق. عند تثبيت التطبيقات في دليل ملفات البرامج كما هو متوقع، يجب ألا تحاول كتابة البيانات إلى هذا الدليل. لدى ملفات البرنامج سياسة التحكم في الوصول المقيدة لسبب ما. لا نريد أن يكتب المستخدمون إلى هذه الأدلة، لأن هذا من شأنه أن يسهل على أحد المستخدمين ترك حصان طروادة ليقوم مستخدم آخر بتنفيذه. في الواقع، تعد هذه الاتفاقية أحد متطلبات التوقيع الأساسية لنظام Windows XP (راجع http://www.microsoft.com/winlogo [الإنجليزية]).
نسمع الكثير من المبرمجين يقدمون أعذارًا عن سبب اختيارهم للعمل كمسؤول عند تطوير التعليمات البرمجية. وإذا واصلنا تجاهل هذه المشكلة، فلن يؤدي ذلك إلا إلى تفاقم الأمور. أيها الأصدقاء، لا تحتاجون إلى حقوق المسؤول لتحرير ملف نصي. حقوق المسؤول ليست مطلوبة أيضًا لتحرير البرنامج أو تصحيح أخطائه. عندما تحتاج إلى امتيازات المسؤول، استخدم ميزة RunAs الخاصة بنظام التشغيل لتشغيل 玎com.asp?TARGET=/winlogo/">http://www.microsoft.com/winlogo [إنجليزي]).
نسمع هذا كثيرًا، فالمبرمجون يقدمون الأعذار لماذا يختارون التشغيل كمسؤول عند تطوير التعليمات البرمجية؟ إذا واصلنا تجاهل هذه المشكلة، فسيؤدي ذلك إلى تفاقم الأمور. ولا يتطلب تحرير الملف النصي امتيازات المسؤول. أو أن تصحيح أخطاء البرنامج لا يتطلب امتيازات المسؤول الامتيازات، استخدم ميزة RunAs الخاصة بنظام التشغيل لتشغيل برنامج منفصل بامتيازات مرتفعة (راجع عمود "ملخصات الأمان" الصادر في نوفمبر 2001 [إنجليزي].) إذا كنت تكتب أدوات للمطورين، فستتحمل مسؤولية إضافية تجاه هذه المجموعة أوقف هذه الحلقة المفرغة من كتابة التعليمات البرمجية التي لا يمكن تشغيلها إلا كمسؤول. يجب أن يتغير الهدف بشكل أساسي
لمزيد من المعلومات حول كيفية تشغيل المطورين بسهولة كغير مسؤولين، راجع موقع الويب الخاص بـ Keith على http://www.develop. com/kbrown (باللغة الإنجليزية) راجع كتاب مايكل "كتابة الكود الآمن" (Microsoft Press, 2001)، والذي يقدم نصائح حول كيفية كتابة التطبيقات التي تعمل بشكل جيد في بيئة غير إدارية.