add query for detecting insecure temprary files

This commit is contained in:
Erik Krogh Kristensen
2022-01-18 11:23:22 +01:00
parent 6a53b7b233
commit 2433eafef2
10 changed files with 286 additions and 0 deletions

View File

@@ -0,0 +1,96 @@
/**
* Provides default sources, sinks and sanitizers for reasoning about
* insecure temporary file creation, as well as
* extension points for adding your own.
*/
import javascript
/**
* Classes and predicates for reasoning about insecure temporary file creation.
*/
module InsecureTemporaryFile {
/**
* A data flow source for insecure temporary file creation.
*/
abstract class Source extends DataFlow::Node { }
/**
* A data flow sink for insecure temporary file creation.
*/
abstract class Sink extends DataFlow::Node { }
/**
* A sanitizer for random insecure temporary file creation.
*/
abstract class Sanitizer extends DataFlow::Node { }
/** A call that opens a file with a given path. */
class OpenFileCall extends DataFlow::CallNode {
string methodName;
OpenFileCall() {
methodName = ["open", "openSync", "writeFile", "writeFileSync"] and
this = NodeJSLib::FS::moduleMember(methodName).getACall()
}
DataFlow::Node getPath() { result = this.getArgument(0) }
DataFlow::Node getMode() {
methodName = ["open", "openSync"] and
result = this.getArgument(2)
or
methodName = ["writeFile", "writeFileSync"] and
result = this.getOptionArgument(2, "mode")
}
}
/** Holds if the `mode` ensure no access to other users. */
bindingset[mode]
private predicate isSecureMode(int mode) {
// the lowest 6 bits should be 0.
// E.g. `0o600` is secure (each digit in a octal number is 3 bits)
mode.bitAnd(1) = 0 and
mode.bitAnd(2) = 0 and
mode.bitAnd(4) = 0 and
mode.bitAnd(8) = 0 and
mode.bitAnd(16) = 0 and
mode.bitAnd(32) = 0
}
/** The path in a call that opens a file without specifying a secure `mode`. Seen as a sink for insecure temporary file creation. */
class InsecureFileOpen extends Sink {
InsecureFileOpen() {
exists(OpenFileCall call |
not exists(call.getMode())
or
exists(int mode | mode = call.getMode().getIntValue() | not isSecureMode(mode))
|
this = call.getPath()
)
}
}
/** A a string that references the global tmp dir. Seen as a source for insecure temporary file creation. */
class OSTempDir extends Source {
OSTempDir() {
this = DataFlow::moduleImport("os").getAMemberCall("tmpdir")
or
this.getStringValue().matches("/tmp/%")
}
}
/** A non-first leaf in a string-concatenation. Seen as a sanitizer for insecure temporary file creation. */
class NonFirstStringConcatLeaf extends Sanitizer {
NonFirstStringConcatLeaf() {
exists(StringOps::ConcatenationRoot root |
this = root.getALeaf() and
not this = root.getFirstLeaf()
)
or
exists(DataFlow::CallNode join | join = DataFlow::moduleMember("path", "join").getACall() |
this = join.getArgument([1 .. join.getNumArgument() - 1])
)
}
}
}

View File

@@ -0,0 +1,27 @@
/**
* Provides a taint tracking configuration for reasoning about insecure temporary
* file creation.
*
* Note, for performance reasons: only import this file if
* `InsecureTemporaryFile::Configuration` is needed, otherwise
* `InsecureTemporaryFileCustomizations` should be imported instead.
*/
import javascript
import InsecureTemporaryFileCustomizations::InsecureTemporaryFile
/**
* A taint-tracking configuration for reasoning about insecure temporary file creation.
*/
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "InsecureTemporaryFile" }
override predicate isSource(DataFlow::Node source) { source instanceof Source }
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
override predicate isSanitizer(DataFlow::Node node) {
super.isSanitizer(node) or
node instanceof Sanitizer
}
}

View File

@@ -0,0 +1,43 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>
Temporary files created in the operating system tmp directory are by default accessible
to other users. This can in some cases lead to information exposure, or in the worst
case to remote code execution.
</p>
</overview>
<recommendation>
<p>
Use a well tested library like <a href="https://www.npmjs.com/package/tmp">tmp</a>
for creating temprary files. These libraries ensure both that the file is inaccesible
to other users and that the file does not already exist.
</p>
</recommendation>
<example>
<p>
The following example creates a temporary file in the operating system tmp directory.
</p>
<sample src="examples/insecure-temporary-file.js" />
<p>
The file created above is accessible to other users, and there is no guarantee that
the file does not already exist.
</p>
<p>
The below example uses the <a href="https://www.npmjs.com/package/tmp">tmp</a> library
to securely create a temporary file.
</p>
<sample src="examples/secure-temporary-file.js" />
</example>
<references>
<li>Mitre.org: <a href="https://cwe.mitre.org/data/definitions/377.html">CWE-377</a>.</li>
<li>NPM: <a href="https://www.npmjs.com/package/tmp">tmp</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,21 @@
/**
* @name Insecure temporary file
* @description Creating a temporary file that is accessible by other users TODO:
* @kind path-problem
* @id js/insecure-temporary-file
* @problem.severity warning
* @security-severity 7.0
* @precision medium
* @tags external/cwe/cwe-377
* external/cwe/cwe-378
* security
*/
import javascript
import DataFlow::PathGraph
import semmle.javascript.security.dataflow.InsecureTemporaryFileQuery
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Insecure creation of file in $@.", source.getNode(),
"the os temp dir"

