Intro to Whitebox Pentesting
Code Review - Authentication
Now that we understand what whitebox penetration testing is and the process we will follow, the remainder of this module will be used to demonstrate a practical example of a whitebox pentesting exercise. We will go through each step and apply what we discussed in the previous sections.
We will discuss a case of advanced code injection, which requires a whitebox pentest to identify and exploit the vulnerability properly. The specific vulnerability we will discuss would only be exploitable with access to the source code due to specific exploitation requirements that would not be evident without direct access to the source code, as is often the case with many other vulnerabilities.
Finally, for the sake of simplicity, we will not 'yet' be reviewing a large code base, as this would make the practical example very long, but other modules will cover larger code bases. Instead, we will focus on a particular functionality within the code, and the provided code base would only contain that functionality and other necessary functions for it to work. As previously discussed, a whitebox pentest is often only requested for a specific functionality instead of the entire code base, especially if whitebox pentest exercises were incorporated in the DevOps cycle. In such cases, we would test each new functionality rather than the whole code base.
With that said, let's get into reviewing the code.
Data Gathering
As discussed in the code review section, the data-gathering phase usually consists of meetings to set the scope of the test and provide the code base and any available documentation for it. In this module, we will assume that we were given the code base in an archive without further details, which is the minimum requirement for any whitebox pentest.
We can start by downloading the archive found at the end of this section, extracting its content, and then opening it in VSCode, using File>Open Folder in VSCode or the following command:
[!bash!]$ code ./intro_to_whitebox_pentesting
Note: With PwnBox, you should use the codium command instead of code.

As we can see, the code base hierarchy is quite simple, consisting of an entry file (app.js) and a couple of other directories. So, let's look at the code to understand better how it works.
app.js
The app.js file starts by setting up an express server, setting up a JSON body parser, and then setting up the main API routes:
// set up express
const app = express();
const port = parseInt("5000");
// set up body parser and cors
app.use(bodyParser.json());
// set up API routes
app.use("/api/auth", authRoutes);
app.use("/api/service", serviceRoutes);
This is a basic express server setup for a node.js API backend. The rest of the file sets up 404 route handling and exception handling and ends by starting the express server:
// start the Express server
app.listen(port, () => {
console.log(`⚡️[server]: Server is running at http://localhost:${port}`);
console.log(`⚡️[api]: APIs are running at http://localhost:${port}/api`);
});
The only interesting bit from this file is the API routes, as the rest are basic express settings. So, let's take a look at those routes.
Authentication
In VSCode, we can hold CMD/CTRL and click on authRoutes, which will take us to the file containing these routes. The routes/auth-routes.js file simply consists of a single API endpoint with getUserToken:
const router = express.Router();
router.post("/authenticate", getUserToken);
module.exports = router;
The /authenticate endpoint requires a POST request and is used under /api/auth. To better look at this function, we can once again click on getUserToken to open it in a new file, and we will get the auth-controller.js file under the controllers/ directory. This file contains the following three functions:
-
validateEmail -
getUserToken -
verifyToken
function validateEmail(email) {
return String(email)
.toLowerCase()
.match(/^...SIP...$/);
}
The validateEmail function appears to be local to this file since it is not exported at the end of the file. Taking a look at it, it seems a basic function that validates a string against a regular expression pattern to ensure it matches an email format.
getUserToken
Getting back to getUserToken, we see that it starts by obtaining the email parameter from req.body, which is the POST request body. We know from the bodyparser we saw previously that all endpoints expect a JSON body, so we should keep that in mind.
After that, the function validates the email format using the above validateEmail function, as denoted by a comment in the code. Such comments are always helpful to make it easier to understand the code. But what if the code didn't have any comments? In that case, we must rely on our coding knowledge to understand the functionality.
While we would be expected to have deep knowledge of the language we are reviewing in a' secure coding' exercise, the same is not a requirement for whitebox pentesting. This is because we would probably be testing various code bases in multiple languages, and we can't be expected to be experts in all languages, unlike secure coding, where we would usually be sticking to a single code base for an extended period.
This is why the primary skill we require for whitebox pentesting is the ability to understand the general purpose of the code, which should enable us to determine whether the code is vulnerable.
If we continue with the function, we will see that comments do not denote the next part. A quick look at it shows that it appears to be signing a jwt token that contains two keys:
- email "from our input"
- role "determined by email"
After that, the endpoint returns the signed jwt token. In case we were not sure of our understanding, we can ask AI to tell us what the function does using VSCode Copilot "or any other coding-aware AI chatbot, like ChatGPT":

Copilot goes into more detail, but it affirms our understanding. Such tools can be beneficial in the whitebox pentesting exercise, as they can simplify many tasks for us. However, a word of warning: Do not overly rely on AI for all tasks, as it is very common for it to make mistakes or miss stuff a human may notice. Mainly use it to confirm your understanding "as we just did" or to clarify something you do not understand "and then double check to confirm".
Note: This is a simplified authentication function that returns an authentication token to the user. This is done to avoid relying on a database that requires further setup and resources, but the general idea remains the same. Many other modules will have a full authentication mechanism, but this should be enough for our purposes.
verifyToken
Finally, we have the verifyToken function. It starts by obtaining the token from req.headers.authorization, which is the authorization HTTP header, as the name suggests. If no token is provided, it will give a 403 Unauthorized error. Otherwise, it uses the jwt.verify function to verify that the token is signed and not manipulated. If the token is signed, it adds it to the request's user object to be used by other endpoints in the server, as we will see later on.
This function retrieves the secure details stored in the user's token to be used by other endpoints in the server. So, if an endpoint uses verifyToken before it is called, then we know that this endpoint likely requires an authenticated token (i.e. valid user authentication).
So far, everything seems normal, so let's jump to the next route and see what it contains.
/ 1 spawns left
Questions
Answer the question(s) below to complete this Section and earn cubes!
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