Tl;dr: I found a privilege escalation 0day (CVE-2019-12181) in the Serv-U FTP Server through command injection. POC code available here

Target

I searched for a program that isn’t too niche and market specific that it hasn’t had time to develop its security. Yet, I didn’t want to commit long months of research to find a vulnerability in an extremely popular program that has already been reviewed by many security researchers. I came across Serv-U FTP Server from shodan and decided to pursue this target after seeing the respectable number of over 168,000 instances running worldwide exposed to the Internet.

As its name suggests, Serv-U FTP Server is an FTP server; but it also has a web interface for easy file management and a web admin interface. Serv-U is available both for Linux and Windows. On Linux, the ftp server is a SUID executable and runs as root. Therefore, even an attack that can only be executed locally is still a threat as it will give the attacker root privileges. This is called a privilege escalation attack.

Technical Vulnerability Background

Some technical background on two specific C functions: main() and execve() is needed to understand and exploit this vulnerability. If you are already familiar with these functions you can skip ahead.

Main() Function Signature

C Main possible signatures
C Main Possible Signatures

According to the gnu libc manual, there are 3 ways to declare the main function.

  1. int main
  2. int main(int argc, char *argv[])
  3. int main(int argc, char *argv[], char *envp[])

When there is an argc parameter, it holds the amount of parameters in the argv parameter passed.

When there is an argv parameter, it points to an array of C strings. Each string is a command line parameter passed to the program. The last element of the string array is NULL. For example, cat a.txt b.txt will result in argv holding[“cat”, “a.txt”, “b.txt”, NULL].

When there is an envp parameter, it points to an array of C strings as well. Each string is an environment variable. The environment variables are described with a string in the template “key=value” where “key” is the name of the environment variable with the value “value”. The last element of the array is NULL.

Experiment with this code to see parameters passed to main.

Exec*() Family Functions

Exec* family function signatures
Exec* family function signatures

The functions in the exec* family allow you to execute a file; optionally, with custom parameters that are passed to the executed program’s main() function. In fact, with these functions you can even set the first argument of argv that is passed to the new process’ main (which you can’t do from the command line). These functions also have the ability to invoke SUID binaries.

Those last two facts will come in handy later ;).

Vulnerability Hunting Methodology

When I start searching for vulnerabilities in a new program, the first thing I do is look for low hanging fruits. I look for calls to specific functions that developers tend to misuse - functions that require programmers to manually check the parameters they pass because these functions won’t do safety checks themselves.

Some dangerous functions I look for:

  • Unsafe string functions - These functions don’t accept a length parameter and therefore require manual bounds checking on the length of the parameters passed. This bounds check can be easily forgotten by a programmer. Examples of unsafe string functions:
    • strcpy
    • strcat
    • sprintf (especially when used with %s)
    • scanf (especially when used with %s)
  • Command execution functions - If these functions are passed strings that can even be partially controlled by a malicious user, then that’s a recipe for disaster.
    • system
    • popen
    • exec* family functions

Since my research target (Serv-U) is closed source, in order to look for calls to the above mentioned dangerous functions, I opened up my favorite reverse engineering platform, IDA Pro, and got to work.

While scanning for the use of vulnerable functions, the fact that the executable calls system four different times(!) caught my eye and led me to focus on that. For comparison, secure programs will usually only use this function once, if at all.

IDA shows 4 references to system
IDA shows 4 references to system

The Vulnerability

Going over the system() calls in the code, I reached a call to system() that looked like through control of the filename_of_exectuable parameter we could inject our own string into the parameter going to system! See picture below:

vulnerable call to system code injection
Call to system highlighted \(Line 13\) \[view from IDA Pro decompiler\]

After reversing, debugging, and taking educated guesses, I came to the conclusion that the command passed to system is chmod u+s <some filename> where <some filename> is set to argv[0]! This is dangerous since if we manage to reach this vulnerable flow, we can control argv[0] with one of the exec* functions \(discussed earlier\) and achieve code execution! Furthermore, since the executable is SUID, even when we run it from exec* functions it will run as root, giving any attacking user root privileges.

After setting a breakpoint on the vulnerable function and normally starting the FTP server, I was disappointed to discover that the breakpoint wasn’t hit: Meaning this function isn’t called by default. I needed to reverse some more to learn how we can trigger the discovered vulnerable program flow.

I looked at who was calling our vulnerable function:

Call to the discovered vulnerable function highlighted in line 74 [view from IDA Pro decompiler]

Looking at the decompiled code we can assume that when some condition on line 53 (highlighted) is met, our wanted function on line 74 (highlighted) will be called. The condition on line 53 looks like a string comparison, and one of the strings being compared looks like a command line flag. Starting the FTP Server with the “-prepareinstallation” command line flag resulted in the program calling the vulnerable function!

Exploitation

Since this is a shell injection vulnerability, exploitation is trivial: all we need to do is escape the current command and append a command of our own to be executed.

vulnerable function with injected string highlighted
Format string our text is injected into is highlighted (Line 12) [view from IDA Pro decompiler]

As seen on line 12 in the decompiled code, the variable filename_of_executable that we control is inserted into the string chmod u+s "*%s*". Since our string is inserted instead of the %s, which is in the middle of a different string, we need to isolate our command with separators at the beginning and end.

Original string passed to system() without vulnerability

system(‘chmod u+s “/usr/local/Serv-U/Serv-U’)

Key:

red text is junk around our string we can’t control that is passed into the system() function

green text is the string we control

We are going to escape the string that we are inserted into (the red text) by only changing our controllable variable (the green text). We begin by closing the opened quotes in the red text with a ‘”’ and then inserting a command separator, which is ‘;’ on Linux. To end our command and close the quote at the very end in the red text, we will end our inserted string with a ‘;’ and ‘”’.

Such that to execute /bin/sh, we will set our controlled string to be: “; /bin/sh/ ;”. The string passed to system() will be as shown below.

Malicious string passed to system() executing /bin/sh

system(‘chmod u+s “”; /bin/sh ;”’)

Key:

red text is junk around our string we can’t control that is passed into the system() function

green text is the string we control

Notice in the display above that all quotes that have been opened are closed, and our command is elegantly separated and thus independently executed because of the ‘;’.

Working exploit code here.

Link to Serv-U release notes with fix here, and link to vulnerability summary and resolution here.

Feel free to leave a comment or DM me with any questions you have :).

Thank yous

Speical thanks to @yoavalon and @B_H101 for helping me with reviewing and editing this post!

Also, thank you to the good folks at psirt@solarwinds.com for the quick and professional handling of my vulnerability disclosure.

Follow my twitter @whtaguy for my latest vulnerability research.