CVE-2017-16198 (JavaScript)

ritp is a static web server. ritp is vulnerable to a directory traversal issue whereby an attacker can gain access to the file system by placing ../ in the URL. Access is restricted to files with a file extension, so files such as /etc/passwd are not accessible.

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/hardog/ritp

Vulnerable code:
file_path: index.js
snippet: var main = function(req, res) {
        var reqUrl = req.url; 
        //\u8bb0\u5f55\u8bf7\u6c42\u4fe1\u606f
        logger.http(req, res);
        
        //\u4f7f\u7528url\u89e3\u6790\u6a21\u5757\u83b7\u53d6url\u4e2d\u7684\u8def\u5f84\u540d 
        var pathName = url.parse(reqUrl).pathname;

        //\u8865\u5168\u6587\u4ef6\u8def\u5f84
        if (path.extname(pathName) == "") {

            if(!/\/$/.test(pathName)){
                pathName += '/';
            }

            pathName += argvs.getDefault();
        }

        //\u4f7f\u7528\u8def\u5f84\u89e3\u6790\u6a21\u5757,\u7ec4\u88c5\u5b9e\u9645\u6587\u4ef6\u8def\u5f84 
        var filePath = path.join(argvs.getPath(), pathName);

        //\u5224\u65ad\u6587\u4ef6\u662f\u5426\u5b58\u5728 
        fs.exists(filePath, function(exists) {
            try{
                if (exists) {
                    //\u5728\u8fd4\u56de\u5934\u4e2d\u5199\u5165\u5185\u5bb9\u7c7b\u578b 
                    res.writeHead(200, {
                        "Content-Type": mime.lookup(filePath)
                    });

                    //\u521b\u5efa\u53ea\u8bfb\u6d41\u7528\u4e8e\u8fd4\u56de 
                    var stream = fs.createReadStream(filePath, {
                        flags: "r",
                        encoding: null
                    });

                    //\u6307\u5b9a\u5982\u679c\u6d41\u8bfb\u53d6\u9519\u8bef,\u8fd4\u56de404\u9519\u8bef 
                    stream.on("error", function() {
                        res.writeHead(404);
                        res.end('<h1>500 the file [path=' + filePath +'] Read Error!</h1>');
                    });
                    //\u8fde\u63a5\u6587\u4ef6\u6d41\u548chttp\u8fd4\u56de\u6d41\u7684\u7ba1\u9053,\u7528\u4e8e\u8fd4\u56de\u5b9e\u9645Web\u5185\u5bb9 
                    stream.pipe(res);
                } else {
                    //\u8fd4\u56de404\u9519\u8bef 
                    res.writeHead(404, {
                        "Content-Type": "text/html"
                    });
                    res.end('<h1>404 Not Found file [path=' + filePath + ']</h1>');
                }
            }catch(e){
                logger.error('read file from [' + filePath + '] error!');
            }
        });
    }

Fixed code:
file_path: index.js
snippet: var main = function(req, res) {
        var reqUrl = req.url.replace(/(\.\.\/?)/g, '');
        //\u8bb0\u5f55\u8bf7\u6c42\u4fe1\u606f
        logger.http(req, res);
        
        //\u4f7f\u7528url\u89e3\u6790\u6a21\u5757\u83b7\u53d6url\u4e2d\u7684\u8def\u5f84\u540d 
        var pathName = url.parse(reqUrl).pathname;

        //\u8865\u5168\u6587\u4ef6\u8def\u5f84
        if (path.extname(pathName) == "") {

            if(!/\/$/.test(pathName)){
                pathName += '/';
            }

            pathName += argvs.getDefault();
        }

        //\u4f7f\u7528\u8def\u5f84\u89e3\u6790\u6a21\u5757,\u7ec4\u88c5\u5b9e\u9645\u6587\u4ef6\u8def\u5f84 
        var filePath = path.join(argvs.getPath(), pathName);

        //\u5224\u65ad\u6587\u4ef6\u662f\u5426\u5b58\u5728 
        fs.exists(filePath, function(exists) {
            try{
                if (exists) {
                    //\u5728\u8fd4\u56de\u5934\u4e2d\u5199\u5165\u5185\u5bb9\u7c7b\u578b 
                    res.writeHead(200, {
                        "Content-Type": mime.lookup(filePath)
                    });

                    //\u521b\u5efa\u53ea\u8bfb\u6d41\u7528\u4e8e\u8fd4\u56de 
                    var stream = fs.createReadStream(filePath, {
                        flags: "r",
                        encoding: null
                    });

                    //\u6307\u5b9a\u5982\u679c\u6d41\u8bfb\u53d6\u9519\u8bef,\u8fd4\u56de404\u9519\u8bef 
                    stream.on("error", function() {
                        res.writeHead(404);
                        res.end('<h1>500 the file [path=' + filePath +'] Read Error!</h1>');
                    });
                    //\u8fde\u63a5\u6587\u4ef6\u6d41\u548chttp\u8fd4\u56de\u6d41\u7684\u7ba1\u9053,\u7528\u4e8e\u8fd4\u56de\u5b9e\u9645Web\u5185\u5bb9 
                    stream.pipe(res);
                } else {
                    //\u8fd4\u56de404\u9519\u8bef 
                    res.writeHead(404, {
                        "Content-Type": "text/html"
                    });
                    res.end('<h1>404 Not Found file [path=' + filePath + ']</h1>');
                }
            }catch(e){
                logger.error('read file from [' + filePath + '] error!');
            }
        });
    }