Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/codeql/codeql-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: "My CodeQL config"

queries:
- uses: security-extended
# for ui5/cap queries
- uses: ./javascript/frameworks/ui5/src/codeql-suites/javascript-security-extended.qls
- uses: ./javascript/frameworks/cap/src/codeql-suites/javascript-security-extended.qls
- uses: ./javascript/frameworks/XSJS/src/codeql-suites/javascript-security-extended.qls

paths-ignore:
- "**/frameworks/*/test/models"
4 changes: 4 additions & 0 deletions javascript/frameworks/XSJS/ext/codeql-pack.lock.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
lockVersion: 1.0.0
dependencies: {}
compiled: false
9 changes: 9 additions & 0 deletions javascript/frameworks/XSJS/ext/qlpack.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
library: true
name: advanced-security/javascript-sap-async-xsjs-models
version: 0.1.0
extensionTargets:
codeql/javascript-all: "^1.1.0"
codeql/javascript-queries: "^1.0.3"
dataExtensions:
- "*.model.yml"
71 changes: 71 additions & 0 deletions javascript/frameworks/XSJS/ext/xsjs.model.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
extensions:
- addsTo:
pack: codeql/javascript-all
extensible: typeModel
data:
# ========== 1. Web Request ==========
- [WebRequest, XsjsDollar, "Member[request]"]
- [WebRequest, XsjsDollar, "Member[request].Member[entities].Fuzzy"]

# ========== 1-1. Web Request Bodies ==========
- [WebRequestBody, WebRequest, "Member[body]"]

# ========== 1-2. Web Request Parameters ==========
- [WebRequestParameters, WebRequest, "Member[parameters]"]

# ========== 1-3. Web Request Headers ==========
- [WebRequestHeaders, WebRequest, "Member[headers]"]

# ========== 2. Web Response ==========
- [WebResponse, global, "Member[$].Member[response]"]
- [WebResponse, global, "Member[$].Member[response].Member[entities].Fuzzy"]

# ========== 2-1. Web Response Bodies ==========
- [WebResponseBody, global, "Member[body]"]

# ========== 3. Mail, SMTPConnection ==========
- [Mail, XsjsDollar, "Member[net].Member[Mail].Instance"]
- [SMTPConnection, XsjsDollar, "Member[net].Member[SMTPConnection].Instance"]

# ========== 4. Inbound Web Response ==========
- [InboundResponse, XsjsDollar, "Member[net].Member[http].Member[Client].Instance.Member[getResponse].ReturnValue"]

- addsTo:
pack: codeql/javascript-all
extensible: sourceModel
data:
# ========== 1. Retrieving Web Request Body ==========
- [WebRequestBody, "Member[asArrayBuffer].ReturnValue", remote]
- [WebRequestBody, "Member[asString].ReturnValue", remote]
- [WebRequestBody, "Member[asWebRequest].ReturnValue", remote]

# ========== 2. Retrieving Web Request Parameter Value ==========
- [WebRequestParameters, "Member[get].ReturnValue", remote]
- [WebRequestParameters, AnyMember, remote]

# ========== 3. Receiving Response through HTTPClient ==========
- [HTTPClient, "InboundResponse.Member[body]", remote]
- [HTTPClient, "InboundResponse.Member[body].Member[asArrayBuffer].ReturnValue", remote]
- [HTTPClient, "InboundResponse.Member[body].Member[asString].ReturnValue", remote]
- [HTTPClient, "InboundResponse.Member[body].Member[asWebRequest].ReturnValue", remote]
- [HTTPClient, "InboundResponse.Member[cacheControl]", remote]
- [HTTPClient, "InboundResponse.Member[contentType]", remote]
- [HTTPClient, "InboundResponse.Member[cookies]", remote]
- [HTTPClient, "InboundResponse.Member[entities]", remote]
- [HTTPClient, "InboundResponse.Member[headers]", remote]
- [HTTPClient, "InboundResponse.Member[status]", remote]

- addsTo:
pack: codeql/javascript-all
extensible: sinkModel
data:
- [WebResponse, "Member[setBody].Argument[0]", html-injection]
# - [Mail, "Member[send].Argument[this]", "???"]
# - [SMTPConnection, "Member[send].Argument[0]", "???"]
# - ["HTTPClient", "Member[request].Argument[0]", "???"]

- addsTo:
pack: codeql/javascript-all
extensible: summaryModel
data:
- [global, "Member[JSON].Member[parse]", "Argument[0]", "ReturnValue", taint]
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
import javascript
import DataFlow

/**
* The root XSJS namespace, accessed as a dollar sign (`$`) symbol.
*/
class XSJSDollarNamespace extends GlobalVarRefNode {
XSJSDollarNamespace() {
this = globalVarRef("$") and
this.getFile().getExtension() = "xsjs"
}
}

