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

Skip to content

Commit 8152cef

Browse files
author
Denis Levin
committed
Squished changes for HttpToFileAccess commint
1 parent e21a5e4 commit 8152cef

22 files changed

Lines changed: 4061 additions & 6 deletions
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* @name File Access data flows to Http POST/PUT
3+
* @description Writing data from file directly to http body or request header can be an indication to data exfiltration or unauthorized information disclosure.
4+
* @kind problem
5+
* @problem.severity warning
6+
* @id js/file-access-to-http
7+
* @tags security
8+
* external/cwe/cwe-200
9+
*/
10+
11+
import javascript
12+
import semmle.javascript.security.dataflow.FileAccessToHttp
13+
14+
from FileAccessToHttpDataFlow::Configuration config, DataFlow::Node src, DataFlow::Node sink
15+
where config.hasFlow (src, sink)
16+
select src, "$@ flows directly to Http request body", sink, "File access"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* @name Http response data flows to File Access
3+
* @description Writing data from an HTTP request directly to the file system allows arbitrary file upload and might indicate a backdoor.
4+
* @kind problem
5+
* @problem.severity warning
6+
* @id js/http-to-file-access
7+
* @tags security
8+
* external/cwe/cwe-912
9+
*/
10+
11+
import javascript
12+
import semmle.javascript.security.dataflow.HttpToFileAccess
13+
14+
from HttpToFileAccessFlow::Configuration configuration, DataFlow::Node src, DataFlow::Node sink
15+
where configuration.hasFlow(src, sink)
16+
select sink, "$@ flows to file system", src, "Untrusted data received from Http response"

javascript/ql/src/semmle/javascript/Concepts.qll

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,27 @@ abstract class SystemCommandExecution extends DataFlow::Node {
2222
}
2323

2424
/**
25-
* A data flow node that performs a file system access.
25+
* A data flow node that performs a file system access (read, write, copy, permissions, stats, etc).
2626
*/
2727
abstract class FileSystemAccess extends DataFlow::Node {
28+
2829
/** Gets an argument to this file system access that is interpreted as a path. */
2930
abstract DataFlow::Node getAPathArgument();
31+
32+
/** Gets a node that represents file system access data, such as buffer the data is copied to. */
33+
abstract DataFlow::Node getDataNode();
3034
}
3135

36+
/**
37+
* A data flow node that performs read file system access.
38+
*/
39+
abstract class FileSystemReadAccess extends FileSystemAccess { }
40+
41+
/**
42+
* A data flow node that performs write file system access.
43+
*/
44+
abstract class FileSystemWriteAccess extends FileSystemAccess { }
45+
3246
/**
3347
* A data flow node that contains a file name or an array of file names from the local file system.
3448
*/

javascript/ql/src/semmle/javascript/frameworks/Express.qll

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,10 @@ module Express {
788788
asExpr().(MethodCallExpr).calls(any(ResponseExpr res), "sendFile")
789789
}
790790

791+
override DataFlow::Node getDataNode() {
792+
result = DataFlow::valueNode(astNode)
793+
}
794+
791795
override DataFlow::Node getAPathArgument() {
792796
result = DataFlow::valueNode(astNode.getArgument(0))
793797
}

javascript/ql/src/semmle/javascript/frameworks/HTTP.qll

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,11 @@ module HTTP {
132132
result = "http" or result = "https"
133133
}
134134

135+
/**
136+
* An expression whose value is sent as (part of) the body of an HTTP request (POST, PUT).
137+
*/
138+
abstract class RequestBody extends DataFlow::Node {}
139+
135140
/**
136141
* An expression whose value is sent as (part of) the body of an HTTP response.
137142
*/

javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll

Lines changed: 173 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,23 @@ module NodeJSLib {
336336
)
337337
}
338338

339+
/**
340+
* Holds if the `i`th parameter of method `methodName` of the Node.js
341+
* `fs` module might represent a data parameter or buffer or a callback
342+
* that receives the data.
343+
*
344+
* We determine this by looking for an externs declaration for
345+
* `fs.methodName` where the `i`th parameter's name is `data` or
346+
* `buffer` or a 'callback'.
347+
*/
348+
private predicate fsDataParam(string methodName, int i, string n) {
349+
exists (ExternalMemberDecl decl, Function f, JSDocParamTag p |
350+
decl.hasQualifiedName("fs", methodName) and f = decl.getInit() and
351+
p.getDocumentedParameter() = f.getParameter(i).getAVariable() and
352+
n = p.getName().toLowerCase() |
353+
n = "data" or n = "buffer" or n = "callback"
354+
)
355+
}
339356
/**
340357
* A member `member` from module `fs` or its drop-in replacements `graceful-fs` or `fs-extra`.
341358
*/
@@ -348,21 +365,161 @@ module NodeJSLib {
348365
)
349366
}
350367

