add query to detect accidential leak of private files

This commit is contained in:
Erik Krogh Kristensen
2020-06-08 19:54:23 +02:00
parent d2d235d7a4
commit 167239e745
7 changed files with 214 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Placeholder
</p>
</overview>
<recommendation>
<p>
Placeholder
</p>
</recommendation>
<example>
<p>
Placeholder
</p>
</example>
<references>
<li>OWASP: <a href="https://www.owasp.org/index.php/Top_10-2017_A3-Sensitive_Data_Exposure">Sensitive Data Exposure</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,116 @@
/**
* @name Exposure of private files
* @description Exposing a node_modules folder, or the project folder to the public, can cause exposure
* of private information.
* @kind problem
* @problem.severity warning
* @id js/exposure-of-private-files
* @tags security
* external/cwe/cwe-200
* @precision high
*/
import javascript
/**
* Holds if `folder` is a node_modules folder, and at most 1 subdirectory down.
*/
bindingset[folder]
predicate isNodeModuleFolder(string folder) {
folder.regexpMatch("(\\.?\\.?/)*node_modules(/|(/[a-zA-Z@_-]+/?))?")
}
/**
* Get a data-flow node that represents a path to the node_modules folder represented by the string-literal `path`.
*/
DataFlow::Node getANodeModulePath(string path) {
result.getStringValue() = path and
isNodeModuleFolder(path)
or
exists(DataFlow::CallNode resolve |
resolve = DataFlow::moduleMember("path", ["resolve", "join"]).getACall()
|
result = resolve and
resolve.getLastArgument() = getANodeModulePath(path)
)
or
exists(StringOps::ConcatenationRoot root | root = result |
root.getLastLeaf() = getANodeModulePath(path)
)
or
result.getAPredecessor() = getANodeModulePath(path) // local data-flow
or
exists(string base, string folder |
path = base + folder and
folder.regexpMatch("(/)?[a-zA-Z@_-]+/?") and
base.regexpMatch("(\\.?\\.?/)*node_modules(/)?") // node_modules, without any sub-folders.
|
exists(StringOps::ConcatenationRoot root | root = result |
root.getNumOperand() = 2 and
root.getFirstLeaf() = getANodeModulePath(base) and
root.getLastLeaf().getStringValue() = folder
)
or
exists(DataFlow::CallNode resolve |
resolve = DataFlow::moduleMember("path", ["resolve", "join"]).getACall()
|
result = resolve and
resolve.getNumArgument() = 2 and
resolve.getArgument(0) = getANodeModulePath(path) and
resolve.getArgument(1).mayHaveStringValue(folder)
)
)
}
/**
* Gets a folder that contains a `package.json` file.
*/
pragma[noinline]
Folder getAPackageJSONFolder() { result = any(PackageJSON json).getFile().getParentContainer() }
/**
* Gets a reference to `dirname` that might cause information to be leaked.
* That can happen if there is a `package.json` file in the same folder.
* (It is assumed that the presence of a `package.json` file means that a `node_modules` folder can also exist.
*/
DataFlow::Node dirname() {
exists(ModuleScope ms | result.asExpr() = ms.getVariable("__dirname").getAnAccess()) and
result.getFile().getParentContainer() = getAPackageJSONFolder()
or
result.getAPredecessor() = dirname()
or
exists(StringOps::ConcatenationRoot root | root = result |
root.getNumOperand() = 2 and
root.getOperand(0) = dirname() and
root.getOperand(1).getStringValue() = "/"
)
}
/**
* Gets a data-flow node that represents a path to the private folder `path`.
*/
DataFlow::Node getAPrivateFolderPath(string description) {
exists(string path |
result = getANodeModulePath(path) and description = "the folder \"" + path + "\""
)
or
result = dirname() and
description = "the folder " + result.getFile().getParentContainer().getRelativePath()
or
result.getStringValue() = [".", "./"] and
description = "the current working folder"
}
/**
* Gest a call that serves the folder `path` to the public.
*/
DataFlow::CallNode servesAPrivateFolder(string description) {
result = DataFlow::moduleMember("express", "static").getACall() and
result.getArgument(0) = getAPrivateFolderPath(description)
}
from Express::RouteSetup setup, string path
where
setup.isUseCall() and
setup.getArgument([0 .. 1]) = servesAPrivateFolder(path).getEnclosingExpr()
select setup, "Serves " + path + ", which can contain private information."

View File