View File

@@ -0,0 +1,6 @@
const fs = require('fs');
const os = require('os');
const path = require('path');
const file = path.join(os.tmpdir(), "test-" + (new Date()).getTime() + ".txt");
fs.writeFileSync(file, "content");

View File

@@ -0,0 +1,5 @@
const fs = require('fs');
const tmp = require('tmp');
const file = tmp.fileSync().name;
fs.writeFileSync(file, "content");

View File

@@ -0,0 +1,4 @@
---
category: newQuery
---
* A new query `js/insecure-temporary-file` has been added. The query detects the creation of temporary files that may be accessible by others users. The query is not run by default.

View File

@@ -0,0 +1,53 @@
nodes
| insecure-temporary-file.js:7:9:11:5 | tmpLocation |
| insecure-temporary-file.js:7:23:11:5 | path.jo ... )\\n ) |
| insecure-temporary-file.js:8:9:8:45 | os.tmpd ... mpDir() |
| insecure-temporary-file.js:8:21:8:31 | os.tmpdir() |
| insecure-temporary-file.js:8:21:8:31 | os.tmpdir() |
| insecure-temporary-file.js:13:22:13:32 | tmpLocation |
| insecure-temporary-file.js:13:22:13:32 | tmpLocation |
| insecure-temporary-file.js:15:9:15:34 | tmpPath |
| insecure-temporary-file.js:15:19:15:34 | "/tmp/something" |
| insecure-temporary-file.js:15:19:15:34 | "/tmp/something" |
| insecure-temporary-file.js:17:22:17:49 | path.jo ... /foo/") |
| insecure-temporary-file.js:17:22:17:49 | path.jo ... /foo/") |
| insecure-temporary-file.js:17:32:17:38 | tmpPath |
| insecure-temporary-file.js:23:22:23:49 | path.jo ... /foo/") |
| insecure-temporary-file.js:23:22:23:49 | path.jo ... /foo/") |
| insecure-temporary-file.js:23:32:23:38 | tmpPath |
| insecure-temporary-file.js:25:11:25:92 | tmpPath2 |
| insecure-temporary-file.js:25:22:25:92 | path.jo ... )}.md`) |
| insecure-temporary-file.js:25:32:25:42 | os.tmpdir() |
| insecure-temporary-file.js:25:32:25:42 | os.tmpdir() |
| insecure-temporary-file.js:26:22:26:29 | tmpPath2 |
| insecure-temporary-file.js:26:22:26:29 | tmpPath2 |
| insecure-temporary-file.js:28:17:28:24 | tmpPath2 |
| insecure-temporary-file.js:28:17:28:24 | tmpPath2 |
edges
| insecure-temporary-file.js:7:9:11:5 | tmpLocation | insecure-temporary-file.js:13:22:13:32 | tmpLocation |
| insecure-temporary-file.js:7:9:11:5 | tmpLocation | insecure-temporary-file.js:13:22:13:32 | tmpLocation |
| insecure-temporary-file.js:7:23:11:5 | path.jo ... )\\n ) | insecure-temporary-file.js:7:9:11:5 | tmpLocation |
| insecure-temporary-file.js:8:9:8:45 | os.tmpd ... mpDir() | insecure-temporary-file.js:7:23:11:5 | path.jo ... )\\n ) |
| insecure-temporary-file.js:8:21:8:31 | os.tmpdir() | insecure-temporary-file.js:8:9:8:45 | os.tmpd ... mpDir() |
| insecure-temporary-file.js:8:21:8:31 | os.tmpdir() | insecure-temporary-file.js:8:9:8:45 | os.tmpd ... mpDir() |
| insecure-temporary-file.js:15:9:15:34 | tmpPath | insecure-temporary-file.js:17:32:17:38 | tmpPath |
| insecure-temporary-file.js:15:9:15:34 | tmpPath | insecure-temporary-file.js:23:32:23:38 | tmpPath |
| insecure-temporary-file.js:15:19:15:34 | "/tmp/something" | insecure-temporary-file.js:15:9:15:34 | tmpPath |
| insecure-temporary-file.js:15:19:15:34 | "/tmp/something" | insecure-temporary-file.js:15:9:15:34 | tmpPath |
| insecure-temporary-file.js:17:32:17:38 | tmpPath | insecure-temporary-file.js:17:22:17:49 | path.jo ... /foo/") |
| insecure-temporary-file.js:17:32:17:38 | tmpPath | insecure-temporary-file.js:17:22:17:49 | path.jo ... /foo/") |
| insecure-temporary-file.js:23:32:23:38 | tmpPath | insecure-temporary-file.js:23:22:23:49 | path.jo ... /foo/") |
| insecure-temporary-file.js:23:32:23:38 | tmpPath | insecure-temporary-file.js:23:22:23:49 | path.jo ... /foo/") |
| insecure-temporary-file.js:25:11:25:92 | tmpPath2 | insecure-temporary-file.js:26:22:26:29 | tmpPath2 |
| insecure-temporary-file.js:25:11:25:92 | tmpPath2 | insecure-temporary-file.js:26:22:26:29 | tmpPath2 |
| insecure-temporary-file.js:25:11:25:92 | tmpPath2 | insecure-temporary-file.js:28:17:28:24 | tmpPath2 |
| insecure-temporary-file.js:25:11:25:92 | tmpPath2 | insecure-temporary-file.js:28:17:28:24 | tmpPath2 |
| insecure-temporary-file.js:25:22:25:92 | path.jo ... )}.md`) | insecure-temporary-file.js:25:11:25:92 | tmpPath2 |
| insecure-temporary-file.js:25:32:25:42 | os.tmpdir() | insecure-temporary-file.js:25:22:25:92 | path.jo ... )}.md`) |
| insecure-temporary-file.js:25:32:25:42 | os.tmpdir() | insecure-temporary-file.js:25:22:25:92 | path.jo ... )}.md`) |
#select
| insecure-temporary-file.js:13:22:13:32 | tmpLocation | insecure-temporary-file.js:8:21:8:31 | os.tmpdir() | insecure-temporary-file.js:13:22:13:32 | tmpLocation | Insecure creation of file in $@. | insecure-temporary-file.js:8:21:8:31 | os.tmpdir() | the os temp dir |
| insecure-temporary-file.js:17:22:17:49 | path.jo ... /foo/") | insecure-temporary-file.js:15:19:15:34 | "/tmp/something" | insecure-temporary-file.js:17:22:17:49 | path.jo ... /foo/") | Insecure creation of file in $@. | insecure-temporary-file.js:15:19:15:34 | "/tmp/something" | the os temp dir |
| insecure-temporary-file.js:23:22:23:49 | path.jo ... /foo/") | insecure-temporary-file.js:15:19:15:34 | "/tmp/something" | insecure-temporary-file.js:23:22:23:49 | path.jo ... /foo/") | Insecure creation of file in $@. | insecure-temporary-file.js:15:19:15:34 | "/tmp/something" | the os temp dir |
| insecure-temporary-file.js:26:22:26:29 | tmpPath2 | insecure-temporary-file.js:25:32:25:42 | os.tmpdir() | insecure-temporary-file.js:26:22:26:29 | tmpPath2 | Insecure creation of file in $@. | insecure-temporary-file.js:25:32:25:42 | os.tmpdir() | the os temp dir |
| insecure-temporary-file.js:28:17:28:24 | tmpPath2 | insecure-temporary-file.js:25:32:25:42 | os.tmpdir() | insecure-temporary-file.js:28:17:28:24 | tmpPath2 | Insecure creation of file in $@. | insecure-temporary-file.js:25:32:25:42 | os.tmpdir() | the os temp dir |

