Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
02c8eda
Add small investigative modelling for fragments - WIP
knewbury01 Dec 10, 2025
ad42e91
Fix controls in xml detection
knewbury01 Dec 16, 2025
8f80775
Add simple testcase xss fragment in html view
knewbury01 Dec 17, 2025
b0c1aef
Add Fragment load model and test cases for basic xss
knewbury01 Dec 30, 2025
530df03
Improve UI5Xss detection for fragments_samples 1
data-douser Dec 31, 2025
bb9452e
More potential detection improvements for UI5 XSS
data-douser Dec 31, 2025
97af1ac
Merge branch 'main' into knewbury01/ui5-fragments
mbaluda Jan 2, 2026
4be732a
Fix syntax error in testcases for fragments
knewbury01 Jan 5, 2026
52cab4a
Merge branch 'knewbury01/ui5-fragments' of https://github.com/advance…
knewbury01 Jan 5, 2026
8b2be19
Fix sytnax fragment testcases
knewbury01 Jan 5, 2026
e75912c
Cleanup UI5 & RemoteFlowSources qll
data-douser Jan 6, 2026
56ab432
Cleanup xss-urlparams-jsonmodel query test
data-douser Jan 6, 2026
255b3ca
Add comment to FragmentModule class def
data-douser Jan 8, 2026
caf2596
Merge branch 'knewbury01/ui5-fragments' into knewbury01/dd/ui5-fragments
data-douser Jan 8, 2026
e762b43
Merge pull request #281 from advanced-security/knewbury01/dd/ui5-frag…
knewbury01 Jan 8, 2026
3b1847d
Merge branch 'knewbury01/ui5-fragments' of https://github.com/advance…
knewbury01 Jan 8, 2026
e66d3d8
Update expected js sarif for scan
knewbury01 Jan 8, 2026
af9af3d
Fix testcase naming for views in view files
knewbury01 Jan 8, 2026
e251725
Update expected file for xss-urlparams-jsonmodel after known model up…
knewbury01 Jan 8, 2026
86fd7d5
Add modelling for binding paths and default models when those are use…
knewbury01 Jan 21, 2026
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* Provides classes for modeling the sap.ui.core.Fragment API.
*/

import javascript
import DataFlow
import advanced_security.javascript.frameworks.ui5.UI5

