Skip to content
Open
Show file tree
Hide file tree
Changes from 14 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
4 changes: 2 additions & 2 deletions .github/workflows/code_scanning.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:
- name: Initialize CodeQL
id: initialize-codeql
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v4
env:
# Add our custom extractor to the CodeQL search path
CODEQL_ACTION_EXTRA_OPTIONS: '{"database":{"init":["--search-path","${{ github.workspace }}/extractors"]}}'
Expand All @@ -62,7 +62,7 @@ jobs:
- name: Perform CodeQL Analysis
id: analyze
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v4
env:
LGTM_INDEX_XML_MODE: all
LGTM_INDEX_FILETYPES: ".json:JSON"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/javascript.sarif.expected

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import javascript
import DataFlow
import advanced_security.javascript.frameworks.ui5.UI5
import advanced_security.javascript.frameworks.ui5.dataflow.DataFlow
private import semmle.javascript.frameworks.data.internal.ApiGraphModelsExtensions as ApiGraphModelsExtensions
Expand Down Expand Up @@ -648,16 +646,7 @@ class XmlView extends UI5View instanceof XmlFile {
type = result.getControlTypeName() and
ApiGraphModelsExtensions::sinkModel(getASuperType(type), path, "ui5-html-injection", _) and
property = path.replaceAll(" ", "").regexpCapture("Member\\[([^\\]]+)\\]", 1) and
result.getBindingTarget() = control.getAttribute(property) and
/* If the control is an `sap.ui.core.HTML` then the control should be missing the `sanitizeContent` attribute */
(
getASuperType(type) = "HTMLControl"
implies
(
not exists(control.getAttribute("sanitizeContent")) or
control.getAttribute("sanitizeContent").getValue() = "false"
)
)
result.getBindingTarget() = control.getAttribute(property)
)
}

Expand Down Expand Up @@ -823,7 +812,7 @@ class UI5Control extends TUI5Control {
}

/** Holds if this control reads from or writes to a model. */
predicate accessesModel(UI5Model model) { accessesModel(model, _) }
predicate accessesModel(UI5Model model) { this.accessesModel(model, _) }

/** Holds if this control reads from or writes to a model with regards to a binding path. */
predicate accessesModel(UI5Model model, XmlBindingPath bindingPath) {
Expand All @@ -842,6 +831,38 @@ class UI5Control extends TUI5Control {

/** Get the controller that manages this control. */
CustomController getController() { result = this.getView().getController() }

/**
* Gets the full import path of the associated control.
*/
string getControlTypeName() { result = this.getQualifiedType().replaceAll(".", "/") }

/**
* Holds if the attribute `sanitizeContent`
* in controls `sap.ui.core.HTML` and `sap.ui.richttexteditor.RichTextEditor`
* is set to true and never set to false anywhere
*/
predicate isSanitizedControl() {
not this = this.sanitizeContentSetTo(false) and
(
this.getControlTypeName() = "sap/ui/richttexteditor/RichTextEditor"
or
this.getControlTypeName() = "sap/ui/core/HTML" and
this = this.sanitizeContentSetTo(true)
)
}

bindingset[val]
private UI5Control sanitizeContentSetTo(boolean val) {
/* 1. `sanitizeContent` attribute is set declaratively. */
result.getProperty("sanitizeContent").toString() = val.toString()
or
/* 2. `sanitizeContent` attribute is set programmatically using setProperty(). */
exists(CallNode node | node = result.getAReference().getAMemberCall("setProperty") |
node.getArgument(0).getStringValue() = "sanitizeContent" and
not node.getArgument(1).mayHaveBooleanValue(val.booleanNot())
)
}
}

private newtype TUI5ControlProperty =
Expand All @@ -857,7 +878,7 @@ class UI5ControlProperty extends TUI5ControlProperty {
ValueNode asJsControlProperty() { this = TJsControlProperty(result) }

string toString() {
result = this.asXmlControlProperty().toString() or
result = this.asXmlControlProperty().getValue().toString() or
result = this.asJsonControlProperty().toString() or
result = this.asJsControlProperty().toString()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ module UI5PathGraph<PathNodeSig ConfigPathNode, PathGraphSig<ConfigPathNode> Con
}

UI5PathNode getAPrimaryHtmlISink() {
not result.asUI5BindingPathNode().getControlDeclaration().isSanitizedControl() and
if
this.asDataFlowNode() instanceof LocalModelContentBoundBidirectionallyToHtmlISinkControl or
this.asDataFlowNode() instanceof UI5ExternalModel // TODO: Narrow it down to ExternalModelBoundToHtmlISinkControl
Expand Down
6 changes: 5 additions & 1 deletion javascript/frameworks/ui5/test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ User input flows to XSS sinks via event handlers in 4 different ways:

### [xss-html-control](queries/UI5Xss/xss-html-control)
- `sap.ui.core.HTML` Control
- sanitization using the `sanitizeContent` property
- sanitization disabled by programmatically setting the `sanitizeContent` property to false

### [xss-html-control-df](queries/UI5Xss/xss-html-control-df)
- `sap.ui.core.HTML` Control
Expand All @@ -57,15 +59,17 @@ User input flows to XSS sinks via event handlers in 4 different ways:

### [xss-html-view](queries/UI5Xss/xss-html-view)
- `sap.ui.core.mvc.HTMLView` View
-

### [xss-indirect-control](queries/UI5Xss/xss-indirect-control)
- control accessed indirectly

### [xss-js-view](queries/UI5Xss/xss-js-view)
- `sap.ui.core.mvc.JSView` View
- sanitization using the `sanitizeContent` property

### [xss-json-view](queries/UI5Xss/xss-json-view)
- `sap.ui.core.mvc.JSONView` View
- sanitization using the `sanitizeContent` property

### [xss-separate-renderer](queries/UI5Xss/xss-separate-renderer)
- `renderer` property is set to a class name (a string)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ import javascript
import advanced_security.javascript.frameworks.ui5.UI5View

from UI5BindingPath bp
where bp = any(UI5View ui5v).getAnHtmlISink()
where
bp = any(UI5View ui5v).getAnHtmlISink() and
not bp.getControlDeclaration().isSanitizedControl()
select bp, "The binding path `" + bp.toString() + "` is an HTML injection sink."
1 change: 1 addition & 0 deletions javascript/frameworks/ui5/test/models/sink/sink1.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
xmlns:core="sap.ui.core"
xmlns:mvc="sap.ui.core.mvc">
<core:HTML content="{path: '/input'}"/> <!--XSS sink sap.ui.core.HTML.content -->
<core:HTML content="{path: '/input'}" sanitizeContent="true"/> <!--sanitized XSS sink sap.ui.core.HTML.content -->
</mvc:View>
Loading