@@ -0,0 +1,16 @@
| lib/tst.js:7:1:7:45 | app.use ... rname)) | Serves the folder query-tests/Security/CWE-200/lib, which can contain private information. |
| lib/tst.js:9:1:9:43 | app.use ... otDir)) | Serves the folder query-tests/Security/CWE-200/lib, which can contain private information. |
| lib/tst.js:11:1:11:52 | app.use ... + '/')) | Serves the folder query-tests/Security/CWE-200/lib, which can contain private information. |
| private-file-exposure.js:8:1:8:49 | app.use ... ular')) | Serves the folder "./node_modules/angular", which can contain private information. |
| private-file-exposure.js:9:1:9:59 | app.use ... ular')) | Serves the folder "node_modules/angular", which can contain private information. |
| private-file-exposure.js:10:1:10:67 | app.use ... mate')) | Serves the folder "node_modules/angular-animate", which can contain private information. |
| private-file-exposure.js:11:1:11:67 | app.use ... ular')) | Serves the folder "/node_modules/angular", which can contain private information. |
| private-file-exposure.js:12:1:12:78 | app.use ... ute/')) | Serves the folder "/node_modules/angular-route/", which can contain private information. |
| private-file-exposure.js:13:1:13:48 | app.use ... ular')) | Serves the folder "/node_modules/angular", which can contain private information. |
| private-file-exposure.js:14:1:14:84 | app.use ... les'))) | Serves the folder "../node_modules", which can contain private information. |
| private-file-exposure.js:15:1:15:35 | app.use ... ('./')) | Serves the current working folder, which can contain private information. |
| private-file-exposure.js:16:1:16:67 | app.use ... lar/')) | Serves the folder "./node_modules/angular/", which can contain private information. |
| private-file-exposure.js:17:1:17:78 | app.use ... ar/'))) | Serves the folder "./node_modules/angular/", which can contain private information. |
| private-file-exposure.js:18:1:18:74 | app.use ... les"))) | Serves the folder "/node_modules", which can contain private information. |
| private-file-exposure.js:19:1:19:88 | app.use ... lar/')) | Serves the folder "/node_modules/angular/", which can contain private information. |
| private-file-exposure.js:22:1:22:58 | app.use ... lar/')) | Serves the folder "/node_modules/angular/", which can contain private information. |

View File

@@ -0,0 +1 @@
Security/CWE-200/PrivateFileExposure.ql

View File

@@ -0,0 +1,6 @@
{
"name": "foo",
"dependencies": {
"async": "3.2.0"
}
}

View File

@@ -0,0 +1,11 @@
var express = require('express');
var path = require("path");
var app = express();
app.use('basedir', express.static(__dirname)); // BAD
const rootDir = __dirname;
app.use('basedir', express.static(rootDir)); // BAD
app.use('/monthly', express.static(__dirname + '/')); // BAD

View File

@@ -0,0 +1,37 @@
var express = require('express');
var path = require("path");
var app = express();
// Not good.
app.use(express.static('./node_modules/angular'));
app.use('/angular', express.static('node_modules/angular'));
app.use('/animate', express.static('node_modules/angular-animate'));
app.use('/js', express.static(__dirname + '/node_modules/angular'));
app.use('/router', express.static(__dirname + '/node_modules/angular-route/'));
app.use(express.static('/node_modules/angular'));
app.use('/node_modules', express.static(path.resolve(__dirname, '../node_modules')));
app.use('/js',express.static('./'));
app.use('/angular', express.static("./node_modules" + '/angular/'));
app.use('/angular', express.static(path.join("./node_modules" + '/angular/')));
app.use('/angular', express.static(path.join(__dirname, "/node_modules")));
app.use('/angular', express.static(path.join(__dirname, "/node_modules") + '/angular/'));
const rootDir = __dirname;
const nodeDir = path.join(rootDir + "/node_modules");
app.use('/angular', express.static(nodeDir + '/angular/'));
// Good
app.use(express.static('./node_modules/jquery/dist'));
app.use(express.static('./node_modules/bootstrap/dist'));
app.use('/js', express.static(__dirname + '/node_modules/html5sortable/dist'));
app.use('/css', express.static(__dirname + '/css'));
app.use('/favicon.ico', express.static(__dirname + '/favicon.ico'));
app.use(express.static(__dirname + "/static"));
app.use(express.static(__dirname + "/static/js"));
app.use('/docs/api', express.static('docs/api'));
app.use('/js/', express.static('node_modules/bootstrap/dist/js'))
app.use('/css/', express.static('node_modules/font-awesome/css'));
app.use('basedir', express.static(__dirname)); // GOOD, because there is no package.json in the same folder.
app.use('/monthly', express.static(__dirname + '/')); // GOOD, because there is no package.json in the same folder.