Intro to Whitebox Pentesting  

Code Injection

Instead of randomly injecting various injection characters, as we do with blackbox pentesting, it is more efficient to gradually build our payload based on the obtained knowledge to easily track how our payload looks when reaching the target function.

So, let's go back to the onError string and try to prepare a working payload by following the above:

`throw({message: 'The input "${text}" contains the following invalid characters: [${text.match(
  /['"`;]/g
)}]', statusCode: 403})`;

If we re-use the JSON payload we used in the previous section { "text": ";" }, the above code would look like the following, as we saw in the previous section:

throw {
  message: 'The input ";" contains the following invalid characters: [;]',
  statusCode: 403,
};

So, we have two potential points of injection:

  1. At "${text}"
  2. At [${text.match(/['"``;]/g)}]

The first one is ideal because our entire input is placed within the string, while the second one is not entirely used, as it is passed into the match function, and then its output is placed in the string.

Injecting

Since we know the string we are injecting into, we do not need to guess the injection character. We see that the string starts with 'The...., so we can use a single-quote character to escape the string and inject code, as follows:

throw({message: 'The input "'" contains the following invalid characters: [']', statusCode: 403})

However, simply using a single-quote character would break the code and crash the application. This can be easily seen from the broken syntax highlighting in the above code, as it is no longer a valid JavaScript code. This is why it is always crucial to ensure that our injection does not cause any syntax errors.

To fix this and build a working payload, there are three rules we can follow:

  1. Comment out the rest of the code
  2. Ensure quotes/parentheses/curly braces are even
  3. Maintain a working function without syntax errors

Commenting

First, we can comment out the rest of the code by using a comment character, which is // in JavaScript. Let's add it after the single quote and see what the code would look like:

throw({message: 'The input "'//" contains the following invalid characters: [']', statusCode: 403})

The code still needs to be fixed, and that's because we need an even number of quotes, parentheses, and curly braces.

Quotes/Parentheses/Curly Braces

To fix this, we should close the opening parenthesis/curly braces ({, which should lead to a valid JavaScript code:

throw { message: 'The input "' }; //" contains the following invalid characters: [']', statusCode: 403, })

Syntax Errors

At this point, our payload is '})//. But, we must ensure the new code would not cause any syntax errors. For example, the throw function may be expecting the statusCode code variable, and since we have commented it out, the function may have an error. If this is the case, we can add it after the single quote, as follows:

', statusCode: 403})//

Another example would be if we were injecting in a multi-line string, like the following:

throw({
  message: 'The input "'})//" contains the following invalid characters: [']',
  statusCode: 403,
});

This way, the closing parenthesis would not be placed correctly, as it would leave the rest of the function code dangling outside. So, we may need to use a command , instead (i.e. ',//), to maintain a working function:

throw {
  message: 'The input "', //" contains the following invalid characters: [']',
  statusCode: 403,
};

Furthermore, since the application is using JSON for the POST body, we must ensure to escape any double-quotes we use, or it may break the request or the JSON body, and may take us down a rabbit hole of not knowing why our payload is not working. With that in mind, and with a working injection payload, let's try to inject some code to see if it would work.

Code Injection

Let's try injecting a simple console.log function and watch the Node console to see if it logs anything. To do so, we will add a semi-colon ; after the parenthesis to start a new line of code and then add the injected code, as follows:

{ "text": "'}); console.log('pwned')//" }

Before we send our payload, let's see how it would look like within the JavaScript code "we can remove the part after // as it won't affect the code":

throw { message: 'The input "' };
console.log("pwned"); //

As we can see from the syntax highlighting, it appears to be a working JavaScript code. So, let's try to send this payload to /api/service/generate (we escaped double quotes in JSON):

[!bash!]$ curl -s -X POST -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzI1N...SNIP...9R6zeoubrQTbUiThBpeQD7_DWibgo" -d "{ \"text\": \"'}); console.log('pwned')//\" }" http://localhost:5000/api/service/generate
{"message":"Could not generate QR code."}

We get the expected error message, but when we go to the Debug Console in VSCode (CMD/CTRL+SHIFT+Y), we do not see anything logged into the console:

That is odd. Our injection has failed. We may face similar cases in a real whitebox pentest exercise, so let's do some debugging to see what went wrong.

Debugging

To ensure that we successfully reach code injection, we must ensure that our payload reaches the vulnerable function as expected. So, let's set a breakpoint on the eval function at line 9, and send the above request again, to review the value of onError:

As we can see, we did get the value. So, let's right-click on it and select Copy Value to review it:

"throw({message: 'The input \"'}); console.log('pwned')//\" contains the following invalid characters: [',',']', statusCode: 403})";

Let's remove the quotes and any added double-quote escapes to view this as JavaScript code as would be executed by the eval function:

throw { message: 'The input "' };
console.log("pwned"); //" contains the following invalid characters: [',',']', statusCode: 403})

The code looks as we intended, so nothing was modified or adjusted in the payload we sent. So, what went wrong? Why did our injected code fail to run? This is a very specific case, but it is important to understand why it failed, as we may face various odd issues in real whitebox pentests.

If we copy the code to a new JavaScript file and view the syntax highlighting, we will notice that the injected code is slightly transparent:

If you have general experience in coding, you would know that in VSCode, or code editors in general, this indicates that this part of code is never reached. This is commonly seen, for example, when importing a package or defining a variable that is never used.

So, why is this code never reached? The answer is that it falls after a throw statement, which is meant to stop execution and return an error message. Does this mean that the code is not vulnerable? Not necessarily. Any lines of code that come after the throw statement are never run. However, we can ensure that our injection falls within the same line code.

Since we used a semi-colon ; to add our injected code, JavaScript considers this as a new line of code, which causes this issue. To avoid this, we can simply use a plus character +, which works similarly to && in bash and executes commands/code consecutively. So, let's replace ; with + in our payload, and send the request again:

As we can see, the word pwned was logged to the Node console, meaning that we successfully reached code injection and confirmed the existence of the vulnerability. Let's review our earlier plan to see how we are doing:

  • [x] Hit the validateString function
  • [x] Trace how our input looks within the function
  • [x] Obtain an admin role
  • [x] Confirm that we reach the eval function
  • [x] Prepare the payload
  • [x] Confirm the payload reaches the target function as intended
  • [x] Inject code and confirm code injection
  • [ ] Reach command execution or file writing
  • [ ] Blindly verify command execution/file writing
  • [ ] Automate the exploitation process by writing an exploit

With the vulnerability confirmed, we can now move to the Proof of Concept step, and try to turn this code injection into command execution, which we will do in the next section.

/ 1 spawns left

Waiting to start...

Optional Exercises

Challenge your understanding of the Module content and answer the optional question(s) below. These are considered supplementary content and are not required to complete the Module. You can reveal the answer at any time to check your work.

Previous

+10 Streak pts

Next