[en] Why system(3) is bad.

Many novice programmers frequently abuse the functions system(3) and popen(3). Especially when they want to use some functionality of other executables, they don’t think about them and just fire up system(3) or popen(3). Those functions are abstracted through libc, so they provide more portability than directly using the system calls of a specific OS. However, those benefits don’t come without disadvantages.

system(3) and popen(3)’s cost comes from their implementation. They accept shell commands as an argument and then executes it using system’s shell.

In case of popen(3) in glibc, it is implemented like this: When it is called, it creates a pipe and connects them to standard I/O, then it runs /bin/sh using this code: execl(“/bin/sh”, “sh”, “-c”, argument, (char *)0);1. The executed /bin/sh interprets given argument and runs it as a shell script.

Also, system(3)’s implementation is almost as same as popen(3)’s. Apart from the fact that popen(3) opens a pipe for their standard I/O and system(3) doesn’t, they’re almost identical.2

Now, there are a couple of problems: They call expensive system calls such as fork() and execve(). Also, popen(3) uses pipe(2) for IPC, so they suffer from IPC overhead. Additionally, they interpret their argument as shell scripts using /bin/sh, so they require CPU time for initialization and most importantly, suffer from shell script injection.

Shell script injection allows an attacker to execute remote commands using malicious input. Here comes an example:

Let’s imagine this code:

void add_netfilter_tcp_drop(char *user_src)
{
    char command[1024];
    sprintf(command, "iptables -A INPUT -p tcp -s %s -j DROP", user_src);
    system(command);
}

This code does its work in controlled environments but let’s imagine some attacker puts input like below, using user interface.

WARNING: THIS EXAMPLE IS AN ATTACK SCENARIO. DO NOT EXECUTE ANY COMMAND BELOW THIS WITHOUT UNDERSTANDING. I’M NOT RESPONSIBLE FOR ANY DAMAGE CAUSED BY DOING THAT.

0.0.0.0 -j DROP; rm -rf --no-preserve-root /; #

This input sets command variable like this:

iptables -A INPUT -p tcp -s 0.0.0.0 -j DROP; rm -rf --no-preserve-root /; # -j DROP

… and it executes this command in system(3).

/bin/sh -c "iptables -A INPUT -p tcp -s 0.0.0.0 -j DROP; rm -rf --no-preserve-root /; # -j DROP"

Since the iptables command requires root privilege to operate, this vulnerable function is normally executed as root. So, the attacker’s payload is executed as root and it results in execution of the infamous command rm -rf –no-preserve-root / , which will wipe out the entire system as a result.3

So, I strongly recommend avoiding popen(3) and system(3). Use these as alternatives:

  1. Use a library instead of executable.
  2. If you have to execute an external executable, DO NOT USE popen(3) and system(3) but call fork(2) and execve(2) instead.

Note: this article is translated from a Korean version(Not yet restored from the archive).

Appended at 20150508 02:30

<lifthrasiir> 감솨
<lifthrasiir> 파이썬에는 subprocess라는 좋은 모듈이 있는데요
<lifthrasiir> php에는 없죠
<lifthrasiir> 그리고 모니위키.

MoniWiki (Wiki software written in PHP) uses popen(3) for executing its own VCS and does not escapes user input well. As a result, it allows shell injection.

Special thanks to lifthrasiir, who reminded me about MoniWiki vuln.

Appended at 20150508 13:50
Fixed bug in shell injection explanation.
Thanks to Uni-

Special thanks to /u/bitbait and @mnkai_rin, who helped with the translation.

  1. GNU libc v2.21 libio/iopopen.c 225L
  2. GNU libc v2.21 sysdeps/posix/system.c 136L
  3. Lots of Korean home routers are built with spaghetti code which I call (sarcastically) as C-shell hybrid. I’ll cover them in other posts.

Leave a Reply

Your email address will not be published. Required fields are marked *