/**
* Gets a reference to the Fragment module import.
Comment thread
knewbury01 marked this conversation as resolved.
*/
class FragmentLoad extends InvokeNode, MethodCallNode {
FragmentLoad() {
this =
TypeTrackers::hasDependency(["sap/ui/core/Fragment", "sap.ui.core.Fragment"])
.getAMemberCall("load")
or
exists(RequiredObject requiredModule, SapDefineModule sapModule |
this = requiredModule.asSourceNode().getAMemberCall("load") and
//ensure it is an sap module define
Comment thread
knewbury01 marked this conversation as resolved.
Outdated
requiredModule.getEnclosingFunction() = sapModule.getArgument(1)
)
}

/**
* Gets the configuration object passed to Fragment.load().
*/
DataFlow::Node getConfigObject() { result = this.getArgument(0) }

/**
* Gets the 'name' property value from the config object.
* This specifies the fragment resource to load (e.g., "my.app.fragments.MyFragment").
*/
DataFlow::Node getNameArgument() {
exists(DataFlow::ObjectLiteralNode config |
config = this.getConfigObject().getALocalSource() and
result = config.getAPropertyWrite("name").getRhs()
)
}

/**
* Gets the 'controller' property value from the config object.
* This specifies the fragment's controller, if it has one.
*/
DataFlow::Node getControllerArgument() {
exists(DataFlow::ObjectLiteralNode config |
config = this.getConfigObject().getALocalSource() and
result = config.getAPropertyWrite("controller").getRhs()
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ 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
import advanced_security.javascript.frameworks.ui5.Bindings
import advanced_security.javascript.frameworks.ui5.Fragment

/**
* Gets the immediate supertype of a given type from the extensible predicate `typeModel` provided by
Expand Down Expand Up @@ -187,6 +188,17 @@ predicate isBuiltInControl(string qualifiedTypeUri) {
)
}

/**
* A UI5 Fragment that might include XSS sources and sinks in standard controls.
*/
abstract class UI5Fragment extends File {
abstract UI5Control getControl();

abstract UI5BindingPath getASource();

abstract UI5BindingPath getAnHtmlISink();
}
Comment thread
data-douser marked this conversation as resolved.
Outdated

/**
* A UI5 View that might include XSS sources and sinks in standard controls.
*/
Expand Down Expand Up @@ -683,8 +695,106 @@ class XmlView extends UI5View instanceof XmlFile {
}
}

/**
* An xml fragment. It may or may not have controllers associated.
*/
class XmlFragment extends UI5View instanceof XmlFile {
XmlRootElement root;

XmlFragment() {
root = this.getARootElement() and
(
root.getNamespace().getUri() = "sap.m"
or
root.getNamespace().getUri() = "sap.ui.core"
) and
root.hasName("FragmentDefinition")
}

override XmlBindingPath getASource() {
exists(XmlElement control, string type, string path, string property |
type = result.getControlTypeName() and
this = control.getFile() and
ApiGraphModelsExtensions::sourceModel(getASuperType(type), path, "remote", _) and
property = path.replaceAll(" ", "").regexpCapture("Member\\[([^\\]]+)\\]", 1) and
result.getBindingTarget() = control.getAttribute(property)
)
}

override XmlBindingPath getAnHtmlISink() {
exists(XmlElement control, string type, string path, string property |
this = control.getFile() and
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"
)
)
)
}

override UI5Control getControl() {
exists(XmlElement element |
result.asXmlControl() = element and
/* Use getAChild+ because some controls nest other controls inside them as aggregations */
element = root.getAChild+() and
(
/* 1. A builtin control provided by UI5 */
isBuiltInControl(element.getNamespace().getUri())
or
/* 2. A custom control with implementation code found in the webapp */
exists(CustomControl control |
control.getName() = element.getNamespace().getUri() + "." + element.getName() and
inSameWebApp(control.getFile(), element.getFile())
)
)
)
}

/**
* This is either known in the location from which `loadFragment` is called (in a controller's init function)
* OR in the optional controller param of `Fragment.load`.
* This MAY return no value, when the fragment is not associated to any controller.
* When this returns a value it is guaranteed that this xml fragment is instantiated.
*/
override string getControllerName() {
exists(CustomController controller, MethodCallNode loadFragmentCall |
loadFragmentCall.getMethodName() = "loadFragment" and
controller.getAThisNode().flowsTo(loadFragmentCall.getReceiver()) and
controller.getName() = result
)
or
exists(CustomController controller, FragmentLoad fragmentLoad |
controller.getAThisNode().flowsTo(fragmentLoad.getControllerArgument()) and
/*
* extracting just the base name of the fragment (not the fully qualified)
* otherwise difficult to know which part of absolute path is only for the qualified name
*/

fragmentLoad
.getNameArgument()
.getStringValue()
.matches("%" + this.getBaseName().replaceAll(".fragment.xml", "")) and
controller.getName() = result
)
}
}

private newtype TUI5Control =
TXmlControl(XmlElement control) or
TXmlControl(XmlElement control) {
control
.(Locatable)
.getFile()
.getBaseName()
.matches(["%.view.xml", "%.view.html", "%.fragment.xml"])
} or
TJsonControl(JsonObject control) {
exists(JsonView view | control.getParent() = view.getRoot().getPropValue("content"))
} or
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
nodes
| webapp/controller/app.controller.js:10:17:10:27 | input: null |
| webapp/view/TestFragment.fragment.xml:5:1:7:28 | value={/input} |
| webapp/view/app.view.html:4:11:4:33 | data-content={/input} |
edges
| webapp/controller/app.controller.js:10:17:10:27 | input: null | webapp/view/TestFragment.fragment.xml:5:1:7:28 | value={/input} |
| webapp/controller/app.controller.js:10:17:10:27 | input: null | webapp/view/app.view.html:4:11:4:33 | data-content={/input} |
| webapp/controller/app.controller.js:12:26:12:45 | new JSONModel(oData) | webapp/view/app.view.html:4:11:4:33 | data-content={/input} |
| webapp/view/TestFragment.fragment.xml:5:1:7:28 | value={/input} | webapp/controller/app.controller.js:10:17:10:27 | input: null |
| webapp/view/TestFragment.fragment.xml:5:1:7:28 | value={/input} | webapp/controller/app.controller.js:12:26:12:45 | new JSONModel(oData) |
| webapp/view/app.view.html:4:11:4:33 | data-content={/input} | webapp/controller/app.controller.js:10:17:10:27 | input: null |
#select
| webapp/view/app.view.html:4:11:4:33 | data-content={/input} | webapp/view/TestFragment.fragment.xml:5:1:7:28 | value={/input} | webapp/view/app.view.html:4:11:4:33 | data-content={/input} | XSS vulnerability due to $@. | webapp/view/TestFragment.fragment.xml:5:1:7:28 | value={/input} | user-provided value |
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
UI5Xss/UI5Xss.ql

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "sap-ui5-xss",
"version": "1.0.0",
"main": "index.js"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
specVersion: '3.0'
metadata:
name: sap-ui5-xss
type: application
framework:
name: SAPUI5
version: "1.115.0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/model/json/JSONModel",
"sap/ui/core/Fragment"
], function (Controller, JSONModel, Fragment) {
"use strict"
Comment thread
knewbury01 marked this conversation as resolved.
Outdated
return Controller.extend("codeql-sap-js.controller.app", {
onInit: function () {
var oData = {
input: null
};
var oModel = new JSONModel(oData);
this.getView().setModel(oModel);

Fragment.load({
name: "codeql-sap-js.view.TestFragment",
controller: this,
id: this.getView().getId()
}).then(function (oFragment) {
this.getView().addDependent(oFragment);
oFragment.placeAt("fragmentContainer");
}.bind(this));
}
});
})
Comment thread
knewbury01 marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>

