Skip to content

Commit 44b5a5a

Browse files
Merge pull request #190 from advanced-security/jeongsoolee09/fix-ui5-loginjection
Remove FPs of `js/ui5-log-injection-to-http`
2 parents 9c50a3b + 584e6bc commit 44b5a5a

File tree

56 files changed

+610
-161
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+610
-161
lines changed

.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: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,9 @@ extensions:
2222
- ["Assert", "global", "Member[jQuery].Member[sap].Member[assert]"]
2323
- ["SapLogger", "sap/base/Log", ""]
2424
- ["SapLogger", "global", "Member[sap].Member[base].Member[Log]"]
25+
- ["SapLogger", "SapLogger", "Member[getLogger].ReturnValue"]
2526
- ["SapLogger", "global", "Member[jQuery].Member[sap].Member[log]"]
26-
# Logging functions as well as `getLogger` also serves as a constructor
27-
- ["SapLogger", "SapLogger", "Member[debug,error,fatal,info,setLevel,trace,warning,getLogger].ReturnValue"]
28-
- ["SapLogEntries", "SapLogger", "Member[addLogListener].Parameter[0].Member[onLogEntry].Argument[0]"]
27+
- ["SapLogEntries", "SapLogger", "Member[addLogListener].Argument[0].Member[onLogEntry].Parameter[0]"]
2928
- ["SapLogEntries", "SapLogger", "Member[getLogEntries].ReturnValue"]
3029
- ["ResourceBundle", "ResourceBundle", "Instance"]
3130
- ["ResourceBundle", "sap/base/i18n/ResourceBundle", ""]

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