/**
* `TypeModel` for `XSJSDollarNamespace`.
*/
class XSJSDollarTypeModel extends ModelInput::TypeModel {
override DataFlow::Node getASource(string type) {
type = "XsjsDollar" and
result = any(XSJSDollarNamespace dollar)
}

/**
* Prevents model pruning for type `XsjsDollar`
*/
override predicate isTypeUsed(string type) { type = "XsjsDollar" }
}

/**
* A reference to either a response or a request. A single-part request or response
* is accessed through `$.request` or `$.response`, but the multi-part ones are acceesed
* through `$.request.entities[n]` or `$.response.entities[n]`, respectively.
*/
class XSJSRequestOrResponse extends SourceNode instanceof PropRef {
string fieldName;

XSJSRequestOrResponse() {
fieldName = ["request", "response"] and
(
exists(XSJSDollarNamespace dollar | this = dollar.getAPropertyReference(fieldName))
or
exists(XSJSDollarNamespace dollar |
this =
dollar
.getAPropertyReference(fieldName)
.(SourceNode)
.getAPropertyReference("entities")
.(SourceNode)
.getAPropertyReference() and
this.asExpr() instanceof IndexExpr
)
)
}

predicate isResponse() { fieldName = "response" }

predicate isRequest() { fieldName = "request" }

PropRef getHeaders() { result = this.getAPropertyReference("headers") }

/**
* Get the immediate node corresponding to the `Content-Type` of this response or request.
*/
DataFlow::Node getContentType() {
/* 1. Setting Content-Type via `$.response.contentType` */
result = this.getAPropertyWrite("contentType").getRhs()
or
/* 2. Setting Content-Type via `$.response.headers.set("Content-Type", ...)` */
exists(MethodCallNode headersSetCall |
headersSetCall.getReceiver().getALocalSource() = this.getHeaders() and
headersSetCall.getMethodName() = "set" and
headersSetCall.getArgument(0).getALocalSource().asExpr().getStringValue() = "Content-Type" and
result = headersSetCall.getArgument(1)
)
}

/**
* Holds if the `Content-Type` is scriptable, i.e. able to include a JavaScript source.
*/
predicate isScriptableContentType() {
exists(string scriptableMimeType |
scriptableMimeType in ["text/html", "text/xml", "image/svg+xml"]
|
this.getContentType().getALocalSource().asExpr().getStringValue() = scriptableMimeType or
this.getContentType().asExpr().getStringValue() = scriptableMimeType
)
}

/**
* Holds if the `Content-Type` is dependent on a remote data.
*/
predicate contentTypeIsDependentOnRemote() {
this.getContentType().getALocalSource() instanceof RemoteFlowSource
}
}

/**
* A reference to a request. A single-part request is accessed through `$.request`,
* but a multi-part one is accessed through `$.request.entities[n]`.
*/
class XSJSRequest extends XSJSRequestOrResponse {
XSJSRequest() { this.isRequest() }

/**
* Gets an intraprocedural predecessor or a successor of this request, control-flow wise.
*/
XSJSRequest getAPredOrSuccRequest() {
exists(ControlFlowNode cfgNode |
(
cfgNode = this.asExpr().getFirstControlFlowNode().getAPredecessor+() or
cfgNode = this.asExpr().getFirstControlFlowNode().getASuccessor+()
) and
result.asExpr().getFirstControlFlowNode() = cfgNode
)
}
}

/**
* A reference to a response. A single-part request is accessed through `$.response`,
* but a multi-part one is accessed through `$.response.entities[n]`.
*/
class XSJSResponse extends XSJSRequestOrResponse {
XSJSResponse() { this.isResponse() }

/**
* Gets an intraprocedural predecessor or a successor of this response, control-flow wise.
*/
XSJSResponse getAPredOrSuccResponse() {
exists(ControlFlowNode cfgNode |
(
cfgNode = this.asExpr().getFirstControlFlowNode().getAPredecessor+() or
cfgNode = this.asExpr().getFirstControlFlowNode().getASuccessor+()
) and
result.asExpr().getFirstControlFlowNode() = cfgNode
)
}
}

/**
* A reference to a header of a request or a response.
*/
class XSJSRequestOrResponseHeaders extends SourceNode instanceof PropRef {
XSJSRequestOrResponseHeaders() {
exists(XSJSRequestOrResponse requestOrResponse |
this = requestOrResponse.getAPropertyReference("headers")
)
}

/**
* Given a header property name, gets a call to `$.web.TupelList.set(name, value, options?)`
* on this reference, which sets a header property to a value.
*/
MethodCallNode getHeaderSetCall(string name) {
result.getReceiver().getALocalSource() = this and
result.getMethodName() = "set" and
result.getArgument(0).getALocalSource().asExpr().getStringValue() = name
}

/**
* Gets a call to `$.web.TupelList.set(name, value, options?)` on this reference, which sets
* a header property to a value.
*/
MethodCallNode getAHeaderSetCall() { result = this.getHeaderSetCall(_) }
}

