Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b6e0e66

Browse files
authored
Merge pull request #3645 from erik-krogh/infExposure
JS: add query to detect accidential leak of private files
2 parents c580ada + 167239e commit b6e0e66

7 files changed

Lines changed: 214 additions & 0 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
6+
<overview>
7+
<p>
8+
Placeholder
9+
</p>
10+
</overview>
11+
12+
<recommendation>
13+
<p>
14+
Placeholder
15+
</p>
16+
</recommendation>
17+
18+
<example>
19+
<p>
20+
Placeholder
21+
</p>
22+
</example>
23+
24+
<references>
25+
<li>OWASP: <a href="https://www.owasp.org/index.php/Top_10-2017_A3-Sensitive_Data_Exposure">Sensitive Data Exposure</a>.</li>
26+
</references>
27+
</qhelp>
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/**
2+
* @name Exposure of private files
3+
* @description Exposing a node_modules folder, or the project folder to the public, can cause exposure
4+
* of private information.
5+
* @kind problem
6+
* @problem.severity warning
7+
* @id js/exposure-of-private-files
8+
* @tags security
9+
* external/cwe/cwe-200
10+
* @precision high
11+
*/
12+
13+
import javascript
14+
15+
/**
16+
* Holds if `folder` is a node_modules folder, and at most 1 subdirectory down.
17+
*/
18+
bindingset[folder]
19+
predicate isNodeModuleFolder(string folder) {
20+
folder.regexpMatch("(\\.?\\.?/)*node_modules(/|(/[a-zA-Z@_-]+/?))?")
21+
}
22+
23+
/**
24+
* Get a data-flow node that represents a path to the node_modules folder represented by the string-literal `path`.
25+
*/
26+
DataFlow::Node getANodeModulePath(string path) {
27+
result.getStringValue() = path and
28+
isNodeModuleFolder(path)
29+
or
30+
exists(DataFlow::CallNode resolve |
31+
resolve = DataFlow::moduleMember("path", ["resolve", "join"]).getACall()
32+
|
33+
result = resolve and
34+
resolve.getLastArgument() = getANodeModulePath(path)
35+
)
36+
or
37+
exists(StringOps::ConcatenationRoot root | root = result |
38+
root.getLastLeaf() = getANodeModulePath(path)
39+
)
40+
or
41+
result.getAPredecessor() = getANodeModulePath(path) // local data-flow
42+
or
43+
exists(string base, string folder |
44+
path = base + folder and
45+
folder.regexpMatch("(/)?[a-zA-Z@_-]+/?") and
46+
base.regexpMatch("(\\.?\\.?/)*node_modules(/)?") // node_modules, without any sub-folders.
47+
|
48+
exists(StringOps::ConcatenationRoot root | root = result |
49+
root.getNumOperand() = 2 and
50+
root.getFirstLeaf() = getANodeModulePath(base) and
51+
root.getLastLeaf().getStringValue() = folder
52+
)
53+
or
54+
exists(DataFlow::CallNode resolve |
55+
resolve = DataFlow::moduleMember("path", ["resolve", "join"]).getACall()
56+
|
57+
result = resolve and
58+
resolve.getNumArgument() = 2 and
59+
resolve.getArgument(0) = getANodeModulePath(path) and
60+
resolve.getArgument(1).mayHaveStringValue(folder)
61+
)
62+
)
63+
}
64+
65+
/**
66+
* Gets a folder that contains a `package.json` file.
67+
*/
68+
pragma[noinline]
69+
Folder getAPackageJSONFolder() { result = any(PackageJSON json).getFile().getParentContainer() }
70+
71+
/**
72+
* Gets a reference to `dirname` that might cause information to be leaked.
73+
* That can happen if there is a `package.json` file in the same folder.
74+
* (It is assumed that the presence of a `package.json` file means that a `node_modules` folder can also exist.
75+
*/
76+
DataFlow::Node dirname() {
77+
exists(ModuleScope ms | result.asExpr() = ms.getVariable("__dirname").getAnAccess()) and
78+
result.getFile().getParentContainer() = getAPackageJSONFolder()
79+
or
80+
result.getAPredecessor() = dirname()
81+
or
82+
exists(StringOps::ConcatenationRoot root | root = result |
83+
root.getNumOperand() = 2 and
84+
root.getOperand(0) = dirname() and
85+
root.getOperand(1).getStringValue() = "/"
86+
)
87+
}
88+
89+
/**
90+
* Gets a data-flow node that represents a path to the private folder `path`.
91+
*/
92+
DataFlow::Node getAPrivateFolderPath(string description) {
93+
exists(string path |
94+
result = getANodeModulePath(path) and description = "the folder \"" + path + "\""
95+
)
96+
or
97+
result = dirname() and
98+
description = "the folder " + result.getFile().getParentContainer().getRelativePath()
99+
or
100+
result.getStringValue() = [".", "./"] and
101+
description = "the current working folder"
102+
}
103+
104+
/**
105+
* Gest a call that serves the folder `path` to the public.
106+
*/
107+
DataFlow::CallNode servesAPrivateFolder(string description) {
108+
result = DataFlow::moduleMember("express", "static").getACall() and
109+
result.getArgument(0) = getAPrivateFolderPath(description)
110+
}
111+
112+
from Express::RouteSetup setup, string path
113+
where
114+
setup.isUseCall() and
115+
setup.getArgument([0 .. 1]) = servesAPrivateFolder(path).getEnclosingExpr()
116+
select setup, "Serves " + path + ", which can contain private information."
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
| lib/tst.js:7:1:7:45 | app.use ... rname)) | Serves the folder query-tests/Security/CWE-200/lib, which can contain private information. |
2+
| lib/tst.js:9:1:9:43 | app.use ... otDir)) | Serves the folder query-tests/Security/CWE-200/lib, which can contain private information. |
3+
| lib/tst.js:11:1:11:52 | app.use ... + '/')) | Serves the folder query-tests/Security/CWE-200/lib, which can contain private information. |
4+
| private-file-exposure.js:8:1:8:49 | app.use ... ular')) | Serves the folder "./node_modules/angular", which can contain private information. |
5+
| private-file-exposure.js:9:1:9:59 | app.use ... ular')) | Serves the folder "node_modules/angular", which can contain private information. |
6+
| private-file-exposure.js:10:1:10:67 | app.use ... mate')) | Serves the folder "node_modules/angular-animate", which can contain private information. |
7+
| private-file-exposure.js:11:1:11:67 | app.use ... ular')) | Serves the folder "/node_modules/angular", which can contain private information. |
8+
| private-file-exposure.js:12:1:12:78 | app.use ... ute/')) | Serves the folder "/node_modules/angular-route/", which can contain private information. |
9+
| private-file-exposure.js:13:1:13:48 | app.use ... ular')) | Serves the folder "/node_modules/angular", which can contain private information. |
10+
| private-file-exposure.js:14:1:14:84 | app.use ... les'))) | Serves the folder "../node_modules", which can contain private information. |
11+
| private-file-exposure.js:15:1:15:35 | app.use ... ('./')) | Serves the current working folder, which can contain private information. |
12+
| private-file-exposure.js:16:1:16:67 | app.use ... lar/')) | Serves the folder "./node_modules/angular/", which can contain private information. |
13+
| private-file-exposure.js:17:1:17:78 | app.use ... ar/'))) | Serves the folder "./node_modules/angular/", which can contain private information. |
14+
| private-file-exposure.js:18:1:18:74 | app.use ... les"))) | Serves the folder "/node_modules", which can contain private information. |
15+
| private-file-exposure.js:19:1:19:88 | app.use ... lar/')) | Serves the folder "/node_modules/angular/", which can contain private information. |
16+
| private-file-exposure.js:22:1:22:58 | app.use ... lar/')) | Serves the folder "/node_modules/angular/", which can contain private information. |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Security/CWE-200/PrivateFileExposure.ql
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "foo",
3+
"dependencies": {
4+
"async": "3.2.0"
5+
}
6+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
var express = require('express');
3+
var path = require("path");
4+
5+
var app = express();
6+
7+
app.use('basedir', express.static(__dirname)); // BAD
8+
const rootDir = __dirname;
9+
app.use('basedir', express.static(rootDir)); // BAD
10+
11+
app.use('/monthly', express.static(__dirname + '/')); // BAD
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
2+
var express = require('express');
3+
var path = require("path");
4+
5+
var app = express();
6+
7+
// Not good.
8+
app.use(express.static('./node_modules/angular'));
9+
app.use('/angular', express.static('node_modules/angular'));
10+
app.use('/animate', express.static('node_modules/angular-animate'));
11+
app.use('/js', express.static(__dirname + '/node_modules/angular'));
12+
app.use('/router', express.static(__dirname + '/node_modules/angular-route/'));
13+
app.use(express.static('/node_modules/angular'));
14+
app.use('/node_modules', express.static(path.resolve(__dirname, '../node_modules')));
15+
app.use('/js',express.static('./'));
16+
app.use('/angular', express.static("./node_modules" + '/angular/'));
17+
app.use('/angular', express.static(path.join("./node_modules" + '/angular/')));
18+
app.use('/angular', express.static(path.join(__dirname, "/node_modules")));
19+
app.use('/angular', express.static(path.join(__dirname, "/node_modules") + '/angular/'));
20+
const rootDir = __dirname;
21+
const nodeDir = path.join(rootDir + "/node_modules");
22+
app.use('/angular', express.static(nodeDir + '/angular/'));
23+
24+
25+
// Good
26+
app.use(express.static('./node_modules/jquery/dist'));
27+
app.use(express.static('./node_modules/bootstrap/dist'));
28+
app.use('/js', express.static(__dirname + '/node_modules/html5sortable/dist'));
29+
app.use('/css', express.static(__dirname + '/css'));
30+
app.use('/favicon.ico', express.static(__dirname + '/favicon.ico'));
31+
app.use(express.static(__dirname + "/static"));
32+
app.use(express.static(__dirname + "/static/js"));
33+
app.use('/docs/api', express.static('docs/api'));
34+
app.use('/js/', express.static('node_modules/bootstrap/dist/js'))
35+
app.use('/css/', express.static('node_modules/font-awesome/css'));
36+
app.use('basedir', express.static(__dirname)); // GOOD, because there is no package.json in the same folder.
37+
app.use('/monthly', express.static(__dirname + '/')); // GOOD, because there is no package.json in the same folder.

0 commit comments

Comments
 (0)