Time Based SQL Injection
در مقاله ی قبلی توضیح دادیم که Blind SQL Injection چیست و در چه صورت است که چاره ای جز استفاده از روش Blind نداریم و توضیح دادیم که حملات Blind SQL Injection به 2 دسته Time based و Boolean based تقسیم می شوند؛ در این مقاله قصد داریم که روش Time based را به طور مفصل تری بررسی کنیم.
گفتیم که در روش blind، نمی توانیم به طور مستقیم داده هایی که می خواهیم را مشاهده کنیم و صرفا با یک خروجی Yes/No از طرف سایت قربانی روبرو هستیم و لذا مانند بازی بیست سوالی با طرح سوالات مختلف از پایگاه داده، مقدار مطلوبمان را حدس زده و مرحله به مرحله به جواب دقیق تری می رسیم.
تفاوت 2 روش Time based و Boolean based
این اصول در هر 2 روش Time based و Boolean based برقرار هست و تفاوتی نمی کند.
اما نکته اصلی که باعث تفاوت در 2 روش بالا می شود، شیوه ی پاسخ سایت به سوالات ماست.
جواب سوالات در هر 2 روش Yes/No است اما در Boolean based همانطور که توضیح مختصری هم در مقاله قبل دادیم و در مقاله بعد به طول مفصل تر بررسی می کنیم، خروجی ای با همین مضمون برای ما به نمایش در می آید و از تفاوت خروجی هاست که به جواب سوالمان می رسیم در صورتی که در Time based جواب سایت نسبت به سوال خودمان را از روی زمان تاخیر بارگذاری صفحه متوجه می شویم.
به عنوان مثال سایت زیر را در نظر بگیرید.
در مقاله قبلی دیدیم که سایت فوق به ازای ورودی های مختلفی که می گیرد، تنها 2 خروجی User ID exists in the database و User ID is MISSING from the database را چاپ می کند که در واقع شبیه جواب Yes/No هست.
حال می خواهیم از روش Time based اطلاعاتی را از پایگاه داده استخراج کنیم.
تشخیص آسیب پذیری سایت نسبت به Time based
ابتدا چک می کنیم که آیا این سایت اصلا به حالت Time based آسیب پذیر هست یا خیر.
برای چک کردن این موضوع از تابع sleep
استفاده می کنیم. در واقع تابع sleep
را وارد Query کرده و بررسی می کنیم که آیا این تابع در سایت عمل می کند یا خیر.
ورودی زیر را وارد می کنیم:
1’ and sleep()#
با زدن دکمه submit در مثال تصویر بالا، مشاهده می کنیم که بارگذاری صفحه سایت 3 ثانیه طول می کشد:
و سپس خروجی زیر که معادل حالت No هست، ظاهر می شود.
و اگر در Query ورودی به جای and، or قرار می دادیم باز هم بارگذاری صفحه 3 ثانیه طول می کشید اما خروجی به شکل زیر بود:
برای تست آسیب پذیر بودن سایت فوق به روش Time based، همین طول کشیدن بارگذاری صفحه کافی است و مهم نیست که خروجی حاصل Yes باشد و یا No.
حالا که متوجه شدیم این سایت به حالت Time based آسیب پذیر هست به سراغ استخراج اطلاعات می رویم.
استخراج اطلاعات از طریق Time based SQL Injection
فرض کنید می خواهیم اطلاعات کاربران سایت مانند نام کاربری و رمز عبور را به دست آوریم.
استخراج نام دیتابیس
اولین قدم به دست آوردن نام دیتابیس سایت هست.
برای این کار ابتدا باید تعداد کاراکتر های آن را به دست آوریم؛ سپس تک تک حروف آن را به دست آوریم و نهایتا نام آن را حدس بزنیم.
برای به دست آوردن تعداد حروف دیتابیس Query زیر را تزریق می کنیم:
1' and (select sleep(3) where database() like '%___%')#
و مشاهده می کنیم که تأخیری در بارگذاری صفحه ایجاد نشد؛ لذا نتیجه می گیریم که شرط Query بالا صحیح نیست و تعداد حروف نام دیتابیس 3 حرف نیست.
خروجی چاپی سایت برای ما اهمیتی ندارد و همانطور که گفتیم در روش Time based، جواب Yes و No ما از طریق تأخیر یا عدم تأخیر سایت در بارگذاری صفحه انجام می شود.
برای ادامه چک می کنیم که آیا نام دیتابیس شامل 4 حرف است یا خیر و برای این کار Query زیر را تزریق می کنیم:
1' and (select sleep(3) where database() like '%____%')#
با زدن کلید submit مشاهده می کنیم که در بارگذاری صفحه بعد تأخیری 3 ثانیه ای ایجاد می شود.
بنابراین نتیجه می گیریم که شرط Query بالا درست است و نام دیتابیس سایت شامل 4 حرف است.
در مرحله بعد سراغ شناسایی تک تک حروف و ترتیب قرار گرفتن آن ها در نام دیتابیس می رویم که نهایتا بتوانیم نام دقیق دیتابیس را به دست آوریم.
از این مرحله به بعد برای به دست آوردن نام دقیق دیتابیس 2 تا راه داریم:
- از آنجا که طول نام دیتابیس را به دست آورده ایم، می توانیم به ترتیب هر کدام از کاراکتر ها را چک کنیم که برابر چه حرفی است و بعد از شناسایی کاراکتر چهارم، نام دقیق دیتابیس را به دست آوریم.
- ابتدا به صورت کلی چک کنیم که اصلا نام دیتابیس شامل چه حروفی است و بعد از آن از روی حروف به دست آمده نام دقیق دیتابیس را حدس بزنیم. برای این کار از Query زیر استفاده می کنیم:
1' and (select sleep(3) where database() like '%a%')#
در Query بالا چک می کنیم که آیا نام دیتابیس شامل حرف a هست یا خیر و به همین ترتیب تک تک حروف دیگر را هم چک می کنیم تا نهایتا به 4 حرف برسیم که ترتیب قرارگیری شان مشخص نیست و از روی این 4 حرف ترتیب شان را حدس بزنیم.
اما ما در اینجا روش اول را انتخاب می کنیم؛
از آنجا که تا اینجای کار متوجه شدیم نام دیتابیس شامل 4 حرف هست، در این مرحله حرف تشکیل دهنده هر کدام از این 4 جایگاه که به ترتیب کنار هم قرار گرفته اند را شناسایی می کنیم.
در ابتدا سراغ کاراکتر اول می رویم.
چک می کنیم که آیا این کاراکتر برابر حرف a هست یا خیر و برای این کار Query زیر را تزریق می کنیم:
1’ and (select sleep(3) where database() like ‘a___’)#
مشاهده می کنیم که در بارگذاری صفحه بعدی تأخیری ایجاد نمی شود پس حرف اول a نیست.
به ترتیب سایر حروف رو چک می کنیم و هنگامی که به حرف d می رسیم و Query زیر را تزریق می کنیم، متوجه می شویم که در بارگذاری صفحه 3 ثانیه تأخیر ایجاد می شود؛ بنابراین حرف اول مقدار d هست.
1’ and (select sleep(3) where database() like ‘d___’)#
سراغ شناسایی حرف دوم می رویم.
مشابه حالت قبل عمل می کنیم و برای تشخیص اینکه آیا حرف دوم برابر a هست یا نه، Query زیر را تزریق می کنیم:
1’ and (select sleep(3) where database() like ‘da__’)#
و مشاهده می کنیم که تأخیری در بارگذاری صفحه رخ نمی دهد؛ پس حرف دوم برابر مقدار a نیست.
حروف دیگر را تک تک چک می کنیم و زمانی که به حرف v می رسیم، Query زیر را تزریق می کنیم و متوجه می شویم که بارگذاری صفحه با 3 ثانیه تأخیر انجام می شود پس حرف دوم برابر v هست.
1’ and (select sleep(3) where database() like ‘dv__’)#
همین روند رو برای حروف سوم و چهارم هم انجام می دهیم و بعد از تزریق Query های فراوان نهایتا متوجه می شویم که حرف سوم مقدار w و حرف چهارم مقدار a هست.
در آخر برای چک کردن نام دقیق دیتابیس Query زیر را تزریق می کنیم:
1’ and (select sleep(3) where database() = 'dvwa')#
و مشاهده می کنیم که بارگذاری صفحه با 3 ثانیه تأخیر انجام می شود:
پس نام دقیق دیتابیس برابر dvwa
هست.
همانطور که گفتیم می خواهیم به اطلاعات کاربران نظیر نام کاربری و رمز عبور دسترسی پیدا کنیم.
برای این منظور بعد از طی مرحله بالا، باید مراحل زیر را طی کنیم:
- به دست آوردن تعداد جداول دیتابیس
- به دست آوردن نام جداول دیتابیس
- به دست آوردن نام فیلد های جدول مورد نظر(جدول مربوط به کاربران)
- استخراج اطلاعات کاربران
توجه کنید که در تک تک این مراحل روش کار همانند روشی است که برای به دست آوردن نام دیتابیس به کار برده شد و فقط در توابع استفاده شده در Query مقداری تغییر اعمال می کنیم. به همین دلیل از آوردن مراحل تکراری صرف نظر کرده و صرفا به ذکر خلاصه ای از مراحل کار اکتفا خواهیم کرد.
استخراج تعداد جداول دیتابیس
برای این منظور از Query زیر استفاده می کنیم:
1' and (select sleep(3) where (select count(table_name) from information_schema.tables where table_schema=database()) = 1)#
در واقع در Query بالا چک می کنیم که آیا تعداد جداول دیتابیس یکی است یا خیر و مشاهده می کنیم که تأخیری رخ نمی دهد.
پس مقدار 1 را به 2 افزایش داده و با تأخیر روبرو می شویم:
نتیجه می گیریم که دیتابیس این سایت شامل 2 جدول است.
استخراج نام جداول دیتابیس
در این مرحله از ترکیب Query های تزریق شده در 2 مرحله قبل استفاده می کنیم.
یعنی با استفاده از توابع استفاده شده در Query قبل، شبیه حدس نام دیتابیس مرحله به مرحله نام جداول دیتابیس را حدس می زنیم؛ یعنی ابتدا تعداد حروف آن را مشخص می کنیم و بعد تک تک حروف را به دست می آوریم که منجر به استخراج نام دقیق جداول می شود.
برای به دست آوردن تعداد حروف نام جدول، Query زیر را تزریق می کنیم:
1' and (select sleep(3) where ((select table_name from information_schema.tables where table_schema='dvwa' limit 1,1) like '_____'))#
عبارت limit 1,1 در Query بالا خروجی Query را محدود می کند و می گوید که تنها مقدار دومی که به دست می آوری را حساب کن و نه بقیه نتایج به دست آمده.
مشاهده می کنیم که تأخیر ایجاد می شود پس تعداد حروف نام یکی از جداول برابر 5 است.
روندی که برای به دست آوردن نام دیتابیس طی کردیم اینجا هم طی می کنیم و نهایتا متوجه می شویم که نام 2 جدول دیتابیس این سایت users
و guestbook
هست.
استخراج نام فیلد های جدول users
در این مرحله هم کاری مشابه مراحل قبل انجام می دهیم.
ابتدا تعداد فیلد ها را به دست آورده و سپس نام آن ها و برای به دست آوردن نام آنها مانند به دست آوردن نام دیتابیس عمل می کنیم.
Query زیرا را وارد می کنیم:
1' and (select sleep(3) where (select count(column_name) from information_schema.columns where table_name='users')=1)#
و مقدار 1 را انقدر تغییر می دهیم تا با تأخیر مواجه شویم.
نتیجه می گیریم که تعداد فیلد های جدول users برابر 8 است.
و سپس مطابق روشی که در نام دیتابیس طی کردیم، با استفاده از Query زیر طول تک تک فیلد ها را به دست می آوریم:
1' and (select sleep(3) where (select column_name from information_schema.columns where table_name='users' limit 0,1) like '____')#
در نهایت پس از پیدا کردن طول هر فیلد دوباره با جایگذاری حروف متفاوت در جایگاه به ترتیب تمامی حروف را به دست آورده و نام هر کدام از فیلد ها را پیدا می کنیم.
استخراج اطلاعات کاربران
اکنون به مرحله آخر و حساس کار می رسیم.
حالا که نام تک تک فیلد های users
را به دست آوردیم، نوبت به استخراج داده های درون آن می رسد.
با طی کردن مرحله قبل متوجه می شویم که فیلد های جدول users
عبارتند از:
User_id, first_name, last_name, user, password, avatar, last_login, failed_login
در این قسمت قصد داریم تا نام کاربری افراد مختلف را که در فیلد user ذخیره سازی شده است را به دست آوریم. برای این کار، دقیقا مشابه مراحل قبل عمل می کنیم.
Query زیر را تزریق می کنیم:
1' and (select sleep(3) where (select user_id from users where user like '%a%' limit 0,1))#
در حقیقت در این Query می گوییم که اگر user کاربر اول (limit 0,1)
شامل حرف a بود، sleep صورت بگیرد و مشاهده می کنیم که بارگذاری سایت با تأخیر انجام می شود:
بعد از تزریق Query های بعدی متوجه می شویم که نام کاربری اولین کاربر مقدار admin
هست.
همان طور که روند کار در استخراج داده ها را مشاهده کردید، به همین ترتیب می توانیم سایر اطلاعات کاربران مانند پسورد و ایمیل و ... آن ها را به دست آوریم.
شاید انقدر زحمت هم نیاز نبود!
اگر روندی که برای به دست آوردن نام دیتابیس و تعداد جداول و نام جداول و بعد از آن نام فیلد های جدول کاربران و نهایتا به دست آوردن داده های موجود در جدول کاربران طی کردیم را در نظر بگیرید ممکن است کلا از استخراج اطلاعات به روش Time based blind SQL Injection منصرف شوید اما باید بگوییم که خیلی از این مراحل ممکن بود با حدس هایی هوشمندانه به سرعت قابل انجام باشند و نیازی به استخراج تک تک حروف در مورد آن ها نباشد.
مثلا برای نام جدول کاربران همان اول معلوم بود که به احتمال زیاد برابر users هست.
فیلد های جدول کاربران هم نظیر id، password، name
هم با کمی سعی و خطا معلوم می شد که مثلا فیلدی به نام user_id
یا user
دارد.
بنابراین داشتن تجربه و بررسی اسامی رایج برای جدول کاربران، ممکن است در بعضی از مواقع سرعت شما را در این نوع از SQL Injection بالاتر ببرد.
در طرف دیگر قضیه نیز در نظر داشته باشید که یکی از مواردی که بهره برداری از این نوع آسیب پذیری را سخت و دشوار می کند استفاده از پیشوند های تصادفی یا نام گذاری سخت و سفارشی شده جدوال است. بدین ترتیب با افزایش زمان و تعداد درخواست ها برای یک هکر احتمال اینکه او در دام فایروال ها یا سیستم های مانیتورینگ بیوفتد افزایش پیدا می کند.