mario
mario

Reputation: 395

NodeJS spawn does not escape bad strings

I want to download a url in a remote host using ssh, i was using exec(), it was working:

const cmd = `mkdir -p /home/username/test; wget --no-check-certificate -q -U \"\" -c \"${url}\" -O /home/username/test/img.jpg`;
const out = execSync(`ssh -o ConnectTimeout=8 -o StrictHostKeyChecking=no -p 2356 username@${ip} '${cmd}'`);

But it's usafe to use the url variable this way, the value of this variable is from user input, so i found some posts on stackoverflow saying that i need to use spawn:

const url = 'https://example.com/image.jpg';

const out = spawnSync('ssh', [
    '-o', 'ConnectTimeout=8',
    '-o', 'StrictHostKeyChecking=no',
    '-p', '2356',
    `username@${ip}`,
    `mkdir -p /home/username/test; wget --no-check-certificate -q -U "" -c "${url}" -O /home/username/test/img.jpg`,
]);

What about if const url = 'https://example.com/image.jpg"; echo 5; "';, the echo will be executed, could someone tell me how to execute this code in a safe way?

Upvotes: 1

Views: 507

Answers (1)

Philipp Claßen
Philipp Claßen

Reputation: 43979

There are two aspect. First, you are correct that execSync is unsafe. To quote from the documentation:

Never pass unsanitized user input to this function. Any input containing shell metacharacters may be used to trigger arbitrary command execution.

A solution is to use execSpawn, as you pointed out, for example, like in this related answer.

However, in your example, you are calling ssh and passing it a text, which will be executed by the shell on the remote system. Because of that, you are still vulnerable to the attack, as you showed in the example. But note that it is no longer a NodeJs related exploit, but an exploit on ssh and the shell.

To mitigate the attack, I would recommend to concentrate on the remote server. Instead of sending it a command over ssh, which it should trust and execute in the shell, you could provide a clear defined API from the server. In the HTTP interface, you can accept input and do a proper validation (instead of simplify trusting it). The advantage is that you do not need to deal with the subtleties of the shell.

If you are forced to work with ssh, you could validate the URL and only if it is safe, forward it to the server. It is still not ideal from a security perspective. First, the remote server will need to trust you completely (often it is better to avoid that and instead validate as locally as possible). Second, the validation itself is not straightforward. You will need to decide if a string looks like an URL (e.g. by using new URL(url)), but the more difficult aspect is to make sure that no exploits slip through. I don't have a concrete example, but I would be cautious to assume all strings that pass the URL parser will be safe to execute in a shell environment.

In summary, if possible try to avoid ssh with passing shell command in that situation (input data controlled by an attacker). Instead prefer a normal API like a HTTP interface (or other text or binary protocols). If it is not possible, try hard to sanitize the data before sending it out. Maybe you know in advance how a URL will look like (e.g. the list of allowed hostnames, allowed paths, etc). But realize that there might be hidden examples that you will overlook, and never underestimate the creativity of an attacker.

Upvotes: 1

Related Questions