CVE-2018-16482 (JavaScript)

mcstatic's static file serving logic is vulnerable to directory traversal when it derives a filesystem path from the client-controlled URL path and normalizes it after joining it with the configured static root. An attacker can craft a request path containing slash-separated parent-directory traversal segments, such as slash-prefixed ../ components appended in the URL, causing the resolved path to escape the server's intended root directory. This can allow disclosure of arbitrary readable files outside the directory that mcstatic is supposed to serve.

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/tjchaplin/mcstatic

Vulnerable code:
file_path: lib/staticFileHandler.js
snippet:     return function(req,res){
        var root = options.root;
        var mockPaths = getMockPathForMethod(options,req.method);

        var filePath = httpHelpers.getRequestPathFromUrl(req.url);
        var mockedFilePath = findMockFilePath(filePath,mockPaths);
        if(mockedFilePath)
            filePath = mockedFilePath;

        var file = path.normalize(path.join(root,filePath));
        fs.stat(file,function(error, stats){
            if(error)
                return statusHandlers[500](res, nextHandler, { error: error });

            if(req.method === 'HEAD')
                return responseHandlers.handleHead(req,res);
            
            httpHelpers.setContentType(res,file);
            if (req.headers && req.headers['range'])
                return responseHandlers.handleRange(req, file, stats);

            if(!req.method)
                return statusHandlers[500](res, nextHandler, { error: 'Method not handled' });

            if (req.method === 'GET')
                return responseHandlers.handleGet(res,file, stats, nextHandler);
            if (req.method === 'POST')
                return responseHandlers.handlePost(res,file, stats, nextHandler);
            if (req.method === 'PUT')
                return responseHandlers.handlePut(res,file, stats, nextHandler);
            if (req.method === 'DELETE')
                return responseHandlers.handleDelete(res, file, stats, nextHandler);
        });
    };

Fixed code:
file_path: lib/staticFileHandler.js
snippet: module.exports = function staticFileListener(options, nextHandler){
    assert(options.root,'A root must be defined');
    assert(options.getMockPaths,'A get mock path object must be defined');
    assert(options.putMockPaths,'A put mock path object must be defined');
    assert(options.postMockPaths,'A post mock path object must be defined');
    assert(options.deleteMockPaths,'A delete mock path object must be defined');

    return function(req,res){
        var root = options.root;
        var mockPaths = getMockPathForMethod(options,req.method);
        //fixing path traversal issue - CVE-2018-16482
        var filePath = httpHelpers.getRequestPathFromUrl(path.normalize(req.url));
        var mockedFilePath = findMockFilePath(filePath,mockPaths);
        if(mockedFilePath)
            filePath = mockedFilePath;

        var file = path.normalize(path.join(root,filePath));
        fs.stat(file,function(error, stats){
            if(error)
                return statusHandlers[500](res, nextHandler, { error: error });

            if(req.method === 'HEAD')
                return responseHandlers.handleHead(req,res);
            
            httpHelpers.setContentType(res,file);
            if (req.headers && req.headers['range'])
                return responseHandlers.handleRange(req, file, stats);

            if(!req.method)
                return statusHandlers[500](res, nextHandler, { error: 'Method not handled' });

            if (req.method === 'GET')
                return responseHandlers.handleGet(res,file, stats, nextHandler);
            if (req.method === 'POST')
                return responseHandlers.handlePost(res,file, stats, nextHandler);
            if (req.method === 'PUT')
                return responseHandlers.handlePut(res,file, stats, nextHandler);
            if (req.method === 'DELETE')
                return responseHandlers.handleDelete(res, file, stats, nextHandler);
        });
    };
};