/**
* A reference to a database connection obtained either via the deprecated `$.db` namespace
* or the now-preferred `$.hdb` namespace.
*/
class XSJSDatabaseConnectionReference extends MethodCallNode {
string subNamespace;

XSJSDatabaseConnectionReference() {
exists(XSJSDollarNamespace dollar |
this.getMethodName() = "getConnection" and
this.getReceiver().getALocalSource() = dollar.getAPropertyReference(subNamespace)
)
}

predicate isDbSubNamespace() { subNamespace = "db" }

predicate isHdbSubNamespace() { subNamespace = "hdb" }

/**
* Gets the call that prepares the statement depending on the namespace.
*/
abstract MethodCallNode getStatementPreparingCall();
}

/**
* A reference to a database connection obtained via the deprecated `$.db` namespace.
*/
class XSJSDBConnectionReference extends XSJSDatabaseConnectionReference {
XSJSDBConnectionReference() { this.isDbSubNamespace() }

override MethodCallNode getStatementPreparingCall() {
result = this.getAMemberCall("prepareStatement")
}
}

/**
* A reference to a database connection obtained via the now-preferred `$.hdb` namespace.
*/
class XSJSHDBConnectionReference extends XSJSDatabaseConnectionReference {
XSJSHDBConnectionReference() { this.isHdbSubNamespace() }

override MethodCallNode getStatementPreparingCall() {
result = this.getAMemberCall("executeQuery")
}
}

class XSJSUtilNamespace extends SourceNode instanceof PropRef {
XSJSUtilNamespace() {
exists(XSJSDollarNamespace dollar | this = dollar.getAPropertyReference("util"))
}
}

class XSJSZipInstance extends NewNode {
XSJSZipInstance() {
exists(XSJSUtilNamespace util | this = util.getAConstructorInvocation("Zip"))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import javascript
import DataFlow
import advanced_security.javascript.frameworks.xsjs.AsyncXSJS

class XSJSZipClassInstantiation extends NewNode {
XSJSZipClassInstantiation() {
exists(XSJSUtilNamespace util |
this = util.getAConstructorInvocation("Zip") and
this.getAnArgument().getALocalSource() instanceof RemoteFlowSource
)
}
}

private SourceNode xsjsRequestBody(DataFlow::TypeTracker t) {
t.start() and
exists(XSJSRequest dollarRequest | result = dollarRequest.getAPropertyRead("body"))
or
exists(DataFlow::TypeTracker t2 | result = xsjsRequestBody(t2).track(t2, t))
}

private SourceNode xsjsRequestBody() { result = xsjsRequestBody(DataFlow::TypeTracker::end()) }

class XSJSRequestBody extends SourceNode {
XSJSRequestBody() {
exists(SourceNode dollarRequestBody | dollarRequestBody = xsjsRequestBody() |
this = dollarRequestBody or
this = dollarRequestBody.getAMethodCall(["asArrayBuffer", "asString", "asWebRequest"])
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import javascript
import advanced_security.javascript.frameworks.xsjs.AsyncXSJS
import semmle.javascript.security.dataflow.DomBasedXssQuery as DomBasedXss

class XSJSResponseSetBodyCall extends MethodCallNode {
XSJSResponse response;

XSJSResponseSetBodyCall() {
this.getMethodName() = "setBody" and
this.getReceiver() = response
}

XSJSResponse getParentXSJSResponse() { result = response }
}

class Configuration extends TaintTracking::Configuration {
Configuration() { this = "XSJS Reflected XSS Query" }

override predicate isSource(DataFlow::Node start) {
super.isSource(start) or
start instanceof RemoteFlowSource
}

override predicate isSink(DataFlow::Node end) {
super.isSink(end)
or
exists(XSJSResponseSetBodyCall setBody, XSJSResponse thisOrAnotherXSJSResponse |
thisOrAnotherXSJSResponse = setBody.getParentXSJSResponse() or
thisOrAnotherXSJSResponse = setBody.getParentXSJSResponse().getAPredOrSuccResponse()
|
end = setBody.getArgument(0) and
(
thisOrAnotherXSJSResponse.isScriptableContentType() or
thisOrAnotherXSJSResponse.contentTypeIsDependentOnRemote()
)
)
}
}
Loading