Skip to content

Commit 36c0485

Browse files
Merge pull request #66 from advanced-security/rvermeulen/ui5-bootstrap-detection
Add UI5 web app detection
2 parents 426f1d6 + 5f94964 commit 36c0485

File tree

9 files changed

+828
-80
lines changed

9 files changed

+828
-80
lines changed

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

+573
Large diffs are not rendered by default.

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

+130-31
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,139 @@
1-
private import javascript
1+
private import javascript
22
private import DataFlow
3+
private import advanced_security.javascript.frameworks.ui5.JsonParser
34
private import semmle.javascript.security.dataflow.DomBasedXssCustomizations
45
private import advanced_security.javascript.frameworks.ui5.UI5View
6+
private import advanced_security.javascript.frameworks.ui5.UI5HTML
57

68
module UI5 {
7-
/**
8-
* Helper predicate checking if two elements are in the same Project
9-
*/
10-
predicate inSameUI5Project(File f1, File f2) {
11-
exists(Project p | p.isInThisProject(f1) and p.isInThisProject(f2))
9+
private class ResourceRootPathString extends PathString {
10+
SapUiCoreScriptElement coreScript;
11+
12+
ResourceRootPathString() { this = coreScript.getAResourceRoot().getRoot() }
13+
14+
override Folder getARootFolder() { result = coreScript.getFile().getParentContainer() }
1215
}
1316

14-
class Project extends Folder {
15-
/**
16-
* An UI5 project root folder.
17-
*/
18-
Project() { exists(File yamlFile | yamlFile = this.getFile("ui5.yaml")) }
17+
private newtype TResourceRoot =
18+
MkResourceRoot(string name, string root, string source) {
19+
exists(
20+
JsonParser<getAResourceRootConfig/0>::JsonObject config,
21+
JsonParser<getAResourceRootConfig/0>::JsonMember configEntry, SapUiCoreScriptElement coreScript
22+
|
23+
source = coreScript.getAttributeByName("data-sap-ui-resourceroots").getValue() and
24+
source = config.getSource() and
25+
config.getAMember() = configEntry
26+
|
27+
name = configEntry.getKey() and
28+
root = configEntry.getValue().asString()
29+
)
30+
}
31+
32+
class ResourceRoot extends TResourceRoot, MkResourceRoot {
33+
string getName() { this = MkResourceRoot(result, _, _) }
34+
35+
string getRoot() { this = MkResourceRoot(_, result, _) }
36+
37+
string getSource() { this = MkResourceRoot(_, _, result) }
38+
39+
string toString() { result = this.getName() + ": " + this.getRoot() }
40+
}
41+
42+
class ResolvedResourceRoot extends Container {
43+
ResourceRoot unresolvedRoot;
44+
ResolvedResourceRoot() {
45+
exists(ResourceRootPathString resourceRootPathString | unresolvedRoot.getRoot() = resourceRootPathString |
46+
this = resourceRootPathString.resolve(resourceRootPathString.getARootFolder()).getContainer())
47+
}
48+
49+
string getName() {
50+
result = unresolvedRoot.getName()
51+
}
52+
53+
string getSource() {
54+
result = unresolvedRoot.getSource()
55+
}
56+
57+
predicate contains(File file) {
58+
file.getParentContainer+() = this
59+
}
60+
}
61+
62+
private string getAResourceRootConfig() {
63+
result = any(SapUiCoreScriptElement script).getAttributeByName("data-sap-ui-resourceroots").getValue()
64+
}
65+
66+
class SapUiCoreScriptElement extends HTML::ScriptElement {
67+
SapUiCoreScriptElement() {
68+
this.getSourcePath().matches(["%sap-ui-core.js", "%sap-ui-core-nojQuery.js"])
69+
}
70+
71+
ResourceRoot getAResourceRoot() {
72+
result.getSource() = this.getAttributeByName("data-sap-ui-resourceroots").getValue()
73+
}
74+
75+
ResolvedResourceRoot getAResolvedResourceRoot() {
76+
result.getSource() = this.getAttributeByName("data-sap-ui-resourceroots").getValue()
77+
}
78+
}
79+
80+
/** A UI5 web application manifest associated with a bootstrapped UI5 web application. */
81+
class WebAppManifest extends File {
82+
WebApp webapp;
83+
84+
WebAppManifest() {
85+
this.getBaseName() = "manifest.json" and
86+
this.getParentContainer() = webapp.getWebAppFolder()
87+
}
88+
89+
WebApp getWebapp() { result = webapp }
90+
}
91+
92+
/** A UI5 bootstrapped web application. */
93+
class WebApp extends HTML::HtmlFile {
94+
SapUiCoreScriptElement coreScript;
95+
96+
WebApp() { coreScript.getFile() = this }
97+
98+
File getAResource() { coreScript.getAResolvedResourceRoot().contains(result) }
99+
100+
File getResource(string path) {
101+
getWebAppFolder().getAbsolutePath() + "/" + path = result.getAbsolutePath()
102+
}
103+
104+
Folder getWebAppFolder() { result = this.getParentContainer() }
105+
106+
WebAppManifest getManifest() { result.getWebapp() = this }
19107

20108
/**
21-
* The `ui5.yaml` file that declares a UI5 application.
109+
* Gets the JavaScript module that serves as an entrypoint to this webapp.
22110
*/
23-
File getProjectYaml() { result = this.getFile("ui5.yaml") }
24-
25-
predicate isInThisProject(File file) { this = file.getParentContainer*() }
111+
File getInitialModule() {
112+
exists(
113+
string initialModuleResourcePath, string resolvedModulePath,
114+
ResolvedResourceRoot resourceRoot
115+
|
116+
initialModuleResourcePath = coreScript.getAttributeByName("data-sap-ui-onInit").getValue() and
117+
coreScript.getAResolvedResourceRoot() = resourceRoot and
118+
resolvedModulePath =
119+
initialModuleResourcePath
120+
.regexpReplaceAll("^module\\s*:\\s*", "")
121+
.replaceAll(resourceRoot.getName(), resourceRoot.getAbsolutePath()) and
122+
result.getAbsolutePath() = resolvedModulePath + ".js"
123+
)
124+
}
26125

27-
private HTML::HtmlFile getSapUICoreScript() {
28-
exists(HTML::ScriptElement script |
29-
result = script.getFile() and
30-
this.isInThisProject(result) and
31-
script.getSourcePath().matches("%/sap-ui-core.js")
126+
FrameOptions getFrameOptions() {
127+
exists(HTML::DocumentElement doc | doc.getFile() = this |
128+
result.asHtmlFrameOptions() = coreScript.getAnAttribute()
32129
)
130+
or
131+
result.asJsFrameOptions().getFile() = this
33132
}
34133

35-
HTML::HtmlFile getMainHTML() { result = this.getSapUICoreScript() }
134+
HTML::DocumentElement getDocument() {
135+
result.getFile() = this
136+
}
36137
}
37138

38139
/**
@@ -76,7 +177,7 @@ module UI5 {
76177
)
77178
}
78179

79-
Project getProject() { result = this.getFile().getParentContainer*() }
180+
WebApp getWebApp() { this.getFile() = result.getAResource() }
80181

81182
SapDefineModule getExtendingDefine() {
82183
exists(Extension baseExtension, Extension subclassExtension, SapDefineModule subclassDefine |
@@ -291,13 +392,7 @@ module UI5 {
291392
*/
292393
bindingset[path]
293394
JsonObject resolveDirectPath(string path) {
294-
exists(Project project, File jsonFile |
295-
// project contains this file
296-
project.isInThisProject(jsonFile) and
297-
jsonFile.getExtension() = "json" and
298-
jsonFile.getAbsolutePath() = project.getASubFolder().getAbsolutePath() + "/" + path and
299-
result.getJsonFile() = jsonFile
300-
)
395+
exists(WebApp webApp | result.getJsonFile() = webApp.getResource(path))
301396
}
302397

303398
/**
@@ -502,14 +597,18 @@ module UI5 {
502597
result.getMethodName() = "setProperty" and
503598
result.getArgument(0).asExpr().(StringLiteral).getValue() = propName and
504599
// TODO: in same controller
505-
inSameUI5Project(this.getFile(), result.getFile())
600+
exists(WebApp webApp |
601+
webApp.getAResource() = this.getFile() and webApp.getAResource() = result.getFile()
602+
)
506603
}
507604

508605
bindingset[propName]
509606
MethodCallNode getARead(string propName) {
510607
result.getMethodName() = "get" + capitalize(propName) and
511608
// TODO: in same controller
512-
inSameUI5Project(this.getFile(), result.getFile())
609+
exists(WebApp webApp |
610+
webApp.getAResource() = this.getFile() and webApp.getAResource() = result.getFile()
611+
)
513612
}
514613
}
515614
}

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

+22-19
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ module UI5DataFlow {
1212
private predicate bidiModelControl(DataFlow::Node start, DataFlow::Node end) {
1313
exists(DataFlow::SourceNode property, Metadata metadata, UI5BoundNode node |
1414
// same project
15-
inSameUI5Project(metadata.getFile(), node.getFile()) and
15+
exists(WebApp webApp |
16+
webApp.getAResource() = metadata.getFile() and webApp.getAResource() = node.getFile()
17+
) and
1618
(
1719
// same control
1820
metadata.getControl().getName() = node.getBindingPath().getControlQualifiedType()
@@ -87,21 +89,24 @@ module UI5DataFlow {
8789
UI5BindingPath getBindingPath() { result = bindingPath }
8890

8991
UI5BoundNode() {
90-
/* The relevant portion of the content of a JSONModel */
91-
exists(Property p, JsonModel model |
92-
// The property bound to an UI5View source
93-
this.(DataFlow::PropRef).getPropertyNameExpr() = p.getNameExpr() and
94-
// The binding path refers to this model
95-
bindingPath.getAbsolutePath() = model.getPathString(p) and
96-
inSameUI5Project(this.getFile(), bindingPath.getFile())
97-
)
98-
or
99-
/* The URI string to the JSONModel constructor call */
100-
exists(JsonModel model |
101-
this = model.getArgument(0) and
102-
this.asExpr() instanceof StringLiteral and
103-
bindingPath.getAbsolutePath() = model.getPathString() and
104-
inSameUI5Project(this.getFile(), bindingPath.getFile())
92+
exists(WebApp webApp |
93+
webApp.getAResource() = this.getFile() and
94+
webApp.getAResource() = bindingPath.getFile()
95+
|
96+
/* The relevant portion of the content of a JSONModel */
97+
exists(Property p, JsonModel model |
98+
// The property bound to an UI5View source
99+
this.(DataFlow::PropRef).getPropertyNameExpr() = p.getNameExpr() and
100+
// The binding path refers to this model
101+
bindingPath.getAbsolutePath() = model.getPathString(p)
102+
)
103+
or
104+
/* The URI string to the JSONModel constructor call */
105+
exists(JsonModel model |
106+
this = model.getArgument(0) and
107+
this.asExpr() instanceof StringLiteral and
108+
bindingPath.getAbsolutePath() = model.getPathString()
109+
)
105110
)
106111
}
107112
}
@@ -112,9 +117,7 @@ module UI5DataFlow {
112117
class UI5ModelSource extends UI5DataFlow::UI5BoundNode, RemoteFlowSource {
113118
UI5ModelSource() { bindingPath = any(UI5View view).getASource() }
114119

115-
override string getSourceType() {
116-
result = "UI5 model remote flow source"
117-
}
120+
override string getSourceType() { result = "UI5 model remote flow source" }
118121
}
119122

120123
/**

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,10 @@ class FrameOptions extends TFrameOptions {
8585
}
8686

8787
/**
88-
* Holds if the frame options are left untouched as the default value `trusted`.
88+
* Holds if there are no frame options specified to prevent click jacking.
8989
*/
90-
predicate thereIsNoFrameOptionSet(UI5::Project p) {
91-
not exists(FrameOptions frameOptions | p.isInThisProject(frameOptions.getLocation().getFile()) |
90+
predicate isMissingFrameOptionsToPreventClickjacking(UI5::WebApp webapp) {
91+
not exists(FrameOptions frameOptions | webapp.getFrameOptions() = frameOptions |
9292
frameOptions.allowsSharedOriginEmbedding() or
9393
frameOptions.deniesEmbedding() or
9494
frameOptions.allowsAllOriginEmbedding()

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

+22-9
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,10 @@ abstract class UI5BindingPath extends Locatable {
9898
// The property bound to an UI5View source
9999
result.getPropertyNameExpr() = p.getNameExpr() and
100100
this.getAbsolutePath() = model.getPathString(p) and
101-
//restrict search inside the same project
102-
inSameUI5Project(this.getFile(), result.getFile())
101+
//restrict search inside the same webapp
102+
exists(WebApp webApp |
103+
webApp.getAResource() = this.getFile() and webApp.getAResource() = result.getFile()
104+
)
103105
)
104106
// TODO
105107
/*
@@ -142,8 +144,10 @@ abstract class UI5View extends File {
142144
CustomController getController() {
143145
// The controller name should match
144146
result.getName() = this.getControllerName() and
145-
// The View and the Controller are in a same project
146-
inSameUI5Project(this, result.getFile())
147+
// The View and the Controller are in a same webapp
148+
exists(WebApp webApp |
149+
webApp.getAResource() = this and webApp.getAResource() = result.getFile()
150+
)
147151
}
148152

149153
abstract UI5BindingPath getASource();
@@ -490,10 +494,13 @@ class XmlView extends UI5View, XmlFile {
490494
(
491495
builtInControl(element.getNamespace())
492496
or
493-
// or a custom control with implementation code found in the project
497+
// or a custom control with implementation code found in the webapp
494498
exists(CustomControl control |
495499
control.getName() = element.getNamespace().getUri() + "." + element.getName() and
496-
inSameUI5Project(control.getFile(), element.getFile())
500+
exists(WebApp webApp |
501+
webApp.getAResource() = control.getFile() and
502+
webApp.getAResource() = element.getFile()
503+
)
497504
)
498505
)
499506
)
@@ -563,20 +570,26 @@ class XmlControl extends UI5Control instanceof XmlElement {
563570

564571
override CustomControl getDefinition() {
565572
result.getName() = this.getQualifiedType() and
566-
inSameUI5Project(this.getFile(), result.getFile())
573+
exists(WebApp webApp |
574+
webApp.getAResource() = this.getFile() and webApp.getAResource() = result.getFile()
575+
)
567576
}
568577

569578
bindingset[propName]
570579
override MethodCallNode getARead(string propName) {
571580
// TODO: in same view
572-
inSameUI5Project(this.getFile(), result.getFile()) and
581+
exists(WebApp webApp |
582+
webApp.getAResource() = this.getFile() and webApp.getAResource() = result.getFile()
583+
) and
573584
result.getMethodName() = "get" + capitalize(propName)
574585
}
575586

576587
bindingset[propName]
577588
override MethodCallNode getAWrite(string propName) {
578589
// TODO: in same view
579-
inSameUI5Project(this.getFile(), result.getFile()) and
590+
exists(WebApp webApp |
591+
webApp.getAResource() = this.getFile() and webApp.getAResource() = result.getFile()
592+
) and
580593
result.getMethodName() = "set" + capitalize(propName)
581594
}
582595

0 commit comments

Comments
 (0)