Skip to content

Commit 7e2b76c

Browse files
authored
Merge pull request #278 from advanced-security/mbaluda/get-sanitized-content
Model DF edge for sanitized HTML setContent->getContent
2 parents faa4f7c + 316aae3 commit 7e2b76c

14 files changed

Lines changed: 161 additions & 8 deletions

File tree

.github/workflows/javascript.sarif.expected

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

javascript/frameworks/ui5/ext/ui5.model.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ extensions:
66
- ["SapUICoreInstance", "global", "Member[sap].Member[ui].Member[getCore].ReturnValue"]
77
- ["Control", "Control", "Instance"]
88
- ["Control", "sap/ui/core/Control", ""]
9+
- ["Control", "UI5HTMLControl", ""]
10+
- ["Control", "UI5InputControl", ""]
11+
- ["Control", "CustomControl", ""]
912
- ["Control", "global", "Member[sap].Member[ui].Member[core].Member[Control]"]
1013
- ["Controller", "Controller", "Instance"]
1114
- ["Controller", "sap/ui/core/mvc/Controller", ""]
@@ -112,6 +115,7 @@ extensions:
112115
data:
113116
- ["UI5InputControl", "Member[value]", "remote"]
114117
- ["UI5InputControl", "Member[getValue].ReturnValue", "remote"]
118+
- ["UI5HTMLControl", "Member[getContent].ReturnValue", "remote"]
115119
- ["UI5CodeEditor", "Member[value]", "remote"]
116120
- ["UI5CodeEditor", "Member[getCurrentValue].ReturnValue", "remote"]
117121
- ["global", "Member[jQuery].Member[sap].Member[syncHead,syncGet,syncGetText,syncPost,syncPostText].ReturnValue", "remote"]

javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/UI5View.qll

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,8 @@ private newtype TUI5Control =
805805
.(ArrayLiteralNode)
806806
.asExpr()
807807
)
808+
or
809+
control = ModelOutput::getATypeNode("Control").getAnInvocation()
808810
}
809811

810812
class UI5Control extends TUI5Control {
@@ -852,7 +854,9 @@ class UI5Control extends TUI5Control {
852854
)
853855
or
854856
exists(NewNode control | control = this.asJsControl() |
855-
result = this.asJsControl().asExpr().getAChildExpr().(DotExpr).getQualifiedName()
857+
result = control.asExpr().getAChildExpr().(PropAccess).getQualifiedName()
858+
or
859+
control = API::moduleImport(result).getAnInvocation()
856860
)
857861
}
858862

@@ -988,9 +992,12 @@ class UI5Control extends TUI5Control {
988992
)
989993
or
990994
/* 3. `sanitizeContent` attribute is set programmatically using a setter. */
991-
exists(CallNode node |
992-
node = this.getAReference().getAMemberCall("setS" + propName.suffix(1)) and
995+
exists(CallNode node, string setterName |
996+
setterName = "set" + propName.prefix(1).toUpperCase() + propName.suffix(1) and
993997
not node.getArgument(0).mayHaveBooleanValue(val.booleanNot())
998+
|
999+
node = this.getAReference().getAMemberCall(setterName) or
1000+
node = this.asJsControl().getAMemberCall(setterName)
9941001
)
9951002
}
9961003
}

javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/UI5XssQuery.qll

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ module UI5Xss implements DataFlow::ConfigSig {
3131
node.(DataFlow::CallNode).getReceiver().asExpr().(PropAccess).getQualifiedName() = "jQuery.sap" and
3232
node.(DataFlow::CallNode).getCalleeName() =
3333
["encodeCSS", "encodeJS", "encodeURL", "encodeURLParameters", "encodeXML", "encodeHTML"]
34+
or
35+
/* Block flow through setContent/getContent of a sanitized UI5Control */
36+
exists(UI5Control control, DataFlow::MethodCallNode content |
37+
control.asJsControl() = content.getReceiver().getALocalSource() and
38+
control.isHTMLSanitized() and
39+
content.getMethodName() = ["setContent", "getContent"] and
40+
node = [content, content.getArgument(0)]
41+
)
3442
}
3543

3644
predicate isSink(DataFlow::Node node) {
@@ -56,6 +64,19 @@ module UI5Xss implements DataFlow::ConfigSig {
5664

5765
end = h.getParameter(0)
5866
)
67+
or
68+
/* Flow from `setContent` to `getContent` of a control */
69+
exists(
70+
UI5Control control, DataFlow::MethodCallNode getContent, DataFlow::MethodCallNode setContent
71+
|
72+
control.asJsControl() = setContent.getReceiver().getALocalSource() and
73+
control.asJsControl() = getContent.getReceiver().getALocalSource() and
74+
setContent.getMethodName() = "setContent" and
75+
getContent.getMethodName() = "getContent" and
76+
start = setContent.getArgument(0) and
77+
end = getContent and
78+
not control.isHTMLSanitized()
79+
)
5980
}
6081
}
6182

@@ -93,10 +114,6 @@ private class UI5ExtHtmlISink extends DataFlow::Node {
93114
}
94115
}
95116

96-
private class HTMLControlInstantiation extends ElementInstantiation {
97-
HTMLControlInstantiation() { typeModel("UI5HTMLControl", this.getImportPath(), _) }
98-
}
99-
100117
private module TrackPlaceAtCallConfigFlow = TaintTracking::Global<TrackPlaceAtCallConfig>;
101118