<head>

<meta charset="utf-8">
<title>SAPUI5 XSS</title>
<script src="https://sdk.openui5.org/resources/sap-ui-core.js"
data-sap-ui-libs="sap.m"
data-sap-ui-onInit="module:codeql-sap-js/index"
data-sap-ui-resourceroots='{
"codeql-sap-js": "./"
}'>
</script>
</head>

<body class="sapUiBody" id="content">

</body>

</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
sap.ui.define([
"sap/ui/core/mvc/HTMLView"
], function (HTMLView) {
"use strict";
HTMLView.create({
viewName: "codeql-sap-js.view.app"
}).then(function (oView) {
oView.placeAt("content");
});

});
Comment thread
knewbury01 marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"sap.app": {
"id": "sap-ui5-xss"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<core:FragmentDefinition
xmlns="sap.m"
xmlns:core="sap.ui.core"
>
<Input xmlns="sap.m"
description="Enter Payload - Try: &lt;img src=x onerror=alert(&quot;XSS&quot;)&gt;"
value="{/input}"> <!--User input source sap.m.Input.value -->
</Input>
</core:FragmentDefinition>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<template data-controller-name="codeql-sap-js.controller.app">
<div id="fragmentContainer"></div>
<div data-sap-ui-type="sap.ui.core.HTML"
data-content="{/input}"> <!--XSS sink sap.ui.core.HTML.content -->
Comment thread Dismissed
</div>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
nodes
| webapp/controller/app.controller.js:9:17:9:27 | input: null |
| webapp/view/TestFragment.fragment.xml:5:1:7:28 | value={/input} |
| webapp/view/app.view.html:4:11:4:33 | data-content={/input} |
edges
| webapp/controller/app.controller.js:9:17:9:27 | input: null | webapp/view/TestFragment.fragment.xml:5:1:7:28 | value={/input} |
| webapp/controller/app.controller.js:9:17:9:27 | input: null | webapp/view/app.view.html:4:11:4:33 | data-content={/input} |
| webapp/controller/app.controller.js:11:26:11:45 | new JSONModel(oData) | webapp/view/app.view.html:4:11:4:33 | data-content={/input} |
| webapp/view/TestFragment.fragment.xml:5:1:7:28 | value={/input} | webapp/controller/app.controller.js:9:17:9:27 | input: null |
| webapp/view/TestFragment.fragment.xml:5:1:7:28 | value={/input} | webapp/controller/app.controller.js:11:26:11:45 | new JSONModel(oData) |
| webapp/view/app.view.html:4:11:4:33 | data-content={/input} | webapp/controller/app.controller.js:9:17:9:27 | input: null |
#select
| webapp/view/app.view.html:4:11:4:33 | data-content={/input} | webapp/view/TestFragment.fragment.xml:5:1:7:28 | value={/input} | webapp/view/app.view.html:4:11:4:33 | data-content={/input} | XSS vulnerability due to $@. | webapp/view/TestFragment.fragment.xml:5:1:7:28 | value={/input} | user-provided value |
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
UI5Xss/UI5Xss.ql

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "sap-ui5-xss",
"version": "1.0.0",
"main": "index.js"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
specVersion: '3.0'
metadata:
name: sap-ui5-xss
type: application
framework:
name: SAPUI5
version: "1.115.0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/model/json/JSONModel"
], function (Controller, JSONModel) {
"use strict"
Comment thread
knewbury01 marked this conversation as resolved.
Outdated
return Controller.extend("codeql-sap-js.controller.app", {
onInit: function () {
var oData = {
input: null
};
var oModel = new JSONModel(oData);
this.getView().setModel(oModel);

this.fragment = this.loadFragment({
name: "codeql-sap-js.view.TestFragment"
}).then(function (oFragment) {
oFragment.placeAt("fragmentContainer");
}.bind(this));
}
});
})
Comment thread
knewbury01 marked this conversation as resolved.
Outdated
Loading
Loading