Lines changed: 39 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,8 @@ abstract class UserModule extends CallExpr {
155155
class SapDefineModule extends AmdModuleDefinition::Range, MethodCallExpr, UserModule {
156156
SapDefineModule() {
157157
/*
158-
* NOTE: This only matches a call to the dot expression `sap.ui.define`, and does not consider a flow among `sap`, `ui`, and `define`.
158+
* NOTE: This only matches a call to the dot expression `sap.ui.define`, and does not
159+
* consider a flow among `sap`, `ui`, and `define`.
159160
*/
160161

161162
exists(GlobalVarAccess sap, DotExpr sapUi, DotExpr sapUiDefine |
@@ -220,50 +221,11 @@ class JQueryDefineModule extends UserModule, MethodCallExpr {
220221
}
221222
}
222223

223-
private SourceNode sapControl(TypeTracker t) {
224-
t.start() and
225-
exists(UserModule d, string dependencyType |
226-
dependencyType = ["sap/ui/core/Control", "sap.ui.core.Control"]
227-
|
228-
d.getADependency() = dependencyType and
229-
result = d.getRequiredObject(dependencyType).asSourceNode()
230-
)
231-
or
232-
exists(TypeTracker t2 | result = sapControl(t2).track(t2, t))
233-
}
234-
235-
private SourceNode sapControl() { result = sapControl(TypeTracker::end()) }
236-
237-
private SourceNode sapController(TypeTracker t) {
238-
t.start() and
239-
exists(UserModule d, string dependencyType |
240-
dependencyType = ["sap/ui/core/mvc/Controller", "sap.ui.core.mvc.Controller"]
241-
|
242-
d.getADependency() = dependencyType and
243-
result = d.getRequiredObject(dependencyType).asSourceNode()
244-
)
245-
or
246-
exists(TypeTracker t2 | result = sapController(t2).track(t2, t))
247-
}
248-
249-
private SourceNode sapController() { result = sapController(TypeTracker::end()) }
250-
251-
private SourceNode sapRenderer(TypeTracker t) {
252-
t.start() and
253-
exists(UserModule d, string dependencyType |
254-
dependencyType = ["sap/ui/core/Renderer", "sap.ui.core.Renderer"]
255-
|
256-
d.getADependency() = dependencyType and
257-
result = d.getRequiredObject(dependencyType).asSourceNode()
258-
)
259-
or
260-
exists(TypeTracker t2 | result = sapController(t2).track(t2, t))
261-
}
262-
263-
private SourceNode sapRenderer() { result = sapRenderer(TypeTracker::end()) }
264-
265-
private class Renderer extends SapExtendCall {
266-
Renderer() { this.getReceiver().getALocalSource() = sapRenderer() }
224+
class Renderer extends SapExtendCall {
225+
Renderer() {
226+
this.getReceiver().getALocalSource() =
227+
TypeTrackers::hasDependency(["sap/ui/core/Renderer", "sap.ui.core.Renderer"])
228+
}
267229

268230
FunctionNode getRenderer() {
269231
/* 1. Old API */
@@ -276,7 +238,8 @@ private class Renderer extends SapExtendCall {
276238

277239
class CustomControl extends SapExtendCall {
278240
CustomControl() {
279-
this.getReceiver().getALocalSource() = sapControl() or
241+
this.getReceiver().getALocalSource() =
242+
TypeTrackers::hasDependency(["sap/ui/core/Control", "sap.ui.core.Control"]) or
280243
exists(SapDefineModule sapModule | this.getDefine() = sapModule.getExtendingModule())
281244
}
282245

@@ -450,7 +413,8 @@ class CustomController extends SapExtendCall {
450413
string name;
451414

452415
CustomController() {
453-
this.getReceiver().getALocalSource() = sapController() and
416+
this.getReceiver().getALocalSource() =
417+
TypeTrackers::hasDependency(["sap/ui/core/mvc/Controller", "sap.ui.core.mvc.Controller"]) and
454418
name = this.getFile().getBaseName().regexpCapture("([a-zA-Z0-9]+).[cC]ontroller.js", 1)
455419
}
456420

@@ -772,35 +736,24 @@ abstract class UI5InternalModel extends UI5Model, NewNode {
772736
abstract string getPathString(Property property);
773737
}
774738

775-
/**
776-
* Represents models that are loaded from an external source, e.g. OData service.
777-
* It is the value flowing to a `setModel` call in a handler of a `CustomController` (which is represented by `ControllerHandler`), since it is the closest we can get to the actual model itself.
778-
*/
779-
private SourceNode sapComponent(TypeTracker t) {
780-
t.start() and
781-
exists(UserModule d, string dependencyType |
782-
dependencyType =
783-
[
784-
"sap/ui/core/mvc/Component", "sap.ui.core.mvc.Component", "sap/ui/core/UIComponent",
785-
"sap.ui.core.UIComponent"
786-
]
787-
|
788-
d.getADependency() = dependencyType and
789-
result = d.getRequiredObject(dependencyType).asSourceNode()
790-
)
791-
or
792-
exists(TypeTracker t2 | result = sapComponent(t2).track(t2, t))
793-
}
794-
795-
private SourceNode sapComponent() { result = sapComponent(TypeTracker::end()) }
796-
797739
import ManifestJson
798740

799741
/**
800742
* A UI5 Component that may contain other controllers or controls.
801743
*/
802744
class Component extends SapExtendCall {
803-
Component() { this.getReceiver().getALocalSource() = sapComponent() }
745+
Component() {
746+
this.getReceiver().getALocalSource() =
747+
/*
748+
* Represents models that are loaded from an external source, e.g. OData service.
749+
* It is the value flowing to a `setModel` call in a handler of a `CustomController` (which is represented by `ControllerHandler`), since it is the closest we can get to the actual model itself.
750+
*/
751+
752+
TypeTrackers::hasDependency([
753+
"sap/ui/core/mvc/Component", "sap.ui.core.mvc.Component", "sap/ui/core/UIComponent",
754+
"sap.ui.core.UIComponent"
755+
])
756+
}
804757

805758
string getId() { result = this.getName().regexpCapture("([a-zA-Z0-9.]+).Component", 1) }
806759

@@ -1490,3 +1443,19 @@ class PropertyMetadata extends ObjectLiteralNode {
14901443
inSameWebApp(this.getFile(), result.getFile())
14911444
}
14921445
}
1446+
1447+
module TypeTrackers {
1448+
private SourceNode hasDependency(TypeTracker t, string dependencyPath) {
1449+
t.start() and
1450+
exists(UserModule d |
1451+
d.getADependency() = dependencyPath and
1452+
result = d.getRequiredObject(dependencyPath).asSourceNode()
1453+
)
1454+
or
1455+
exists(TypeTracker t2 | result = hasDependency(t2, dependencyPath).track(t2, t))
1456+
}
1457+
1458+
SourceNode hasDependency(string dependencyPath) {
1459+
result = hasDependency(TypeTracker::end(), dependencyPath)
1460+
}
1461+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import javascript
2+
import advanced_security.javascript.frameworks.ui5.dataflow.DataFlow
3+
private import advanced_security.javascript.frameworks.ui5.dataflow.DataFlow::UI5PathGraph
4+
import semmle.javascript.security.dataflow.LogInjectionQuery as LogInjection
5+
6+
class UI5LogInjectionConfiguration extends LogInjection::LogInjectionConfiguration {
7+
override predicate isSource(DataFlow::Node node) { node instanceof RemoteFlowSource }
8+
9+
override predicate isSink(DataFlow::Node node) {
10+
node = ModelOutput::getASinkNode("ui5-log-injection").asSink()
11+
}
12+
}

javascript/frameworks/ui5/src/UI5LogInjection/UI5LogInjection.ql

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,8 @@
1212
*/
1313

1414
import javascript
15-
import advanced_security.javascript.frameworks.ui5.dataflow.DataFlow
15+
import advanced_security.javascript.frameworks.ui5.UI5LogInjectionQuery
1616
import advanced_security.javascript.frameworks.ui5.dataflow.DataFlow::UI5PathGraph
17-
import semmle.javascript.security.dataflow.LogInjectionQuery as LogInjection
18-
19-
class UI5LogInjectionConfiguration extends LogInjection::LogInjectionConfiguration {
20-
override predicate isSource(DataFlow::Node node) { node instanceof RemoteFlowSource }
21-
22-
override predicate isSink(DataFlow::Node node) {
23-
node = ModelOutput::getASinkNode("ui5-log-injection").asSink()
24-
}
25-
}
2617

2718
from
2819
UI5LogInjectionConfiguration cfg, UI5PathNode source, UI5PathNode sink, UI5PathNode primarySource

javascript/frameworks/ui5/src/UI5LogInjection/UI5LogsToHttp.ql

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,66 @@
1313

1414
import javascript
1515
import advanced_security.javascript.frameworks.ui5.dataflow.DataFlow
16+
import semmle.javascript.frameworks.data.internal.ApiGraphModels
17+
import advanced_security.javascript.frameworks.ui5.UI5LogInjectionQuery
1618
import advanced_security.javascript.frameworks.ui5.dataflow.DataFlow::UI5PathGraph
17-
import semmle.javascript.security.dataflow.LogInjectionQuery as LogInjection
1819

19-
class UI5LogInjectionConfiguration extends LogInjection::LogInjectionConfiguration {
20-
override predicate isSource(DataFlow::Node node) { node instanceof RemoteFlowSource }
21-
22-
override predicate isSink(DataFlow::Node node) {
20+
class ClientRequestInjectionVector extends DataFlow::Node {
21+
ClientRequestInjectionVector() {
2322
exists(ClientRequest req |
24-
node = req.getUrl() or
25-
node = req.getADataNode()
23+
this = req.getUrl() or
24+
this = req.getADataNode()
2625
)
2726
}
2827
}
2928

30-
from
31-
UI5LogInjectionConfiguration cfg, UI5PathNode source, UI5PathNode sink, UI5PathNode primarySource
29+
class UI5LogEntryFlowState extends DataFlow::FlowLabel {
30+
UI5LogEntryFlowState() { this = ["not-logged-not-accessed", "logged-and-accessed"] }
31+
}
32+
33+
class UI5LogEntryToHttp extends TaintTracking::Configuration {
34+
UI5LogEntryToHttp() { this = "UI5 Log Entry included in an outbound HTTP request" }
35+
36+
override predicate isSource(DataFlow::Node node, DataFlow::FlowLabel state) {
37+
node instanceof RemoteFlowSource and
38+
state = "not-logged-not-accessed"
39+
}
40+
41+
override predicate isAdditionalFlowStep(
42+
DataFlow::Node start, DataFlow::Node end, DataFlow::FlowLabel preState,
43+
DataFlow::FlowLabel postState
44+
) {
45+
exists(UI5LogInjectionConfiguration cfg |
46+
cfg.isAdditionalFlowStep(start, end) and
47+
preState = postState
48+
)
49+
or
50+
/*
51+
* NOTE: This disjunct is a labeled version of LogArgumentToListener in
52+
* FlowSteps.qll, a DataFlow::SharedFlowStep. As the class is considered
53+
* legacy on version 2.4.0, we leave the two here (labeled) and there
54+
* (unlabeled). This is something we should also tidy up when we migrate
55+
* to the newer APIs.
56+
*/
57+
58+
inSameWebApp(start.getFile(), end.getFile()) and
59+
start =
60+
ModelOutput::getATypeNode("SapLogger")
61+
.getMember(["debug", "error", "fatal", "info", "trace", "warning"])
62+
.getACall()
63+
.getAnArgument() and
64+
end = ModelOutput::getATypeNode("SapLogEntries").asSource() and
65+
preState = "not-logged-not-accessed" and
66+
postState = "logged-and-accessed"
67+
}
68+
69+
override predicate isSink(DataFlow::Node node, DataFlow::FlowLabel state) {
70+
node instanceof ClientRequestInjectionVector and
71+
state = "logged-and-accessed"
72+
}
73+
}
74+
75+
from UI5LogEntryToHttp cfg, UI5PathNode source, UI5PathNode sink, UI5PathNode primarySource
3276
where
3377
cfg.hasFlowPath(source.getPathNode(), sink.getPathNode()) and
3478
primarySource = source.getAPrimarySource()

javascript/frameworks/ui5/test/queries/UI5LogInjection/avoid-duplicate-alerts/UI5LogInjection.expected

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
WARNING: type 'LogInjectionConfiguration' has been deprecated and may be removed in future (UI5LogInjection.ql:19,44-83)
21
nodes
32
| LogInjectionTest.js:6:9:6:50 | value |
43
| LogInjectionTest.js:6:17:6:50 | jQuery. ... param") |

javascript/frameworks/ui5/test/queries/UI5LogInjection/log-custom-control-property-sanitized/UI5LogInjection.expected

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
WARNING: type 'LogInjectionConfiguration' has been deprecated and may be removed in future (UI5LogInjection.ql:19,44-83)
21
nodes
32
| webapp/control/xss.js:7:23:7:37 | { type: "int" } |
43
| webapp/control/xss.js:13:38:13:55 | oControl.getText() |

javascript/frameworks/ui5/test/queries/UI5LogInjection/log-custom-control-sanitized/UI5LogInjection.expected

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
WARNING: type 'LogInjectionConfiguration' has been deprecated and may be removed in future (UI5LogInjection.ql:19,44-83)
21
nodes
32
| webapp/control/xss.js:8:23:8:40 | { type: "string" } |
43
| webapp/control/xss.js:15:21:15:46 | value |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
UI5LogInjection/UI5LogsToHttp.ql

javascript/frameworks/ui5/test/queries/UI5LogInjection/log-entry-flows-to-http-getLogEntries/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.
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+
}
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"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
sap.ui.define(
2+
[
3+
"sap/ui/core/mvc/Controller",
4+
"sap/ui/model/json/JSONModel",
5+
],
6+
function (Controller, JSONModel) {
7+
"use strict";
8+
return Controller.extend("codeql-sap-js.controller.app", {
9+
onInit: function () {
10+
var oData = {
11+
input: null,
12+
output: null,
13+
};
14+
var oModel = new JSONModel(oData);
15+
this.getView().setModel(oModel);
16+
17+
var input = oModel.getProperty("/input");
18+
jQuery.sap.log.debug(input); //log-injection sink
19+
},
20+
});
21+
},
22+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<!DOCTYPE html>
2+
<html>
3+
4+
<head>
5+
6+
<meta charset="utf-8">
7+
<title>SAPUI5 XSS</title>
8+
<script src="https://sdk.openui5.org/resources/sap-ui-core.js"
9+
data-sap-ui-libs="sap.m"
10+
data-sap-ui-onInit="module:codeql-sap-js/index"
11+
data-sap-ui-resourceroots='{
12+
"codeql-sap-js": "./"
13+
}'>
14+
</script>
15+
</head>
16+
17+
<body class="sapUiBody" id="content">
18+
19+
</body>
20+
21+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
sap.ui.define([
2+
"sap/ui/core/mvc/XMLView"
3+
], function (XMLView) {
4+
"use strict";
5+
XMLView.create({
6+
viewName: "codeql-sap-js.view.app"
7+
}).then(function (oView) {
8+
oView.placeAt("content");
9+
});
10+
11+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"sap.app": {
3+
"id": "sap-ui5-xss"
4+
}
5+
}

0 commit comments

Comments
 (0)