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

Skip to content

Commit 98254e8

Browse files
authored
Merge pull request #132 from denislevin/denisl/js/HttpToFileAccessTest
Approved by xiemaisi
2 parents 30412ca + e147e69 commit 98254e8

22 files changed

Lines changed: 4073 additions & 7 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
@@ -832,6 +832,10 @@ module Express {
832832
asExpr().(MethodCallExpr).calls(any(ResponseExpr res), name))
833833
}
834834

835+
override DataFlow::Node getDataNode() {
836+
result = DataFlow::valueNode(astNode)
837+
}
838+
835839
override DataFlow::Node getAPathArgument() {
836840
result = DataFlow::valueNode(astNode.getArgument(0))
837841
}

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: 174 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,23 @@ module NodeJSLib {
372372
)
373373
}
374374

375+
/**
376+
* Holds if the `i`th parameter of method `methodName` of the Node.js
377+
* `fs` module might represent a data parameter or buffer or a callback
378+
* that receives the data.
379+
*
380+
* We determine this by looking for an externs declaration for
381+
* `fs.methodName` where the `i`th parameter's name is `data` or
382+
* `buffer` or a 'callback'.
383+
*/
384+
private predicate fsDataParam(string methodName, int i, string n) {
385+
exists (ExternalMemberDecl decl, Function f, JSDocParamTag p |
386+
decl.hasQualifiedName("fs", methodName) and f = decl.getInit() and
387+
p.getDocumentedParameter() = f.getParameter(i).getAVariable() and
388+
n = p.getName().toLowerCase() |
389+
n = "data" or n = "buffer" or n = "callback"
390+
)
391+
}
375392
/**
376393
* A member `member` from module `fs` or its drop-in replacements `graceful-fs` or `fs-extra`.
377394
*/
@@ -384,21 +401,161 @@ module NodeJSLib {
384401
)
385402
}
386403

