No spaces? Challenge accepted!

TL;DR

A contractor coworker - let’s call him Jim - built an application that accepts the user’s URL and uses that to run another program with child_process.exec. Jim forgot to sanitize the URL, so I was able to abuse it and get RCE. Jim then fixed the issue by validating URLs with a regex that does not allow spaces, greater than and less than symbols, curly braces, and more. I found a way around it with the IFS shell variable and single quotes: https://www.google.com/;A=$IFS;touch$A'omg.txt';

Intro

What is RCE?

Remote Code Execution is a type of vulnerability or exploit in which an attacker can execute arbitrary code on a target system from a remote location.

Disclaimer

I’m not a cyber security professional. Most of my experience is with software (backend) development. When it comes to cyber security, I only played some Tryhackme and Hack The Box for fun. That said, this might not be something innovative, but I found it interesting enough to share and hopefully someone will stumble upon this and learn something new.

The discovery

It was a beautiful and sunny spring day, and I was helping deploy a NodeJS application that a contractor coworker - Let’s call him Jim - was developing. The application accepted the user’s URL and ran some diagnostics on it. The deployment was done, but the app and its backend didn’t work exactly how they should. Being curious and eager to help, I went spelunking.

I knew the application spawns other processes, and I remembered I saw many zombie processes the other day when I was checking the deployment, so I started there. After poking around for a while, I found something interesting. A call to exec. Surely, it doesn’t use any user input, right? Right? It did. But, the user input is validated, right? Right? It wasn’t. It passed the URL unsanitized as a parameter to another command that exec was running.

let commandLine = `url-diagnostics-app `;
...
commandLine += `--url=${url} `;

The first thing that came to mind was… RCE!

So I had to try it, and lo and behold, it worked. I added ; to the URL to “split” the command in two and then executed my command to create an empty file on the filesystem as a proof of concept. I had to terminate the URL with another ; to cut off the rest of the original parameters.

The fix

I showed this to Jim, who quickly fixed the issue—or so we thought. The fix was a regex to validate URLs and prevent special characters like spaces, percentage signs, less than and greater than signs, and others from being valid. Well, now my normal commands with spaces won’t work anymore.

Challenge accepted!

The bypass

So, I searched for ways to write commands without spaces and found some, but they included curly braces, which the validation also excluded. I opened the shell and started trying things. After messing around for a while with the IFS shell variable, I was able to make it work together with single quotes like this: A=$IFS;touch$A'omg.txt'.

Now that this worked, it was time to figure out what the servers were running to spawn a reverse shell. On our development server, I was first able to use this technique to install netcat and then use it again to spawn a reverse shell with netcat. I knew Jim was running this application on his server also, so I tried it there but couldn’t get it to work. But after we talked a bit more about it, he showed me a list of packages he had installed on that server, and something caught my eye. BusyBox! I tried the reverse shell snippet for BusyBox, and… I worked!

Lesson learned

Now, it’s easy to think that this shouldn’t happen or doesn’t happen to senior developers or more capable developers. Still, the reality is that this happens more often than we think to developers of all kinds of seniority levels. We introduced better, proper fixes, including better validation, using spawn instead of exec and called it a “lesson learned”.

P.S.

I found out later, after reading more about Linux commands without spaces on Unix & Linux StackExchange, that this one should also work: IFS=:;A=touch:omg.wtf;$A

Read more