Skip to content

Commit 0f95069

Browse files
committed
Finalize CqlInjection
1 parent 25450a8 commit 0f95069

File tree

6 files changed

+318
-77
lines changed

6 files changed

+318
-77
lines changed

javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CAPCqlInjectionQuery.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class CqlClauseParserCallWithStringConcat instanceof CqlClauseParserCall {
4444
}
4545

4646
class CqlInjectionConfiguration extends TaintTracking::Configuration {
47-
CqlInjectionConfiguration() { this = "CqlInjection" }
47+
CqlInjectionConfiguration() { this = "CQL injection from untrusted data" }
4848

4949
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
5050

javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll

Lines changed: 93 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ class CdsConnectToCall extends DataFlow::CallNode {
101101
}
102102

103103
/**
104-
* A dataflow node that represents a service.
104+
* A data flow node that represents a service.
105105
* Note that its definition is a `UserDefinedApplicationService`, not a `ServiceInstance`.
106106
*/
107107
abstract class ServiceInstance extends SourceNode {
@@ -146,7 +146,7 @@ class ServiceInstanceFromCdsServe extends ServiceInstance {
146146
* const Service1 = cds.connect.to("service-2");
147147
* ```
148148
*/
149-
class ServiceInstanceFromCdsConnectTo extends ServiceInstance, SourceNode instanceof PropRead {
149+
class ServiceInstanceFromCdsConnectTo extends ServiceInstance {
150150
string serviceDesignator;
151151
string serviceName;
152152

@@ -155,9 +155,8 @@ class ServiceInstanceFromCdsConnectTo extends ServiceInstance, SourceNode instan
155155
}
156156

157157
override UserDefinedApplicationService getDefinition() {
158-
/* 1. The service */
159158
exists(RequiredService serviceDecl |
160-
serviceDecl.getName() = [serviceName, serviceDesignator] and
159+
serviceDecl.getName() = serviceDesignator and
161160
result.hasLocationInfo(serviceDecl.getImplementationFile().getAbsolutePath(), _, _, _, _)
162161
)
163162
or
@@ -169,10 +168,6 @@ class ServiceInstanceFromCdsConnectTo extends ServiceInstance, SourceNode instan
169168
string getServiceName() { result = serviceName }
170169
}
171170

172-
class DBServiceInstanceFromCdsConnectTo extends ServiceInstanceFromCdsConnectTo {
173-
DBServiceInstanceFromCdsConnectTo() { serviceDesignator = "db" }
174-
}
175-
176171
/**
177172
* A service instance obtained by directly calling the constructor
178173
* of its class with a `new` keyword. e.g.
@@ -271,6 +266,28 @@ class ServiceInstanceFromServeWithParameter extends ServiceInstance {
271266
}
272267
}
273268

269+
abstract class CdsDbService extends ServiceInstance {
270+
/* A DB service is implicitly defined. */
271+
override UserDefinedApplicationService getDefinition() { none() }
272+
}
273+
274+
class GloballyAccessedCdsDbService extends CdsDbService {
275+
GloballyAccessedCdsDbService() {
276+
exists(CdsFacade cds |
277+
this = cds.getMember("db").asSource() or
278+
this = cds.asSource()
279+
)
280+
}
281+
}
282+
283+
/* Note: This should not extend `ServiceInstanceFromCdsConnectTo`, as it does NOT do a property read! */
284+
class DbServiceInstanceFromCdsConnectTo extends CdsDbService {
285+
DbServiceInstanceFromCdsConnectTo() { this = serviceInstanceFromCdsConnectTo("db") }
286+
287+
/* A DB service is implicitly defined. */
288+
override UserDefinedApplicationService getDefinition() { none() }
289+
}
290+
274291
/**
275292
* A call to `before`, `on`, or `after` on an `cds.ApplicationService`.
276293
* It registers an handler to be executed when an event is fired,
@@ -564,16 +581,30 @@ class CdsUser extends API::Node {
564581
}
565582
}
566583

567-
class CdsTransaction extends MethodCallNode {
584+
class CdsTransaction extends SourceNode {
568585
ServiceInstance srv;
586+
CallNode txCall;
569587

570-
CdsTransaction() { this = srv.getAMemberCall("tx") }
588+
CdsTransaction() {
589+
txCall = srv.getAMemberCall("tx") and
590+
(
591+
this = txCall or
592+
this =
593+
txCall
594+
.getABoundCallbackParameter([
595+
0, // When the context object is absent
596+
1 // When the context object is present
597+
], 0)
598+
)
599+
}
571600

572601
ServiceInstance getRunner() { result = srv }
573602

574603
SourceNode getContextObject() {
575-
result = this.getAnArgument().getALocalSource() and not result instanceof FunctionNode
604+
/* 1. An object node passed as the first argument to a call to `srv.tx`. */
605+
result = txCall.getALocalSource() and not result instanceof FunctionNode
576606
or
607+
/* 2. A manually overriden `cds.context`. */
577608
exists(Stmt stmt, CdsFacade cds |
578609
stmt = this.asExpr().getFirstControlFlowNode().getAPredecessor+() and
579610
result = cds.getMember("context").asSink() and
@@ -583,25 +614,10 @@ class CdsTransaction extends MethodCallNode {
583614

584615
DataFlow::Node getUser() { result = this.getContextObject().getAPropertyWrite("user").getRhs() }
585616

586-
MethodCallNode getATransactionCall() {
587-
exists(ControlFlowNode exprOrStmt |
588-
exprOrStmt =
589-
this.getAnArgument().(FunctionNode).getALocalSource().asExpr().(Function).getABodyStmt() and
590-
exprOrStmt.(Stmt).getAChildExpr().flow().(MethodCallNode).getReceiver().getALocalSource() =
591-
this.getAnArgument().(FunctionNode).getParameter(_) and
592-
result = exprOrStmt.(Stmt).getAChildExpr().flow()
593-
or
594-
exprOrStmt =
595-
this.getAnArgument().(FunctionNode).getALocalSource().asExpr().(Function).getAChildExpr() and
596-
exprOrStmt.(Expr).flow().(MethodCallNode).getReceiver().getALocalSource() =
597-
this.getAnArgument().(FunctionNode).getParameter(_) and
598-
result = exprOrStmt.(MethodCallExpr).flow()
599-
or
600-
exprOrStmt = this.asExpr().getFirstControlFlowNode().getASuccessor+() and
601-
exprOrStmt.(Expr).flow().(MethodCallNode).getReceiver().getALocalSource() = this and
602-
result = exprOrStmt.(MethodCallExpr).flow()
603-
)
604-
}
617+
/**
618+
* Gets a method call on this transaction object.
619+
*/
620+
MethodCallNode getATransactionCall() { result = this.getAMemberCall(_) }
605621

606622
CqlClause getAnExecutedCqlClause() {
607623
result.asExpr() = this.getATransactionCall().getAnArgument().asExpr()
@@ -722,13 +738,11 @@ class EntityReferenceFromUserDefinedServiceEntities extends EntityReferenceFromE
722738
}
723739

724740
/**
725-
* db.entities, db.entities(...), cds.entities, cds.entities(...)
741+
* cds.entities, cds.entities(...), cds.db.entities, cds.db.entities(...)
726742
*/
727743
class EntityReferenceFromDbOrCdsEntities extends EntityReferenceFromEntities {
728744
EntityReferenceFromDbOrCdsEntities() {
729-
this.getReceiver().getALocalSource() instanceof DBServiceInstanceFromCdsConnectTo or
730-
exists(CdsFacade cds | this.getReceiver().getALocalSource() = cds.getNode()) or
731-
exists(CdsFacade cds | this.getReceiver().getALocalSource() = cds.getMember("db").asSource())
745+
this.getReceiver().getALocalSource() instanceof CdsDbService
732746
}
733747

734748
override CdlEntity getCqlDefinition() {
@@ -823,8 +837,16 @@ abstract class CqlQueryRunnerCall extends MethodCallNode {
823837

824838
exists(CdsFacade cds | base = cds.asSource()) or
825839
exists(CdsDb cdsDb | base = cdsDb) or
826-
/* 2. Method call on a service instance object. */
827-
exists(ServiceInstance srv | base = srv)
840+
/*
841+
* 2. Method call on a service instance object.
842+
*/
843+
844+
exists(ServiceInstance srv | base.getALocalSource() = srv) or
845+
/*
846+
* 3. Method call on a transaction object.
847+
*/
848+
849+
exists(CdsTransaction tx | base = tx)
828850
)
829851
}
830852

@@ -911,3 +933,38 @@ class CqlUpsertMethodCall extends CqlShortcutMethodCall {
911933
)
912934
}
913935
}
936+
937+
/**
938+
* A call to APIs that takes the given input string written in CDL and parses it according to
939+
* the CQN specification.
940+
*
941+
* Note that the outcome of calling the fluent APIs is also a CQN, which means both can be run
942+
* against a service with `srv.run`.
943+
*/
944+
abstract class CqlClauseParserCall extends DataFlow::CallNode {
945+
DataFlow::ExprNode cdlString;
946+
947+
DataFlow::ExprNode getCdlString() { result = cdlString }
948+
}
949+
950+
class GlobalCQLFunction extends CqlClauseParserCall {
951+
GlobalCQLFunction() { this = DataFlow::globalVarRef("CQL").getACall() }
952+
}
953+
954+
class CdsParseCqlCall extends CqlClauseParserCall {
955+
CdsParseCqlCall() {
956+
exists(CdsFacade cds |
957+
this = cds.getMember("parse").getMember("cql").getACall() and
958+
cdlString = this.getArgument(0)
959+
)
960+
}
961+
}
962+
963+
class CdsQlCall extends CqlClauseParserCall {
964+
CdsQlCall() {
965+
exists(CdsFacade cds |
966+
this = cds.getMember("ql").getACall() and
967+
cdlString = this.getArgument(0)
968+
)
969+
}
970+
}

javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CQL.qll

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -354,38 +354,3 @@ class CqlDeleteClause extends CqlClause {
354354
result.asDotExpr().getPropertyName() = "from"
355355
}
356356
}
357-
358-
/**
359-
* A call to APIs that takes the given input string written in CDL and parses it according to
360-
* the CQN specification.
361-
*
362-
* Note that the outcome of calling the fluent APIs is also a CQN, which means both can be run
363-
* against a service with `srv.run`.
364-
*/
365-
abstract class CqlClauseParserCall extends DataFlow::CallNode {
366-
DataFlow::ExprNode cdlString;
367-
368-
DataFlow::ExprNode getCdlString() { result = cdlString }
369-
}
370-
371-
class GlobalCQLFunction extends CqlClauseParserCall {
372-
GlobalCQLFunction() { this = DataFlow::globalVarRef("CQL").getACall() }
373-
}
374-
375-
class CdsParseCqlCall extends CqlClauseParserCall {
376-
CdsParseCqlCall() {
377-
exists(CdsFacade cds |
378-
this = cds.getMember("parse").getMember("cql").getACall() and
379-
cdlString = this.getArgument(0)
380-
)
381-
}
382-
}
383-
384-
class CdsQlCall extends CqlClauseParserCall {
385-
CdsQlCall() {
386-
exists(CdsFacade cds |
387-
this = cds.getMember("ql").getACall() and
388-
cdlString = this.getArgument(0)
389-
)
390-
}
391-
}

javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/DataFlow.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ class AsyncStyleCommunication extends InterServiceCommunication {
110110
[
111111
srvEmit.getEmitter().getDefinition().getManifestName(),
112112
srvEmit.getEmitter().getDefinition().getUnqualifiedName()
113-
] = orchestratingRegistration.getReceiver().(ServiceInstanceFromCdsConnectTo).getServiceName() and
113+
] = orchestratingRegistration.getReceiver().(ServiceInstanceFromCdsConnectTo).getServiceDesignator() and
114114
recipient = methodCallOnReceiver.getReceiver() and
115115
methodCallOnReceiver.getEnclosingFunction() = orchestratingRegistration.getHandler().asExpr()
116116
)

javascript/frameworks/cap/src/cqlinjection/CqlInjection.ql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,6 @@ import advanced_security.javascript.frameworks.cap.CAPCqlInjectionQuery
1616

1717
from CqlInjectionConfiguration sql, DataFlow::PathNode source, DataFlow::PathNode sink
1818
where sql.hasFlowPath(source, sink)
19-
select sink.getNode(), source, sink, "This query depends on a $@.", source.getNode(),
19+
/* TODO: Print different message if sink is `CqlShortcutMethodCallWithStringConcat` */
20+
select sink.getNode(), source, sink, "This CQL query depends on a $@.", source.getNode(),
2021
"user-provided value"

0 commit comments

Comments
 (0)