View File

@@ -0,0 +1 @@
Security/CWE-377/InsecureTemporaryFile.ql

View File

@@ -0,0 +1,30 @@
const os = require('os');
const uuid = require('node-uuid');
const fs = require('fs');
const path = require('path');
(function main() {
var tmpLocation = path.join(
os.tmpdir ? os.tmpdir() : os.tmpDir(),
'something',
uuid.v4().slice(0, 8)
);
fs.writeFileSync(tmpLocation, content); // NOT OK
var tmpPath = "/tmp/something";
fs.writeFileSync(path.join("./foo/", tmpPath), content); // OK
fs.writeFileSync(path.join(tmpPath, "./foo/"), content); // NOT OK
fs.writeFileSync(path.join(tmpPath, "./foo/"), content, {mode: 0o600}); // OK
fs.writeFileSync(path.join(tmpPath, "./foo/"), content, {mode: mode}); // OK - assumed unknown mode is secure
fs.writeFileSync(path.join(tmpPath, "./foo/"), content, {mode: 0o666}); // NOT OK - explicitly insecure
const tmpPath2 = path.join(os.tmpdir(), `tmp_${Math.floor(Math.random() * 1000000)}.md`);
fs.writeFileSync(tmpPath2, content); // NOT OK
fs.openSync(tmpPath2, 'w'); // NOT OK
fs.openSync(tmpPath2, 'w', 0o600); // OK
})