JS: Port example queries

This commit is contained in:
Asger F
2023-10-04 21:31:23 +02:00
parent 449ec72dbe
commit ccd6d3dcd7
9 changed files with 93 additions and 84 deletions

View File

@@ -9,42 +9,42 @@
*/
import javascript
import DataFlow
import DataFlow::PathGraph
/**
* A taint-tracking configuration that tracks user-controlled values into a 'userId' property sent to a backend service.
*/
class IdorTaint extends TaintTracking::Configuration {
IdorTaint() { this = "IdorTaint" }
module IdorTaintConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node node) { node instanceof RemoteFlowSource }
override predicate isSource(Node node) { node instanceof RemoteFlowSource }
predicate isSink(DataFlow::Node node) { exists(ClientRequest req | node = req.getADataNode()) }
override predicate isSink(Node node) { exists(ClientRequest req | node = req.getADataNode()) }
override predicate isAdditionalTaintStep(Node pred, Node succ) {
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
// Step from x -> { userId: x }
succ.(SourceNode).getAPropertyWrite("userId").getRhs() = pred
succ.(DataFlow::SourceNode).getAPropertyWrite("userId").getRhs() = pred
}
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode node) {
predicate isBarrier(DataFlow::Node node) {
// After a check like `if (userId === session.user.id)`, the userId is considered safe.
node instanceof EqualityGuard
node = DataFlow::MakeBarrierGuard<EqualityGuard>::getABarrierNode()
}
}
/**
* A sanitizer for values that have successfully been compared to another value.
*/
class EqualityGuard extends TaintTracking::SanitizerGuardNode, ValueNode {
class EqualityGuard extends DataFlow::ValueNode {
override EqualityTest astNode;
override predicate sanitizes(boolean outcome, Expr e) {
predicate blocksExpr(boolean outcome, Expr e) {
e = astNode.getAnOperand() and
outcome = astNode.getPolarity()
}
}
from IdorTaint cfg, PathNode source, PathNode sink
where cfg.hasFlowPath(source, sink)
module IdorTaintFlow = TaintTracking::Global<IdorTaintConfig>;
import IdorTaintFlow::PathGraph
from IdorTaintFlow::PathNode source, IdorTaintFlow::PathNode sink
where IdorTaintFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "Unauthenticated user ID from $@.", source.getNode(), "here"

View File

@@ -9,23 +9,25 @@
*/
import javascript
import DataFlow
import DataFlow::PathGraph
class DecodingAfterSanitization extends TaintTracking::Configuration {
DecodingAfterSanitization() { this = "DecodingAfterSanitization" }
module DecodingAfterSanitizationConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node node) {
node.(DataFlow::CallNode).getCalleeName() = "escapeHtml"
}
override predicate isSource(Node node) { node.(CallNode).getCalleeName() = "escapeHtml" }
override predicate isSink(Node node) {
exists(CallNode call |
predicate isSink(DataFlow::Node node) {
exists(DataFlow::CallNode call |
call.getCalleeName().matches("decodeURI%") and
node = call.getArgument(0)
)
}
}
from DecodingAfterSanitization cfg, PathNode source, PathNode sink
where cfg.hasFlowPath(source, sink)
module DecodingAfterSanitizationFlow = TaintTracking::Global<DecodingAfterSanitizationConfig>;
import DecodingAfterSanitizationFlow::PathGraph
from DecodingAfterSanitizationFlow::PathNode source, DecodingAfterSanitizationFlow::PathNode sink
where DecodingAfterSanitizationFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "URI decoding invalidates the HTML sanitization performed $@.",
source.getNode(), "here"

View File

@@ -9,16 +9,14 @@
*/
import javascript
import DataFlow
import DataFlow::PathGraph
/**
* A call to a function that may introduce HTML meta-characters by
* replacing `%3C` or `\u003C` with `<`.
*/
class DecodingCall extends CallNode {
class DecodingCall extends DataFlow::CallNode {
string kind;
Node input;
DataFlow::Node input;
DecodingCall() {
this.getCalleeName().matches("decodeURI%") and
@@ -33,20 +31,24 @@ class DecodingCall extends CallNode {
string getKind() { result = kind }
/** Gets the input being decoded. */
Node getInput() { result = input }
DataFlow::Node getInput() { result = input }
}
class DecodingAfterSanitization extends TaintTracking::Configuration {
DecodingAfterSanitization() { this = "DecodingAfterSanitization" }
module DecodingAfterSanitizationConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node node) { node instanceof HtmlSanitizerCall }
override predicate isSource(Node node) { node instanceof HtmlSanitizerCall }
override predicate isSink(Node node) { node = any(DecodingCall c).getInput() }
predicate isSink(DataFlow::Node node) { node = any(DecodingCall c).getInput() }
}
from DecodingAfterSanitization cfg, PathNode source, PathNode sink, DecodingCall decoder
module DecodingAfterSanitizationFlow = TaintTracking::Global<DecodingAfterSanitizationConfig>;
import DecodingAfterSanitizationFlow::PathGraph
from
DecodingAfterSanitizationFlow::PathNode source, DecodingAfterSanitizationFlow::PathNode sink,
DecodingCall decoder
where
cfg.hasFlowPath(source, sink) and
DecodingAfterSanitizationFlow::flowPath(source, sink) and
decoder.getInput() = sink.getNode()
select sink.getNode(), source, sink, decoder.getKind() + " invalidates $@.", source.getNode(),
"this HTML sanitization"

View File

@@ -8,16 +8,17 @@
*/
import javascript
import DataFlow
class EvalTaint extends TaintTracking::Configuration {
EvalTaint() { this = "EvalTaint" }
module EvalTaintConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node node) { node instanceof RemoteFlowSource }
override predicate isSource(Node node) { node instanceof RemoteFlowSource }
override predicate isSink(Node node) { node = globalVarRef("eval").getACall().getArgument(0) }
predicate isSink(DataFlow::Node node) {
node = DataFlow::globalVarRef("eval").getACall().getArgument(0)
}
}
from EvalTaint cfg, Node source, Node sink
where cfg.hasFlow(source, sink)
module EvalTaintFlow = TaintTracking::Global<EvalTaintConfig>;
from DataFlow::Node source, DataFlow::Node sink
where EvalTaintFlow::flow(source, sink)
select sink, "Eval with user-controlled input from $@.", source, "here"

View File

@@ -9,18 +9,20 @@
*/
import javascript
import DataFlow
import DataFlow::PathGraph
class EvalTaint extends TaintTracking::Configuration {
EvalTaint() { this = "EvalTaint" }
module EvalTaintConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node node) { node instanceof RemoteFlowSource }
override predicate isSource(Node node) { node instanceof RemoteFlowSource }
override predicate isSink(Node node) { node = globalVarRef("eval").getACall().getArgument(0) }
predicate isSink(DataFlow::Node node) {
node = DataFlow::globalVarRef("eval").getACall().getArgument(0)
}
}
from EvalTaint cfg, PathNode source, PathNode sink
where cfg.hasFlowPath(source, sink)
module EvalTaintFlow = TaintTracking::Global<EvalTaintConfig>;
import EvalTaintFlow::PathGraph
from EvalTaintFlow::PathNode source, EvalTaintFlow::PathNode sink
where EvalTaintFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "Eval with user-controlled input from $@.", source.getNode(),
"here"

View File

@@ -9,8 +9,6 @@
*/
import javascript
import DataFlow
import DataFlow::PathGraph
/**
* A dataflow configuration that tracks authentication tokens ("authKey")
@@ -26,33 +24,37 @@ import DataFlow::PathGraph
* }), '*');
* ```
*/
class AuthKeyTracking extends DataFlow::Configuration {
AuthKeyTracking() { this = "AuthKeyTracking" }
module AuthKeyTrackingConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node node) {
node.(DataFlow::PropRead).getPropertyName() = "authKey"
}
override predicate isSource(Node node) { node.(PropRead).getPropertyName() = "authKey" }
override predicate isSink(Node node) {
exists(MethodCallNode call |
predicate isSink(DataFlow::Node node) {
exists(DataFlow::MethodCallNode call |
call.getMethodName() = "postMessage" and
call.getArgument(1).getStringValue() = "*" and // no restriction on target origin
call.getArgument(0) = node
)
}
override predicate isAdditionalFlowStep(Node pred, Node succ) {
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
// Step into objects: x -> { f: x }
succ.(SourceNode).getAPropertyWrite().getRhs() = pred
succ.(DataFlow::SourceNode).getAPropertyWrite().getRhs() = pred
or
// Step through JSON serialization: x -> JSON.stringify(x)
// Note: TaintTracking::Configuration includes this step by default, but not DataFlow::Configuration
exists(CallNode call |
call = globalVarRef("JSON").getAMethodCall("stringify") and
exists(DataFlow::CallNode call |
call = DataFlow::globalVarRef("JSON").getAMethodCall("stringify") and
pred = call.getArgument(0) and
succ = call
)
}
}
from AuthKeyTracking cfg, PathNode source, PathNode sink
where cfg.hasFlowPath(source, sink)
module AuthKeyTracking = DataFlow::Global<AuthKeyTrackingConfig>;
import AuthKeyTracking::PathGraph
from AuthKeyTracking::PathNode source, AuthKeyTracking::PathNode sink
where AuthKeyTracking::flowPath(source, sink)
select sink.getNode(), source, sink, "Message leaks the authKey from $@.", source.getNode(), "here"

View File

@@ -9,7 +9,7 @@
import javascript
import semmle.javascript.security.dataflow.StoredXssQuery
import DataFlow::PathGraph
import StoredXssFlow::PathGraph
/**
* The data returned from a MySQL query, such as the `data` parameter in this example:
@@ -31,6 +31,6 @@ class MysqlSource extends Source {
}
}
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
from StoredXssFlow::PathNode source, StoredXssFlow::PathNode sink
where StoredXssFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "Stored XSS from $@.", source.getNode(), "database value."

View File

@@ -10,7 +10,7 @@
import javascript
import semmle.javascript.security.dataflow.StoredXssQuery
import DataFlow::PathGraph
import StoredXssFlow::PathGraph
/**
* Gets an instance of `mysql.createConnection()`, tracked globally.
@@ -45,6 +45,6 @@ class MysqlSource extends Source {
MysqlSource() { this = mysqlConnection().getAMethodCall("query").getCallback(1).getParameter(1) }
}
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
from StoredXssFlow::PathNode source, StoredXssFlow::PathNode sink
where StoredXssFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "Stored XSS from $@.", source.getNode(), "database value."

View File

@@ -8,8 +8,6 @@
*/
import javascript
import DataFlow
import DataFlow::PathGraph
/**
* Gets the name of an unescaped placeholder in a lodash template.
@@ -21,13 +19,11 @@ string getAPlaceholderInString(string s) {
result = s.regexpCapture(".*<%=\\s*([a-zA-Z0-9_]+)\\s*%>.*", 1)
}
class TemplateInjection extends TaintTracking::Configuration {
TemplateInjection() { this = "TemplateInjection" }
module TemplateInjectionConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node node) { node instanceof RemoteFlowSource }
override predicate isSource(Node node) { node instanceof RemoteFlowSource }
override predicate isSink(Node node) {
exists(CallNode call, string placeholder |
predicate isSink(DataFlow::Node node) {
exists(DataFlow::CallNode call, string placeholder |
call = LodashUnderscore::member("template").getACall() and
placeholder = getAPlaceholderInString(call.getArgument(0).getStringValue()) and
node = call.getOptionArgument(1, placeholder)
@@ -35,7 +31,11 @@ class TemplateInjection extends TaintTracking::Configuration {
}
}
from TemplateInjection cfg, PathNode source, PathNode sink
where cfg.hasFlowPath(source, sink)
module TemplateInjectionFlow = TaintTracking::Global<TemplateInjectionConfig>;
import TemplateInjectionFlow::PathGraph
from TemplateInjectionFlow::PathNode source, TemplateInjectionFlow::PathNode sink
where TemplateInjectionFlow::flowPath(source, sink)
select sink.getNode(), source, sink,
"User-controlled value from $@ occurs unescaped in a lodash template.", source.getNode(), "here."