Intro to Whitebox Pentesting
Blind Exploitation
There may be cases where even the previous option would not work, and we would not have any way to inject the command output into the HTTP response. In such cases, we would have two last options: sleep timers and boolean output.
The technique of blindly obtaining text through sleep timers is based on a simple idea: if the first character is X, then sleep for 1 second; otherwise, don't sleep. So, we can send multiple requests iterating over the entire ASCII charset, and whenever there is a delay in the response, we will know that we have hit the correct character.
The same idea is used for boolean output, but instead of sleeping, we would slightly change something in the request, like controlling whether an error message would show or controlling the HTTP response code (e.g. 200 or 403). Since we are going with the premise that we cannot inject command outputs into the HTTP response, we will also assume this is impossible.
Note: Even if we were completely blind and we had absolutely no way to extract the command output, the vulnerability would not be useless, as we would still be executing commands on the backend, only doing so blindly. Any command executed on the backend can result in serious harm, like a DoS attack or a ransomware attack, so the vulnerability would still be considered critical.
Sleep Timers
You may think that since we can execute any JavaScript code, it may be best to use JavaScript to cause a delay, such as using the setTimeout function. However, this may not always work, as NodeJS servers often process requests asynchronously, so we would get a response immediately, even if part of the injected code is still processing. This may not always be the case, so it is worth testing and validating this claim.
Challenge: Try to search for different ways to cause a delay in NodeJS, and then inject that code in our payload to see if the response would be delayed. This is a great learning opportunity to see how different types of injected code would be processed. If you face any issues, you can add a breakpoint on line 45 and then read the value of e.
Luckily, we already have system command execution, so we don't need to rely on JavaScript code and can simply use system commands to cause a delay. The advantage here is that the execSync function waits for the command to finish processing, so a system sleep should cause a delay in the response.
Our backend runs on linux, so we can use a sleep 2 command for 2 seconds delay. Let's try sending a request with this command and see if we get a delay in the HTTP response. To measure the time our request takes, we will add the time command before our curl command, as follows:
[!bash!]$ time curl -s -X POST -H "Content-Type: application/json" -H "Authorization: bearer eyJhbGciOiJIUz...SNIP...1YLEvDs4SR7RHfQ" -d "{ \"text\": \"'}) + require('child_process').execSync('sleep 2')//\" }" http://localhost:5000/api/service/generate
{"message":"Could not generate QR code."}curl -s -X POST -H "Content-Type: application/json" -H -d 0.00s user 0.01s system 0% cpu 2.035 total
As we can see, the command took exactly 2.035 seconds, which matches the sleep timer duration we specified. We can try changing this to 3 or 5, and we would get similar delays. This means that we have a way to delay the response, which we should be able to use to blindly obtain the command output.
Note: Multiple factors may cause a delay in the HTTP responses, like internet speeds or load on the backend server, which may lead to inaccurate results if we use a short delay (e.g. 1 second). This is why using longer delays often leads to more accuracy, which comes at the cost of slower exfiltration. This is detailed further in the Blind SQL Injection module.
Reading Output through Sleep
We need to read the first character in the command output and compare it against each ASCII charset. Then, we must repeat this for all other characters in the command output until no match is found, indicating the output's end.
If we were relying on JavaScript code, we could do so with the following code:
require("child_process").execSync("ls").toString()[0] == "a"
? new Promise((resolve) => setTimeout(resolve, 2000))
: null;
This may work and need some tinkering to avoid breaking the eval function and any other code. Luckily, we already have command execution, so we can rely on the bash command for sleep as we did before and do not need to write a lot of JavaScript code to get it working. If we did not have command execution and were trying to read the content of a local file, we would need to rely on JavaScript, as shown above.
So, we need a one-line bash command that executes our specified command, reads the first character, compares it to a. If it matches, then sleep for 2 seconds, if not, do nothing. Let's copy this entire sentence and ask AI to write this bash code for us:

As we can see, GitHub Copilot gave us this command:
command | head -c 1 | { read c; if [ "$c" = "a" ]; then sleep 2; fi; }
Excellent! This saves us a lot of online searching. Let's test this in our payload while ensuring that we escape any characters that may break the JSON body or the JavaScript code. We will replace command with ls:
{
"text": "'}) + require('child_process').execSync('ls | head -c 1 | { read c; if [ \"$c\" = \"a\" ]; then sleep 2; fi; }')//"
}
If we send the above payload, we get an immediate response. However, if we replace a with the actual first character in the command output, which is n from node_modules as we saw in the previous section, we do indeed get a delay of 2 seconds.
Then, we can move to the next character and try to find its value. But, if we change head -c 1 to head -c 2, then it would return the first 2 characters. So, we would either need to append the first identified character, or use tail to only capture the last character, so we would always be reading 1 character, as follows:
{
"text": "'}) + require('child_process').execSync('ls | head -c 2 | tail -c 1 | { read c; if [ \"$c\" = \"a\" ]; then sleep 2; fi; }')//"
}
This way, we can keep modifying the number after head, and it would act as the index of the character we are currently testing. Obviously, this is not feasible to be done manually character by character, as it would take forever to finish. Furthermore, using curl at this point becomes too difficult for the amount of character escapes we need to add. So, try to write a script to automate all of this and be able to execute any command and read its output.
Note: The Blind SQL Injection module goes into detailed steps of writing such an exploit using Python. It also covers several techniques we can use to make the charset scanning more efficient than going through the entire charset, which saves a lot of time if this was our only means of output exfiltration.
Challenge: Try to use what you learned in this section to reached boolean-based exfiltration using the exercise from the previous section, in which this would be possible. Instead of sleeping, you may send a different HTTP response code (e.g. 200 for match and 404 for fail). This would make you thoroughly understand how both techniques work, and how they differ from each other.
/ 1 spawns left
Questions
Answer the question(s) below to complete this Section and earn cubes!
Click here to spawn the target system!
Target:
Click here to spawn the target system!
+10 Streak pts
Table of Contents
Intro to Whitebox PentestingWhitebox Pentesting Process
Whitebox Pentesting Process Code review Local Testing Proof of Concept Patching & RemediationCode Review
Code Review - Authentication Code Review - ServicesLocal Testing
Planning Eval Injection Target Function Code InjectionProof of Concept (PoC)
Command Execution HTTP Response Injection Blind Exploitation Exploit DevelopmentPatching & Remediation
Patching & RemediationSkills Assessment
Skills Assessment - Intro to Whitebox PentestingMy Workstation
OFFLINE
/ 1 spawns left