Arbitrary File Deletion as Root in Webmin

A vulnerability exists in Webmin <= 1.680 (CVE-2014-2952) that allows authenticated users to delete arbitrary files on the host server as root. The problem exists in the cron module, specifically in creating a new environment variable (System > Scheduled Cron Jobs > Create a new environment variable), in the “user” parameter. Here’s the normal request to create an environment variable:

Screen Shot 2014-09-09 at 10.45.52

Using directory traversal and null byte injection techniques we’re able to force webmin to delete any file on the filesystem. Modifying our request a bit:

Screen Shot 2014-09-09 at 10.48.36

..and the response:

Screen Shot 2014-09-09 at 10.48.58

We get an error, but this request also immediately deletes /etc/passwd on the server. Why? Let’s take a look at the relevant code in /usr/share/webmin/cron/save_env.cgi:

Screen Shot 2014-09-08 at 17.16.55

We get a list of files in the cron directory, then call lock_file() on each file. Note that the filename is constructed from the value of the user parameter then passed unsanitized to lock_file(). The purpose of lock_file(), defined in web-lib-funcs.pl, is to create a file of the form /path/to/filename.lock to indicate the file is in use. We can see the lock file being created on line 5202 of /usr/share/webmin/web-lib-funcs.pl:

Screen Shot 2014-09-09 at 10.16.32

Looking further down lock_file():

Screen Shot 2014-09-09 at 10.31.41

On line 5231 it’s storing the filename in locked_file_list, and on 5232 it gets added to temporary_files as well. These data structures come into play when webmin throws the error we saw earlier. Taking a look at the end of the error() function in web-lib-funcs.pl, we see:

Screen Shot 2014-09-09 at 11.05.31

First let’s take a look at unlock_all_files():

Screen Shot 2014-09-09 at 11.08.11

It iterates through locked_file_list calling unlock_file() on each filename. Now on to unlock_file():

Screen Shot 2014-09-09 at 11.09.52

Line 5283 calls unlink() on the filename with “.lock” appended, to delete the lock file. However since we injected a null byte at the end of the filename, everything after the %00 gets ignored. This means the real file, ../../../../etc/passwd, gets passed to unlink() and deleted.

The damage is already done but even if the unlock_all_files() call were not present cleanup_tempnames() would do the same job. Let’s take a look:

Screen Shot 2014-09-09 at 11.16.01

Again we’re iterating through the list of files, in this case temporary_files, and calling unlink on the file. Even though the filenames stored in temporary_files were added with “.lock” appended, our null byte knocked that off and stored ../../../../etc/passwd instead, which of course causes /etc/passwd to be deleted.


Timeline:

2014-05-14: UT ISO reports the vulnerability to Webmin developer Jamie Cameron as well as CERT. CERT reserves CVE-2014-2952 for the vulnerability.
2014-05-14: Jamie patches Webmin on github.
2014-05-20: Webmin 1.690 is released with the fix.
2014-09-09: UT ISO publishes this writeup.

jgor

UT Austin ISO Blog

The University of Texas at Austin Information Security Office shares research and musings with the infosec community via this blog. Stay tuned.