Important: this vulnerability applies to 2 modules which share the same code but different npm packages: @nubosoftware/node-static
and node-static
and so a CVE should be assigned to both.
@nubosoftware/node-static
is a local HTTP file server, or as is it describes itself: a simple, rfc 2616 compliant file streaming module for node
.
Observation:
- It's at virtually 0 downloads
- It was last published 6 months ago (referring to @nubosoftware/node-static)
Resources:
- Project's GitHub source code: https://github.com/cloudhead/node-static
- Project's npm package: https://www.npmjs.com/package/@nubosoftware/node-static
The static-dev-server
npm package is joining paths from users to the root directory
and asset for the path accessed being relative to that of the root directory,
however this last case is where this is implemented in a vulnerable way:
Lines 160-163 in lib/node-static.js
make an attempt to ensure the path is within the constraint root directory, however this logic of startsWith()
is
flawed.
// Make sure we're not trying to access a
// file outside of the root.
if (pathname.startsWith(that.root)) {
tryStat(pathname, function (e, stat) {
This vulnerability should probably be classified as a CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').
- Install the latest version of
@nubosoftware/node-static
:npm install @nubosoftware/node-static@0.7.11
- Make sure you have a
public/
directory with files in it - Make sure you have a
public-isprivate
directory with files in it - Make sure you have a
private/
directory with files in it
All directories above should share the same relative parent, meaning the directory structure should look as follows:
.
├── private
│ └── index.html
├── public
│ └── index.html
└── public-isprivate
└── index.html
ca
Then, run a server powered by @nubosoftware/node-static
as follows:
var static = require('@nubosoftware/node-static');
//
// Create a node-static server instance to serve the './public' folder
//
var file = new static.Server('./public');
require('http').createServer(function (request, response) {
request.addListener('end', function () {
//
// Serve files!
//
file.serve(request, response);
}).resume();
}).listen(3000);
which sets the public root directory to the public/
directory that we previously created:
The server should run within the local folder where all private/
, public/
,
and public-isprivate
are subfolders.
Next, verify the following:
curl --path-as-is "http://localhost:3000/../private/index.html"
-> this request is denied, as expected with prior vulnerability fix.curl --path-as-is "http://localhost:3000/../public/index.html"
-> this request is allowed, as expected with the functionality of this local http servercurl --path-as-is "http://localhost:3000/../public-isprivate/index.html"
-> this request SHOULD BE DENIED because it is outside thepublic/
folder, but it is actually allowed.
Case (3) shouldn't happen, but it does, due to an improper fix in the library's source code.
Liran Tal