404+
387405
/**
388406
* A call to a method from module `fs`, `graceful-fs` or `fs-extra`.
389407
*/
390-
private class NodeJSFileSystemAccess extends FileSystemAccess, DataFlow::CallNode {
408+
private class NodeJSFileSystemAccessCall extends FileSystemAccess, DataFlow::CallNode {
391409
string methodName;
392410

393-
NodeJSFileSystemAccess() {
411+
NodeJSFileSystemAccessCall() {
394412
this = fsModuleMember(methodName).getACall()
395413
}
396414

415+
string getMethodName() {
416+
result = methodName
417+
}
418+
419+
override DataFlow::Node getDataNode() {
420+
(
421+
methodName = "readFileSync" and
422+
result = this
423+
)
424+
or
425+
exists (int i, string paramName | fsDataParam(methodName, i, paramName) |
426+
(
427+
paramName = "callback" and
428+
exists (DataFlow::ParameterNode p, string n |
429+
p = getCallback(i).getAParameter() and
430+
n = p.getName().toLowerCase() and
431+
result = p |
432+
n = "data" or n = "buffer" or n = "string"
433+
)
434+
)
435+
or
436+
result = getArgument(i))
437+
}
438+
397439
override DataFlow::Node getAPathArgument() {
398-
exists (int i | fsFileParam(methodName, i) |
399-
result = getArgument(i)
440+
exists (int i | fsFileParam(methodName, i) |
441+
result = getArgument(i))
442+
}
443+
}
444+
445+
/** Only NodeJSSystemFileAccessCalls that write data to 'fs' */
446+
private class NodeJSFileSystemAccessWriteCall extends FileSystemWriteAccess, NodeJSFileSystemAccessCall {
447+
NodeJSFileSystemAccessWriteCall () {
448+
this.getMethodName() = "appendFile" or
449+
this.getMethodName() = "appendFileSync" or
450+
this.getMethodName() = "write" or
451+
this.getMethodName() = "writeFile" or
452+
this.getMethodName() = "writeFileSync" or
453+
this.getMethodName() = "writeSync"
454+
}
455+
}
456+
457+
/** Only NodeJSSystemFileAccessCalls that read data from 'fs' */
458+
private class NodeJSFileSystemAccessReadCall extends FileSystemReadAccess, NodeJSFileSystemAccessCall {
459+
NodeJSFileSystemAccessReadCall () {
460+
this.getMethodName() = "read" or
461+
this.getMethodName() = "readSync" or
462+
this.getMethodName() = "readFile" or
463+
this.getMethodName() = "readFileSync"
464+
}
465+
}
466+
467+
/**
468+
* A call to write corresponds to a pattern where file stream is open first with 'createWriteStream', followed by 'write' or 'end' call
469+
*/
470+
private class NodeJSFileSystemWrite extends FileSystemWriteAccess, DataFlow::CallNode {
471+
472+
NodeJSFileSystemAccessCall init;
473+
474+
NodeJSFileSystemWrite() {
475+
exists (NodeJSFileSystemAccessCall n |
476+
n.getCalleeName() = "createWriteStream" and init = n |
477+
this = n.getAMemberCall("write") or
478+
this = n.getAMemberCall("end")
479+
)
480+
}
481+
482+
override DataFlow::Node getDataNode() {
483+
result = this.getArgument(0)
484+
}
485+
486+
override DataFlow::Node getAPathArgument() {
487+
result = init.getAPathArgument()
488+
}
489+
}
490+
491+
/**
492+
* A call to read corresponds to a pattern where file stream is open first with createReadStream, followed by 'read' call
493+
*/
494+
private class NodeJSFileSystemRead extends FileSystemReadAccess, DataFlow::CallNode {
495+
496+
NodeJSFileSystemAccessCall init;
497+
498+
NodeJSFileSystemRead() {
499+
exists (NodeJSFileSystemAccessCall n |
500+
n.getCalleeName() = "createReadStream" and init = n |
501+
this = n.getAMemberCall("read")
502+
)
503+
}
504+
505+
override DataFlow::Node getDataNode() {
506+
result = this
507+
}
508+
509+
override DataFlow::Node getAPathArgument() {
510+
result = init.getAPathArgument()
511+
}
512+
}
513+
514+
/**
515+
* A call to read corresponds to a pattern where file stream is open first with createReadStream, followed by 'pipe' call
516+
*/
517+
private class NodeJSFileSystemPipe extends FileSystemReadAccess, DataFlow::CallNode {
518+
519+
NodeJSFileSystemAccessCall init;
520+
521+
NodeJSFileSystemPipe() {
522+
exists (NodeJSFileSystemAccessCall n |
523+
n.getCalleeName() = "createReadStream" and init = n |
524+
this = n.getAMemberCall("pipe")
525+
)
526+
}
527+
528+
override DataFlow::Node getDataNode() {
529+
result = this.getArgument(0)
530+
}
531+
532+
override DataFlow::Node getAPathArgument() {
533+
result = init.getAPathArgument()
534+
}
535+
}
536+
537+
/**
538+
* An 'on' event where data comes in as a parameter (usage: readstream.on('data', chunk))
539+
*/
540+
private class NodeJSFileSystemReadDataEvent extends FileSystemReadAccess, DataFlow::CallNode {
541+
542+
NodeJSFileSystemAccessCall init;
543+
544+
NodeJSFileSystemReadDataEvent() {
545+
exists(NodeJSFileSystemAccessCall n |
546+
n.getCalleeName() = "createReadStream" and init = n |
547+
this = n.getAMethodCall("on") and
548+
this.getArgument(0).mayHaveStringValue("data")
400549
)
401550
}
551+
552+
override DataFlow::Node getDataNode() {
553+
result = this.getCallback(1).getParameter(0)
554+
}
555+
556+
override DataFlow::Node getAPathArgument() {
557+
result = init.getAPathArgument()
558+
}
402559
}
403560

404561
/**
@@ -637,9 +794,20 @@ module NodeJSLib {
637794
result = "http.request data parameter"
638795
}
639796
}
640-
641-
797+
642798
/**
799+
* An argument to client request.write () method, can be used to write body to a HTTP or HTTPS POST/PUT request,
800+
* or request option (like headers, cookies, even url)
801+
*/
802+
class HttpRequestWriteArgument extends HTTP::RequestBody, DataFlow::Node {
803+
HttpRequestWriteArgument () {
804+
exists(CustomClientRequest req |
805+
this = req.getAMethodCall("write").getArgument(0) or
806+
this = req.getArgument(0))
807+
}
808+
}
809+
810+
/**
643811
* 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)`.
644812
*/
645813
class ClientRequestHandler extends DataFlow::FunctionNode {

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)