102119
abstract class DynamicallySetElementValueOfHTML extends DataFlow::Node { }
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
nodes
2+
| webapp/controller/app.controller.js:10:17:10:27 | input: null |
3+
| webapp/controller/app.controller.js:16:35:16:62 | oModel. ... input') |
4+
| webapp/controller/app.controller.js:19:36:19:63 | oModel. ... input') |
5+
| webapp/controller/app.controller.js:20:35:20:58 | unsanit ... ntent() |
6+
| webapp/view/app.view.xml:5:5:7:28 | value={/input} |
7+
| webapp/view/app.view.xml:8:5:8:79 | content={/output} |
8+
edges
9+
| webapp/controller/app.controller.js:10:17:10:27 | input: null | webapp/controller/app.controller.js:16:35:16:62 | oModel. ... input') |
10+
| webapp/controller/app.controller.js:10:17:10:27 | input: null | webapp/controller/app.controller.js:19:36:19:63 | oModel. ... input') |
11+
| webapp/controller/app.controller.js:10:17:10:27 | input: null | webapp/view/app.view.xml:5:5:7:28 | value={/input} |
12+
| webapp/controller/app.controller.js:11:17:11:28 | output: null | webapp/view/app.view.xml:8:5:8:79 | content={/output} |
13+
| webapp/controller/app.controller.js:13:26:13:45 | new JSONModel(oData) | webapp/view/app.view.xml:8:5:8:79 | content={/output} |
14+
| webapp/controller/app.controller.js:19:36:19:63 | oModel. ... input') | webapp/controller/app.controller.js:20:35:20:58 | unsanit ... ntent() |
15+
| webapp/view/app.view.xml:5:5:7:28 | value={/input} | webapp/controller/app.controller.js:10:17:10:27 | input: null |
16+
| webapp/view/app.view.xml:5:5:7:28 | value={/input} | webapp/controller/app.controller.js:13:26:13:45 | new JSONModel(oData) |
17+
| webapp/view/app.view.xml:8:5:8:79 | content={/output} | webapp/controller/app.controller.js:11:17:11:28 | output: null |
18+
#select
19+
| webapp/controller/app.controller.js:16:35:16:62 | oModel. ... input') | webapp/view/app.view.xml:5:5:7:28 | value={/input} | webapp/controller/app.controller.js:16:35:16:62 | oModel. ... input') | XSS vulnerability due to $@. | webapp/view/app.view.xml:5:5:7:28 | value={/input} | user-provided value |
20+
| webapp/controller/app.controller.js:19:36:19:63 | oModel. ... input') | webapp/view/app.view.xml:5:5:7:28 | value={/input} | webapp/controller/app.controller.js:19:36:19:63 | oModel. ... input') | XSS vulnerability due to $@. | webapp/view/app.view.xml:5:5:7:28 | value={/input} | user-provided value |
21+
| webapp/controller/app.controller.js:20:35:20:58 | unsanit ... ntent() | webapp/controller/app.controller.js:20:35:20:58 | unsanit ... ntent() | webapp/controller/app.controller.js:20:35:20:58 | unsanit ... ntent() | XSS vulnerability due to $@. | webapp/controller/app.controller.js:20:35:20:58 | unsanit ... ntent() | user-provided value |
22+
| webapp/controller/app.controller.js:20:35:20:58 | unsanit ... ntent() | webapp/view/app.view.xml:5:5:7:28 | value={/input} | webapp/controller/app.controller.js:20:35:20:58 | unsanit ... ntent() | XSS vulnerability due to $@. | webapp/view/app.view.xml:5:5:7:28 | value={/input} | user-provided value |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
UI5Xss/UI5Xss.ql

javascript/frameworks/ui5/test/queries/UI5Xss/xss-html-control-sanitized/package-lock.json

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "sap-ui5-xss",
3+
"version": "1.0.0",
4+
"main": "index.js"
5+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
specVersion: '3.0'
2+
metadata:
3+
name: sap-ui5-xss
4+
type: application
5+
framework:
6+
name: SAPUI5
7+
version: "1.115.0"
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
sap.ui.define([
2+
"sap/ui/core/mvc/Controller",
3+
"sap/ui/model/json/JSONModel",
4+
"sap/ui/core/HTML"
5+
], function (Controller, JSONModel, HTML) {
6+
"use strict";
7+
return Controller.extend("codeql-sap-js.controller.app", {
8+
onInit: function () {
9+
var oData = {
10+
input: null,
11+
output: null,
12+
};
13+
var oModel = new JSONModel(oData);
14+
this.getView().setModel(oModel);
15+
16+
jQuery.sap.globalEval(oModel.getProperty('/input')); // UNSAFE: evaluating user input
17+
18+
var unsanitized = new HTML();
19+
unsanitized.setContent(oModel.getProperty('/input')); // UNSAFE: evaluating user input
20+
jQuery.sap.globalEval(unsanitized.getContent()); // UNSAFE: setContent->getContent flow step
21+
22+
var sanitized = new HTML();
23+
sanitized.setSanitizeContent(true);
24+
sanitized.setContent(oModel.getProperty('/input')); // SAFE: content is sanitized before eval
25+
jQuery.sap.globalEval(sanitized.getContent()); // SAFE: content is sanitized before eval
26+
27+
let htmlSanitized = this.getView().byId("htmlSanitized");
28+
jQuery.sap.globalEval(htmlSanitized.getContent()); // SAFE: content is sanitized declaratively in the view
29+
}
30+
});
31+
}
32+
);

0 commit comments

Comments
 (0)