368+
351369
/**
352370
* A call to a method from module `fs`, `graceful-fs` or `fs-extra`.
353371
*/
354-
private class NodeJSFileSystemAccess extends FileSystemAccess, DataFlow::CallNode {
372+
private class NodeJSFileSystemAccessCall extends FileSystemAccess, DataFlow::CallNode {
355373
string methodName;
356374

357-
NodeJSFileSystemAccess() {
375+
NodeJSFileSystemAccessCall() {
358376
this = fsModuleMember(methodName).getACall()
359377
}
360378

379+
string getMethodName() {
380+
result = methodName
381+
}
382+
383+
override DataFlow::Node getDataNode() {
384+
(
385+
methodName = "readFileSync" and
386+
result = this
387+
)
388+
or
389+
exists (int i, string paramName | fsDataParam(methodName, i, paramName) |
390+
(
391+
paramName = "callback" and
392+
exists (DataFlow::ParameterNode p, string n |
393+
p = getCallback(i).getAParameter() and
394+
n = p.getName().toLowerCase() and
395+
result = p |
396+
n = "data" or n = "buffer" or n = "string"
397+
)
398+
)
399+
or
400+
result = getArgument(i))
401+
}
402+
361403
override DataFlow::Node getAPathArgument() {
362-
exists (int i | fsFileParam(methodName, i) |
363-
result = getArgument(i)
404+
exists (int i | fsFileParam(methodName, i) |
405+
result = getArgument(i))
406+
}
407+
}
408+
409+
/** Only NodeJSSystemFileAccessCalls that write data to 'fs' */
410+
private class NodeJSFileSystemAccessWriteCall extends FileSystemWriteAccess, NodeJSFileSystemAccessCall {
411+
NodeJSFileSystemAccessWriteCall () {
412+
this.getMethodName() = "appendFile" or
413+
this.getMethodName() = "appendFileSync" or
414+
this.getMethodName() = "write" or
415+
this.getMethodName() = "writeFile" or
416+
this.getMethodName() = "writeFileSync" or
417+
this.getMethodName() = "writeSync"
418+
}
419+
}
420+
421+
/** Only NodeJSSystemFileAccessCalls that read data from 'fs' */
422+
private class NodeJSFileSystemAccessReadCall extends FileSystemReadAccess, NodeJSFileSystemAccessCall {
423+
NodeJSFileSystemAccessReadCall () {
424+
this.getMethodName() = "read" or
425+
this.getMethodName() = "readSync" or
426+
this.getMethodName() = "readFile" or
427+
this.getMethodName() = "readFileSync"
428+
}
429+
}
430+
431+
/**
432+
* A call to write corresponds to a pattern where file stream is open first with 'createWriteStream', followed by 'write' or 'end' call
433+
*/
434+
private class NodeJSFileSystemWrite extends FileSystemWriteAccess, DataFlow::CallNode {
435+
436+
NodeJSFileSystemAccessCall init;
437+
438+
NodeJSFileSystemWrite() {
439+
exists (NodeJSFileSystemAccessCall n |
440+
n.getCalleeName() = "createWriteStream" and init = n |
441+
this = n.getAMemberCall("write") or
442+
this = n.getAMemberCall("end")
443+
)
444+
}
445+
446+
override DataFlow::Node getDataNode() {
447+
result = this.getArgument(0)
448+
}
449+
450+
override DataFlow::Node getAPathArgument() {
451+
result = init.getAPathArgument()
452+
}
453+
}
454+
455+
/**
456+
* A call to read corresponds to a pattern where file stream is open first with createReadStream, followed by 'read' call
457+
*/
458+
private class NodeJSFileSystemRead extends FileSystemReadAccess, DataFlow::CallNode {
459+
460+
NodeJSFileSystemAccessCall init;
461+
462+
NodeJSFileSystemRead() {
463+
exists (NodeJSFileSystemAccessCall n |
464+
n.getCalleeName() = "createReadStream" and init = n |
465+
this = n.getAMemberCall("read")
466+
)
467+
}
468+
469+
override DataFlow::Node getDataNode() {
470+
result = this
471+
}
472+
473+
override DataFlow::Node getAPathArgument() {
474+
result = init.getAPathArgument()
475+
}
476+
}
477+
478+
/**
479+
* A call to read corresponds to a pattern where file stream is open first with createReadStream, followed by 'pipe' call
480+
*/
481+
private class NodeJSFileSystemPipe extends FileSystemReadAccess, DataFlow::CallNode {
482+
483+
NodeJSFileSystemAccessCall init;
484+
485+
NodeJSFileSystemPipe() {
486+
exists (NodeJSFileSystemAccessCall n |
487+
n.getCalleeName() = "createReadStream" and init = n |
488+
this = n.getAMemberCall("pipe")
489+
)
490+
}
491+
492+
override DataFlow::Node getDataNode() {
493+
result = this.getArgument(0)
494+
}
495+
496+
override DataFlow::Node getAPathArgument() {
497+
result = init.getAPathArgument()
498+
}
499+
}
500+
501+
/**
502+
* An 'on' event where data comes in as a parameter (usage: readstream.on('data', chunk))
503+
*/
504+
private class NodeJSFileSystemReadDataEvent extends FileSystemReadAccess, DataFlow::CallNode {
505+
506+
NodeJSFileSystemAccessCall init;
507+
508+
NodeJSFileSystemReadDataEvent() {
509+
exists(NodeJSFileSystemAccessCall n |
510+
n.getCalleeName() = "createReadStream" and init = n |
511+
this = n.getAMethodCall("on") and
512+
this.getArgument(0).mayHaveStringValue("data")
364513
)
365514
}
515+
516+
override DataFlow::Node getDataNode() {
517+
result = this.getCallback(1).getParameter(0)
518+
}
519+
520+
override DataFlow::Node getAPathArgument() {
521+
result = init.getAPathArgument()
522+
}
366523
}
367524

368525
/**
@@ -602,7 +759,18 @@ module NodeJSLib {
602759
}
603760
}
604761

605-
762+
/**
763+
* An argument to client request.write () method, can be used to write body to a HTTP or HTTPS POST/PUT request,
764+
* or request option (like headers, cookies, even url)
765+
*/
766+
class HttpRequestWriteArgument extends HTTP::RequestBody, DataFlow::Node {
767+
HttpRequestWriteArgument () {
768+
exists(CustomClientRequest req |
769+
this = req.getAMethodCall("write").getArgument(0) or
770+
this = req.getArgument(0))
771+
}
772+
}
773+
606774
/**
607775
* A data flow node that is registered as a callback for an HTTP or HTTPS request made by a Node.js process, for example the function `handler` in `http.request(url).on(message, handler)`.
608776
*/

javascript/ql/src/semmle/javascript/frameworks/Request.qll

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,13 @@ module Request {
4444
}
4545

4646
}
47+
48+
// using 'request' library to make http 'POST' and 'PUT' requests with message body.
49+
private class RequestPostBody extends HTTP::RequestBody {
50+
RequestPostBody () {
51+
this = DataFlow::moduleMember("request", "post").getACall().getArgument(1) or
52+
this = DataFlow::moduleImport("request").getAnInvocation().getArgument(0)
53+
}
54+
}
4755

4856
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* Provides Taint tracking configuration for reasoning about file access taint flow to http post body
3+
*/
4+
import javascript
5+
import semmle.javascript.frameworks.HTTP
6+
7+
module FileAccessToHttpDataFlow {
8+
/**
9+
* A data flow source for reasoning about file access to http post body flow vulnerabilities.
10+
*/
11+
abstract class Source extends DataFlow::Node { }
12+
13+
/**
14+
* A data flow sink for reasoning about file access to http post body flow vulnerabilities.
15+
*/
16+
abstract class Sink extends DataFlow::Node { }
17+
18+
/**
19+
* A sanitizer for reasoning about file access to http post body flow vulnerabilities.
20+
*/
21+
abstract class Sanitizer extends DataFlow::Node { }
22+
23+
/**
24+
* A taint-tracking configuration for reasoning about file access to http post body flow vulnerabilities.
25+
*/
26+
class Configuration extends TaintTracking::Configuration {
27+
Configuration() { this = "FileAccessToHttpDataFlow" }
28+
29+
override predicate isSource(DataFlow::Node source) {
30+
source instanceof Source
31+
}
32+
33+
override predicate isSink(DataFlow::Node sink) {
34+
sink instanceof Sink
35+
}
36+
37+
override predicate isSanitizer(DataFlow::Node node) {
38+
super.isSanitizer(node) or
39+
node instanceof Sanitizer
40+
}
41+
42+
/** additional taint step that taints an object wrapping a source */
43+
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
44+
(
45+
pred = DataFlow::valueNode(_) or
46+
pred = DataFlow::parameterNode(_) or
47+
pred instanceof DataFlow::PropRead
48+
) and
49+
exists (DataFlow::PropWrite pwr |
50+
succ = pwr.getBase() and
51+
pred = pwr.getRhs()
52+
)
53+
}
54+
}
55+
56+
/** A source is a file access parameter, as in readFromFile(buffer). */
57+
private class FileAccessArgumentAsSource extends Source {
58+
FileAccessArgumentAsSource() {
59+
exists(FileSystemReadAccess src |
60+
this = src.getDataNode().getALocalSource()
61+
)
62+
}
63+
}
64+
65+
/** Sink is any parameter or argument that evaluates to a parameter ot a function or call that sets Http Body on a request */
66+
private class HttpRequestBodyAsSink extends Sink {
67+
HttpRequestBodyAsSink () {
68+
this instanceof HTTP::RequestBody
69+
}
70+
}
71+
}

0 commit comments

Comments
 (0)