mirror of
https://github.com/github/codeql.git
synced 2025-12-20 10:46:30 +01:00
add query for detecting insecure temprary files
This commit is contained in:
@@ -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])
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
21
javascript/ql/src/Security/CWE-377/InsecureTemporaryFile.ql
Normal file
21
javascript/ql/src/Security/CWE-377/InsecureTemporaryFile.ql
Normal 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"
|
||||
@@ -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");
|
||||
@@ -0,0 +1,5 @@
|
||||
const fs = require('fs');
|
||||
const tmp = require('tmp');
|
||||
|
||||
const file = tmp.fileSync().name;
|
||||
fs.writeFileSync(file, "content");
|
||||
@@ -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.
|
||||
@@ -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 |
|
||||
@@ -0,0 +1 @@
|
||||
Security/CWE-377/InsecureTemporaryFile.ql
|
||||
@@ -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
|
||||
})
|
||||
Reference in New Issue
Block a user