mirror of
https://github.com/github/codeql.git
synced 2026-04-30 19:26:02 +02:00
Merge pull request #3645 from erik-krogh/infExposure
JS: add query to detect accidential leak of private files
This commit is contained in:
27
javascript/ql/src/Security/CWE-200/PrivateFileExposure.qhelp
Normal file
27
javascript/ql/src/Security/CWE-200/PrivateFileExposure.qhelp
Normal 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>
|
||||
116
javascript/ql/src/Security/CWE-200/PrivateFileExposure.ql
Normal file
116
javascript/ql/src/Security/CWE-200/PrivateFileExposure.ql
Normal 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."
|
||||
@@ -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. |
|
||||
@@ -0,0 +1 @@
|
||||
Security/CWE-200/PrivateFileExposure.ql
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "foo",
|
||||
"dependencies": {
|
||||
"async": "3.2.0"
|
||||
}
|
||||
}
|
||||
11
javascript/ql/test/query-tests/Security/CWE-200/lib/tst.js
Normal file
11
javascript/ql/test/query-tests/Security/CWE-200/lib/tst.js
Normal 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
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user