How a simple-httpd Vulnerability Slipped In

Over Thanksgiving weekend I discovered a vulnerability in my recent simple-httpd overhaul. I fixed it immediately and pushed out the patch. Despite being careful about the translation of request paths to filesystems paths, one thing slipped by. Here’s how.

When writing my original web server a couple of years ago I established a global variable, httpd-root, which is the location on the filesystem from where all files are served. Nothing above this directory should be visible to clients in any way. The simple, dangerous way to do this is with a plain concat.

(concat httpd-root request-path)

The vulnerability here is that request-path could contain .., a reference to the parent directory. This would allow a request to access anything on the filesystem. This was obvious to me at the time, so I wrote a httpd-clean-path to remove any .. portions in the request. As long as httpd-root isn’t an empty string, this closes all the holes.

(concat httpd-root (httpd-clean-path request-path))

A couple of years later after, I’ve honed my Emacs Lisp skill, I go through refactoring everything. I’ve since learned about the function expand-file-name and when I see concat being used to build a path, I change it to this function, which more appropriate for the job. This happened in commit 3b405343 (2012-08-07). I’m using httpd-clean-path to handle everything dangerous, so it’s safe, right?!

(expand-file-name (httpd-clean-path request-path) httpd-root)

When the path being expanded by expand-file-name is an absolute path, that path is returned directly, ignoring the second argument. Unfortunately, anything beginning with ~ is an absolute path, because this automatically expands into a home directory. With concat this didn’t need to be handled because any ~ was always prepended with httpd-root. Now that I was using expand-file-name, this allowed everyone read-access to everything in the hosting user’s home directory if the request path started the request path with a ~. Doh!

(expand-file-name "~foo" "/etc")  ; => "/home/foo"

The fix is dead simple: prefix the cleaned path with a ./ before using expand-file-name. This forces the path to be relative so that it’s expanded properly.

(expand-file-name (concat "./" "~foo") "/etc")  ; => "/etc/~foo"

I apologize to anyone using simple-httpd. The fix has been on MELPA for about a month now, so make sure you’re updated. I myself have a long-running simple-httpd instance exposed to the Internet (impatient-mode makes for a wonderful pastebin!), and when I found this my stomach sunk in panic. I do keep an eye on my *httpd* log and I never saw anyone exploit this. Due to simple-httpd’s obscurity I’m pretty sure no one else discovered this anyway. The vulnerability only existed for about three months before I caught it. If you are able to find any other vulnerability please tell me!

Have a comment on this article? Start a discussion in my public inbox by sending an email to ~skeeto/public-inbox@lists.sr.ht [mailing list etiquette] , or see existing discussions.

null program

Chris Wellons

wellons@nullprogram.com (PGP)
~skeeto/public-inbox@lists.sr.ht (view)