Skip to content

Commit 01fc884

Browse files
committed
Refactor UI5 dataflow imports and rename UI5DataFlow module
1 parent 250c1c9 commit 01fc884

File tree

23 files changed

+326
-327
lines changed

23 files changed

+326
-327
lines changed

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,6 @@ extensions:
115115
data:
116116
- ["UI5InputControl", "Member[value]", "remote"]
117117
- ["UI5InputControl", "Member[getValue].ReturnValue", "remote"]
118-
- ["UI5HTMLControl", "Member[getContent].ReturnValue", "remote"]
119118
- ["UI5CodeEditor", "Member[value]", "remote"]
120119
- ["UI5CodeEditor", "Member[getCurrentValue].ReturnValue", "remote"]
121120
- ["global", "Member[jQuery].Member[sap].Member[syncHead,syncGet,syncGetText,syncPost,syncPostText].ReturnValue", "remote"]

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

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -203,14 +203,3 @@ private class DisplayEventHandlerParameterAccess extends RemoteFlowSource instan
203203
)
204204
}
205205
}
206-
207-
/**
208-
* Method calls that fetch a piece of data either from a library control capable of accepting user input, or from a URI parameter.
209-
*/
210-
private class UI5ExtRemoteSource extends RemoteFlowSource {
211-
UI5ExtRemoteSource() { this = ModelOutput::getASourceNode("remote").asSource() }
212-
213-
override string getSourceType() {
214-
result = "Remote flow" // Don't discriminate between UI5-specific remote flows and vanilla ones
215-
}
216-
}
Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
import advanced_security.javascript.frameworks.ui5.UI5
2+
3+
private newtype TUI5Control =
4+
TXmlControl(XmlElement control) {
5+
control
6+
.(Locatable)
7+
.getFile()
8+
.getBaseName()
9+
.matches(["%.view.xml", "%.view.html", "%.fragment.xml"])
10+
} or
11+
TJsonControl(JsonObject control) {
12+
exists(JsonView view | control.getParent() = view.getRoot().getPropValue("content"))
13+
} or
14+
TJsControl(NewNode control) {
15+
exists(JsView view |
16+
control.asExpr().getParentExpr() =
17+
view.getRoot()
18+
.getArgument(1)
19+
.getALocalSource()
20+
.(ObjectLiteralNode)
21+
.getAPropertyWrite("createContent")
22+
.getRhs()
23+
.(FunctionNode)
24+
.getReturnNode()
25+
.getALocalSource()
26+
.(ArrayLiteralNode)
27+
.asExpr()
28+
)
29+
or
30+
control = ModelOutput::getATypeNode("Control").getAnInvocation()
31+
}
32+
33+
class UI5Control extends TUI5Control {
34+
XmlElement asXmlControl() { this = TXmlControl(result) }
35+
36+
JsonObject asJsonControl() { this = TJsonControl(result) }
37+
38+
NewNode asJsControl() { this = TJsControl(result) }
39+
40+
string toString() {
41+
result = this.asXmlControl().toString()
42+
or
43+
result = this.asJsonControl().toString()
44+
or
45+
result = this.asJsControl().toString()
46+
}
47+
48+
predicate hasLocationInfo(
49+
string filepath, int startcolumn, int startline, int endcolumn, int endline
50+
) {
51+
this.asXmlControl().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
52+
or
53+
/* Since JsonValue does not implement `hasLocationInfo`, we use `getLocation` instead. */
54+
exists(Location location | location = this.asJsonControl().getLocation() |
55+
location.getFile().getAbsolutePath() = filepath and
56+
location.getStartColumn() = startcolumn and
57+
location.getStartLine() = startline and
58+
location.getEndColumn() = endcolumn and
59+
location.getEndLine() = endline
60+
)
61+
or
62+
this.asJsControl().hasLocationInfo(filepath, startcolumn, startline, endcolumn, endline)
63+
}
64+
65+
/**
66+
* Gets the qualified type string, e.g. `sap.m.SearchField`.
67+
*/
68+
string getQualifiedType() {
69+
exists(XmlElement control | control = this.asXmlControl() |
70+
result = control.getNamespace().getUri() + "." + control.getName()
71+
)
72+
or
73+
exists(JsonObject control | control = this.asJsonControl() |
74+
result = control.getPropStringValue("Type")
75+
)
76+
or
77+
exists(NewNode control | control = this.asJsControl() |
78+
result = control.asExpr().getAChildExpr().(PropAccess).getQualifiedName()
79+
or
80+
control = API::moduleImport(result).getAnInvocation()
81+
)
82+
}
83+
84+
File getFile() {
85+
result = this.asXmlControl().getFile() or
86+
result = this.asJsonControl().getFile() or
87+
result = this.asJsControl().getFile()
88+
}
89+
90+
/**
91+
* Gets the `id` property of this control.
92+
*/
93+
string getId() { result = this.getProperty("id").getValue() }
94+
95+
/**
96+
* Gets the qualified type name, e.g. `sap/m/SearchField`.
97+
*/
98+
string getImportPath() { result = this.getQualifiedType().replaceAll(".", "/") }
99+
100+
/**
101+
* Gets the definition of this control if this is a custom one.
102+
*/
103+
CustomControl getDefinition() {
104+
result.getName() = this.getQualifiedType() and
105+
inSameWebApp(this.getFile(), result.getFile())
106+
}
107+
108+
/**
109+
* Gets a reference to this control. Currently supports only such references made through `byId`.
110+
* Handles both:
111+
* - `this.byId("controlId")` or `this.getView().byId("controlId")` - ID in argument 0
112+
* - `Fragment.byId(viewId, "controlId")` - ID in argument 1
113+
*/
114+
ControlReference getAReference() {
115+
result.getMethodName() = "byId" and
116+
(
117+
// Standard byId: ID in first argument
118+
result.getNumArgument() = 1 and
119+
result.getArgument(0).getALocalSource().asExpr().(StringLiteral).getValue() =
120+
this.getProperty("id").getValue()
121+
or
122+
// Fragment.byId: ID in second argument
123+
result.getNumArgument() = 2 and
124+
result.getArgument(1).getALocalSource().asExpr().(StringLiteral).getValue() =
125+
this.getProperty("id").getValue()
126+
)
127+
}
128+
129+
/** Gets a property of this control having the name. */
130+
UI5ControlProperty getProperty(string propName) {
131+
result.asXmlControlProperty() = this.asXmlControl().getAttribute(propName)
132+
or
133+
result.asJsonControlProperty() = this.asJsonControl().getPropValue(propName)
134+
or
135+
result.asJsControlProperty() =
136+
this.asJsControl()
137+
.getArgument(0)
138+
.getALocalSource()
139+
.asExpr()
140+
.(ObjectExpr)
141+
.getPropertyByName(propName)
142+
.getAChildExpr()
143+
.flow() and
144+
not exists(Property property | result.asJsControlProperty() = property.getNameExpr().flow())
145+
}
146+
147+
/** Gets a property of this control. */
148+
UI5ControlProperty getAProperty() { result = this.getProperty(_) }
149+
150+
bindingset[propName]
151+
MethodCallNode getARead(string propName) {
152+
// TODO: in same view
153+
inSameWebApp(this.getFile(), result.getFile()) and
154+
result.getMethodName() = "get" + capitalize(propName)
155+
}
156+
157+
bindingset[propName]
158+
MethodCallNode getAWrite(string propName) {
159+
// TODO: in same view
160+
inSameWebApp(this.getFile(), result.getFile()) and
161+
result.getMethodName() = "set" + capitalize(propName)
162+
}
163+
164+
/** Holds if this control reads from or writes to a model. */
165+
predicate accessesModel(UI5Model model) { this.accessesModel(model, _) }
166+
167+
/** Holds if this control reads from or writes to a model with regards to a binding path. */
168+
predicate accessesModel(UI5Model model, XmlBindingPath bindingPath) {
169+
// Verify that the controller's model has the referenced property
170+
exists(XmlView view |
171+
// Both this control and the model belong to the same view
172+
this = view.getControl() and
173+
model = view.getController().getModel() and
174+
model.(UI5InternalModel).getPathString() = bindingPath.getPath() and
175+
bindingPath.getBindingTarget() = this.asXmlControl().getAnAttribute()
176+
)
177+
}
178+
179+
/** Get the view that this control is part of. */
180+
UI5View getView() { result = this.asXmlControl().getFile() }
181+
182+
/** Get the controller that manages this control. */
183+
CustomController getController() { result = this.getView().getController() }
184+
185+
/**
186+
* Gets the full import path of the associated control.
187+
*/
188+
string getControlTypeName() { result = this.getQualifiedType().replaceAll(".", "/") }
189+
190+
/**
191+
* Holds if the control content is sanitized for HTML
192+
* 'sap/ui/core/HTML' sanitized using the property 'sanitizeContent'
193+
* 'sap/ui/richttexteditor/RichTextEditor' sanitized using the property 'sanitizeValue'
194+
*/
195+
predicate isHTMLSanitized() {
196+
this.getControlTypeName() = "sap/ui/richtexteditor/RichTextEditor" and
197+
not this.isSanitizePropertySetTo("sanitizeValue", false)
198+
or
199+
this.getControlTypeName() = "sap/ui/core/HTML" and
200+
this.isSanitizePropertySetTo("sanitizeContent", true) and
201+
not this.isSanitizePropertySetTo("sanitizeContent", false)
202+
}
203+
204+
bindingset[propName, val]
205+
private predicate isSanitizePropertySetTo(string propName, boolean val) {
206+
/* 1. `sanitizeContent` attribute is set declaratively. */
207+
this.getProperty(propName).toString() = val.toString()
208+
or
209+
/* 2. `sanitizeContent` attribute is set programmatically using setProperty(). */
210+
exists(CallNode node | node = this.getAReference().getAMemberCall("setProperty") |
211+
node.getArgument(0).getStringValue() = propName and
212+
not node.getArgument(1).mayHaveBooleanValue(val.booleanNot())
213+
)
214+
or
215+
/* 3. `sanitizeContent` attribute is set programmatically using a setter. */
216+
exists(CallNode node, string setterName |
217+
setterName = "set" + propName.prefix(1).toUpperCase() + propName.suffix(1) and
218+
not node.getArgument(0).mayHaveBooleanValue(val.booleanNot())
219+
|
220+
node = this.getAReference().getAMemberCall(setterName) or
221+
node = this.asJsControl().getAMemberCall(setterName)
222+
)
223+
}
224+
}
225+
226+
private newtype TUI5ControlProperty =
227+
TXmlControlProperty(XmlAttribute property) or
228+
TJsonControlProperty(JsonValue property) or
229+
TJsControlProperty(ValueNode property)
230+
231+
class UI5ControlProperty extends TUI5ControlProperty {
232+
XmlAttribute asXmlControlProperty() { this = TXmlControlProperty(result) }
233+
234+
JsonValue asJsonControlProperty() { this = TJsonControlProperty(result) }
235+
236+
ValueNode asJsControlProperty() { this = TJsControlProperty(result) }
237+
238+
string toString() {
239+
result = this.asXmlControlProperty().getValue().toString() or
240+
result = this.asJsonControlProperty().toString() or
241+
result = this.asJsControlProperty().toString()
242+
}
243+
244+
UI5Control getControl() {
245+
result.asXmlControl() = this.asXmlControlProperty().getElement() or
246+
result.asJsonControl() = this.asJsonControlProperty().getParent() or
247+
result.asJsControl().getArgument(0).asExpr() = this.asJsControlProperty().getEnclosingExpr()
248+
}
249+
250+
string getName() {
251+
result = this.asXmlControlProperty().getName()
252+
or
253+
exists(JsonValue parent | parent.getPropValue(result) = this.asJsonControlProperty())
254+
or
255+
exists(Property property |
256+
property.getAChildExpr() = this.asJsControlProperty().asExpr() and result = property.getName()
257+
)
258+
}
259+
260+
string getValue() {
261+
result = this.asXmlControlProperty().getValue() or
262+
result = this.asJsonControlProperty().getStringValue() or
263+
result = this.asJsControlProperty().asExpr().(StringLiteral).getValue()
264+
}
265+
}
266+
267+
/**
268+
* Utility predicate capturing the handler name.
269+
*/
270+
bindingset[notation]
271+
private string handlerNotationCaptureName(string notation) {
272+
result =
273+
notation.replaceAll(" ", "").regexpCapture("\\.([\\w-]+)(?:\\([^)]*\\$(\\{[^}]+}).*)?", 1)
274+
}
275+
276+
/**
277+
* A function mentioned in a property of a UI5Control, usually an event handler.
278+
*
279+
* e.g. The function referred to by `doSomething()` as in `<Button press=".doSomething"/>`.
280+
*/
281+
class UI5Handler extends FunctionNode {
282+
UI5Control control;
283+
284+
UI5Handler() {
285+
this = control.getController().getAMethod() and
286+
handlerNotationCaptureName(control.getProperty(_).getValue()) = this.getName()
287+
}
288+
289+
UI5BindingPath getBindingPath() {
290+
exists(string propName |
291+
handlerNotationCaptureName(control.getProperty(propName).getValue()) = this.getName() and
292+
result.getLiteralRepr() = control.getProperty(result.getPropertyName()).getValue()
293+
)
294+
}
295+
296+
UI5Control getControl() { result = control }
297+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import javascript
2-
import advanced_security.javascript.frameworks.ui5.dataflow.DataFlow
2+
import advanced_security.javascript.frameworks.ui5.dataflow.UI5DataFlow
33

44
/**
55
* Call to [`sap.ui.util.Storage.put`](https://sapui5.hana.ondemand.com/sdk/#/api/module:sap/ui/util/Storage%23methods/put)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import javascript
2-
import advanced_security.javascript.frameworks.ui5.dataflow.DataFlow
2+
import advanced_security.javascript.frameworks.ui5.dataflow.UI5DataFlow
33
import semmle.javascript.security.dataflow.LogInjectionQuery
44

55
module UI5LogInjection implements DataFlow::ConfigSig {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import javascript
2-
import advanced_security.javascript.frameworks.ui5.dataflow.DataFlow
2+
import advanced_security.javascript.frameworks.ui5.dataflow.UI5DataFlow
33
import advanced_security.javascript.frameworks.ui5.UI5LogInjectionQuery
44

55
class ClientRequestInjectionVector extends DataFlow::Node {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import javascript
2-
import advanced_security.javascript.frameworks.ui5.dataflow.DataFlow
2+
import advanced_security.javascript.frameworks.ui5.dataflow.UI5DataFlow
33
import semmle.javascript.security.dataflow.LogInjectionQuery
44

55
module UI5UnsafeLogAccess implements DataFlow::ConfigSig {

0 commit comments

Comments
 (0)