nullprogram.com/blog/2012/12/18/
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!