CVE-2018-3734 (JavaScript)

The stattic module's HTTP static file handler is vulnerable to path traversal. It derives a filesystem path from the attacker-controlled request URL pathname and joins it with the configured static root directory without reliably ensuring that the resulting target remains inside that root. An attacker who knows or can guess a filesystem path can send a URL containing parent-directory traversal segments to cause the server to read and return files outside the configured static content directory. This can disclose arbitrary local file contents through the HTTP response.

Weakness:
CWE-22: name: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
description: The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.

Repository: https://github.com/jmjuanes/stattic

Vulnerable code:
file_path: index.js
snippet: module.exports.listen = function (port, cb) {
    //Parse the arguments
    cb = (typeof port === "function") ? port : cb;
    port = (typeof port === "number") ? parseInt(port) : options.port;

    //Get the static files folder path
    options.folder = path.resolve(process.cwd(), options.folder);

    //Get the error file path
    options.error = path.resolve(process.cwd(), options.error);

    //Initialize the server
    let server = http.createServer(function (req, res) {
        let timeStart = Date.now();

        //Check the cors option
        if (options.cors === true) {
            res.setHeader("Access-Control-Allow-Origin", "*");
            res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE");
            res.setHeader("Access-Control-Allow-Headers", "X-Requested-With,content-type");
        }
        let pathname = url.parse(req.url).pathname;
        let localPath = path.join(options.folder, pathname);
        if (path.extname(localPath) === "") {
            //Add the index file to the local path
            localPath = path.join(localPath, "./" + path.basename(options.index));
        }

        //Reponse finish event
        res.on("finish", function () {
            console.log("" + res.statusCode + "  " + pathname + "  " + (Date.now() - timeStart) + " ms");
        });

        //Check if the file exists in this directory
        return utily.fs.isFile(localPath, function (error, exists) {
            if (error) {
                return errorPage(res, 500, "Error processing your request.");
            }
            if (exists === false) {
                return errorPage(res, 404, "File not found.");
            }

            //Write the header with the content type
            res.writeHead(200, {"Content-Type": mime.getType(localPath)});

            //Initialize the reader stream
            //let reader = fs.createReadStream(local_path, { encoding: "utf8" });
            //Remove encoding -> fixed bug reading images (jpg, png, etc...)
            let reader = fs.createReadStream(localPath);
            reader.on("data", function (data) {
                //Write the data to the response
                res.write(data);
            });
            reader.on("end", function () {
                res.end("");
            });
            reader.on("error", function (error) {
                return errorPage(res, 500, "Something went wrong...");
            })
        });
    });

    //Start server
    server.listen(port, function () {
        if (typeof cb === "function") {
            cb.call(null);
        }
        else {
            //Show the console log success
            console.log("");
            console.log("Static server listening on: " + "http://localhost:" + options.port + "");
            console.log("Reading files from: " + options.folder + "");
            console.log("");
        }
    });
};

Fixed code:
file_path: index.js
snippet: module.exports.listen = function (port, cb) {
    //Parse the arguments
    cb = (typeof port === "function") ? port : cb;
    port = (typeof port === "number") ? parseInt(port) : options.port;

    //Get the static files folder path
    options.folder = path.resolve(process.cwd(), options.folder);

    //Get the error file path
    options.error = path.resolve(process.cwd(), options.error);

    //Initialize the server
    let server = http.createServer(function (req, res) {
        let timeStart = Date.now();

        //Check the cors option
        if (options.cors === true) {
            res.setHeader("Access-Control-Allow-Origin", "*");
            res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE");
            res.setHeader("Access-Control-Allow-Headers", "X-Requested-With,content-type");
        }
        let pathname = url.parse(req.url).pathname;
        pathname = path.normalize(pathname); //Fix path traversal
        let localPath = path.join(options.folder, pathname);
        if (path.extname(localPath) === "") {
            //Add the index file to the local path
            localPath = path.join(localPath, "./" + path.basename(options.index));
        }

        //Reponse finish event
        res.on("finish", function () {
            console.log("" + res.statusCode + "  " + pathname + "  " + (Date.now() - timeStart) + " ms");
        });

        //Check if the file exists in this directory
        return utily.fs.isFile(localPath, function (error, exists) {
            if (error) {
                return errorPage(res, 500, "Error processing your request.");
            }
            if (exists === false) {
                return errorPage(res, 404, "File not found.");
            }

            //Write the header with the content type
            res.writeHead(200, {"Content-Type": mime.getType(localPath)});

            //Initialize the reader stream
            //let reader = fs.createReadStream(local_path, { encoding: "utf8" });
            //Remove encoding -> fixed bug reading images (jpg, png, etc...)
            let reader = fs.createReadStream(localPath);
            reader.on("data", function (data) {
                //Write the data to the response
                res.write(data);
            });
            reader.on("end", function () {
                res.end("");
            });
            reader.on("error", function (error) {
                return errorPage(res, 500, "Something went wrong...");
            })
        });
    });

    //Start server
    server.listen(port, function () {
        if (typeof cb === "function") {
            cb.call(null);
        }
        else {
            //Show the console log success
            console.log("");
            console.log("Static server listening on: " + "http://localhost:" + options.port + "");
            console.log("Reading files from: " + options.folder + "");
            console.log("");
        }
    });
};