Introduction

This write-up aims to explain a simple vulnerability discovered in an open-source project developed in JavaScript and to show how we can receive a reward without having a big experience in infosec.

Command Injection

According to OWASP, Command injection is an attack in which the goal is the execution of arbitrary commands on the host operating system via a vulnerable application [1].

Thus, many applications are running OS commands concatenating the data from the user, which allows a malicious user to execute arbitrary commands when it is not being sanitized correctly. Example:

1
2
3
4
5
const { execSync } = require('child_process');
const username = "test";
const res = execSync(`echo 'Username: ${username}'`).toString();

console.log(res);

The code above shows the import of child_process, which provides a function to execute commands on the OS. Supposing the constant username represents a user input and then it is put into the function execSync to return the string Username: <user input>. In this case, the user could send a command like $(touch hacked). So, the final string to be executed on the OS would be echo 'Username: $(touch hacked)', which implies in the execution of the command touch HACKED.

This example shows only the basic exploration of the vulnerability, perhaps we can exploit in others ways to concatenate OS commands instead of $().

Real world vulnerability

The systeminformation is a popular Node.js package to get information about the OS. The user data flow is shown in the codes below.

1
2
3
4
5
6
7
8
function inetChecksite(url, callback) {
const exec = require('child_process').exec;
const s = sanitizeShellString(url, true);

...

exec(cmd + args);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function sanitizeShellString(str, strict = false) {
const s = str || '';
let result = '';
for (let i = 0; i <= mathMin(s.length, 2000); i++) {
if (!(s[i] === undefined ||
s[i] === '>' ||
s[i] === '<' ||
s[i] === '*' ||
s[i] === '?' ||
s[i] === '[' ||
s[i] === ']' ||
s[i] === '|' ||
s[i] === '˚' ||
s[i] === '$' ||
s[i] === ';' ||
s[i] === '&' ||
s[i] === '(' ||
s[i] === ')' ||
s[i] === ']' ||
s[i] === '#' ||
s[i] === '\\' ||
s[i] === '\t' ||
s[i] === '\n' ||
s[i] === '\'' ||
s[i] === '`' ||
s[i] === '"' ||
s[i].length > 1 ||
(strict && s[i] === '@') ||
(strict && s[i] === ' ') ||
(strict && s[i] == '{') ||
(strict && s[i] == ')'))) {
result = result + s[i];
}
}
return result;
}

Based on many bypasses that exist for commands concatenation, the function sanitizeShellString seems to be okay. The big problem with those codes is the url supposed to be a string, but if we send an array ["url"], all verifications on sanitizeShellString will not work correctly. This happens because when the sanitizeShellString function is invoked, the access of s[i] returns the entire string instead of a single character. Thus, when we send the URL ["$(touch hacked)"], the verification of s[i] === '$' means '$(touch hacked)' === '$', which results in false.

This approach can be applied in a scenario in which is possible to send an array instead of a string. ForbiddenProgrammer shows a great example of this approach through the Express.js framework, check it out.

Disclosure & Fix

The report containing the vulnerability description and the fix can be accessed here. The fix only checks if the url provided by the user is a string, as you can see below.

1
2
3
4
if (typeof url !== "string") {
if (callback) { callback(null); }
return resolve(null);
}

This vulnerability was disclosed and fixed by me, and it was assigned as CVE-2021-21315.

Other useful links about this vulnerability:

Bug Bounty

You can notice that all steps to report and fix the vulnerability went through Huntr, which is a platform that pays for vulnerabilities in open-source repositories.

That is a big opportunity for beginners to start with bug bounty programs. Even though you do not have a lot of experience with infosec, you can learn the basics of vulnerabilities and try to find something in any repository on GitHub. Once GitHub has “infinite” repositories, you have real chances to report a valid vulnerability and be rewarded for it.

As you can see on my reported vulnerability, it was a fairly simple exploration. And it was rewarded with $80 dollars.

For further information about Huntr, access the site. Good luck!

References

  1. Command Injection - OWASP

Discuss on Twitter