Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Expand Up @@ -203,14 +203,3 @@ private class DisplayEventHandlerParameterAccess extends RemoteFlowSource instan
)
}
}

/**
* Method calls that fetch a piece of data either from a library control capable of accepting user input, or from a URI parameter.
*/
private class UI5ExtRemoteSource extends RemoteFlowSource {
UI5ExtRemoteSource() { this = ModelOutput::getASourceNode("remote").asSource() }

override string getSourceType() {
result = "Remote flow" // Don't discriminate between UI5-specific remote flows and vanilla ones
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
import advanced_security.javascript.frameworks.ui5.UI5

private newtype TUI5Control =
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
TJsControl(NewNode control) {
exists(JsView view |
control.asExpr().getParentExpr() =
view.getRoot()
.getArgument(1)
.getALocalSource()
.(ObjectLiteralNode)
.getAPropertyWrite("createContent")
.getRhs()
.(FunctionNode)
.getReturnNode()
.getALocalSource()
.(ArrayLiteralNode)
.asExpr()
)
or
control = ModelOutput::getATypeNode("Control").getAnInvocation()
}

class UI5Control extends TUI5Control {
XmlElement asXmlControl() { this = TXmlControl(result) }

JsonObject asJsonControl() { this = TJsonControl(result) }

NewNode asJsControl() { this = TJsControl(result) }

string toString() {
result = this.asXmlControl().toString()
or
result = this.asJsonControl().toString()
or
result = this.asJsControl().toString()
}

predicate hasLocationInfo(
string filepath, int startcolumn, int startline, int endcolumn, int endline
) {
this.asXmlControl().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
or
/* Since JsonValue does not implement `hasLocationInfo`, we use `getLocation` instead. */
exists(Location location | location = this.asJsonControl().getLocation() |
location.getFile().getAbsolutePath() = filepath and
location.getStartColumn() = startcolumn and
location.getStartLine() = startline and
location.getEndColumn() = endcolumn and
location.getEndLine() = endline
)
or
this.asJsControl().hasLocationInfo(filepath, startcolumn, startline, endcolumn, endline)
}

/**
* Gets the qualified type string, e.g. `sap.m.SearchField`.
*/
string getQualifiedType() {
exists(XmlElement control | control = this.asXmlControl() |
result = control.getNamespace().getUri() + "." + control.getName()
)
or
exists(JsonObject control | control = this.asJsonControl() |
result = control.getPropStringValue("Type")
)
or
exists(NewNode control | control = this.asJsControl() |
result = control.asExpr().getAChildExpr().(PropAccess).getQualifiedName()
or
control = API::moduleImport(result).getAnInvocation()
)
}

File getFile() {
result = this.asXmlControl().getFile() or
result = this.asJsonControl().getFile() or
result = this.asJsControl().getFile()
}

/**
* Gets the `id` property of this control.
*/
string getId() { result = this.getProperty("id").getValue() }

/**
* Gets the qualified type name, e.g. `sap/m/SearchField`.
*/
string getImportPath() { result = this.getQualifiedType().replaceAll(".", "/") }

/**
* Gets the definition of this control if this is a custom one.
*/
CustomControl getDefinition() {
result.getName() = this.getQualifiedType() and
inSameWebApp(this.getFile(), result.getFile())
}

/**
* Gets a reference to this control. Currently supports only such references made through `byId`.
* Handles both:
* - `this.byId("controlId")` or `this.getView().byId("controlId")` - ID in argument 0
* - `Fragment.byId(viewId, "controlId")` - ID in argument 1
*/
ControlReference getAReference() {
result.getMethodName() = "byId" and
(
// Standard byId: ID in first argument
result.getNumArgument() = 1 and
result.getArgument(0).getALocalSource().asExpr().(StringLiteral).getValue() =
this.getProperty("id").getValue()
or
// Fragment.byId: ID in second argument
result.getNumArgument() = 2 and
result.getArgument(1).getALocalSource().asExpr().(StringLiteral).getValue() =
this.getProperty("id").getValue()
)
}

/** Gets a property of this control having the name. */
UI5ControlProperty getProperty(string propName) {
result.asXmlControlProperty() = this.asXmlControl().getAttribute(propName)
or
result.asJsonControlProperty() = this.asJsonControl().getPropValue(propName)
or
result.asJsControlProperty() =
this.asJsControl()
.getArgument(0)
.getALocalSource()
.asExpr()
.(ObjectExpr)
.getPropertyByName(propName)
.getAChildExpr()
.flow() and
not exists(Property property | result.asJsControlProperty() = property.getNameExpr().flow())
}

/** Gets a property of this control. */
UI5ControlProperty getAProperty() { result = this.getProperty(_) }

bindingset[propName]
MethodCallNode getARead(string propName) {
// TODO: in same view
inSameWebApp(this.getFile(), result.getFile()) and
result.getMethodName() = "get" + capitalize(propName)
}

bindingset[propName]
MethodCallNode getAWrite(string propName) {
// TODO: in same view
inSameWebApp(this.getFile(), result.getFile()) and
result.getMethodName() = "set" + capitalize(propName)
}

/** Holds if this control reads from or writes to a model. */
predicate accessesModel(UI5Model model) { this.accessesModel(model, _) }

/** Holds if this control reads from or writes to a model with regards to a binding path. */
predicate accessesModel(UI5Model model, XmlBindingPath bindingPath) {
// Verify that the controller's model has the referenced property
exists(XmlView view |
// Both this control and the model belong to the same view
this = view.getControl() and
model = view.getController().getModel() and
model.(UI5InternalModel).getPathString() = bindingPath.getPath() and
bindingPath.getBindingTarget() = this.asXmlControl().getAnAttribute()
)
}

/** Get the view that this control is part of. */
UI5View getView() { result = this.asXmlControl().getFile() }

/** Get the controller that manages this control. */
CustomController getController() { result = this.getView().getController() }

/**
* Gets the full import path of the associated control.
*/
string getControlTypeName() { result = this.getQualifiedType().replaceAll(".", "/") }

/**
* Holds if the control content is sanitized for HTML
* 'sap/ui/core/HTML' sanitized using the property 'sanitizeContent'
* 'sap/ui/richttexteditor/RichTextEditor' sanitized using the property 'sanitizeValue'
*/
predicate isHTMLSanitized() {
this.getControlTypeName() = "sap/ui/richtexteditor/RichTextEditor" and
not this.isSanitizePropertySetTo("sanitizeValue", false)
or
this.getControlTypeName() = "sap/ui/core/HTML" and
this.isSanitizePropertySetTo("sanitizeContent", true) and
not this.isSanitizePropertySetTo("sanitizeContent", false)
}

bindingset[propName, val]
private predicate isSanitizePropertySetTo(string propName, boolean val) {
/* 1. `sanitizeContent` attribute is set declaratively. */
this.getProperty(propName).toString() = val.toString()
or
/* 2. `sanitizeContent` attribute is set programmatically using setProperty(). */
exists(CallNode node | node = this.getAReference().getAMemberCall("setProperty") |
node.getArgument(0).getStringValue() = propName and
not node.getArgument(1).mayHaveBooleanValue(val.booleanNot())
)
or
/* 3. `sanitizeContent` attribute is set programmatically using a setter. */
exists(CallNode node, string setterName |
setterName = "set" + propName.prefix(1).toUpperCase() + propName.suffix(1) and
not node.getArgument(0).mayHaveBooleanValue(val.booleanNot())
|
node = this.getAReference().getAMemberCall(setterName) or
node = this.asJsControl().getAMemberCall(setterName)
)
}
}

class XmlControlProperty extends XmlAttribute {
XmlControlProperty() { exists(UI5Control control | this.getElement() = control.asXmlControl()) }
}

bindingset[qualifiedTypeUri]
predicate isBuiltInControl(string qualifiedTypeUri) {
exists(string namespace |
namespace =
[
"sap\\.m.*", // https://sapui5.hana.ondemand.com/#/api/sap.m: The main UI5 control library, with responsive controls that can be used in touch devices as well as desktop browsers.
"sap\\.f.*", // https://sapui5.hana.ondemand.com/#/api/sap.f: SAPUI5 library with controls specialized for SAP Fiori apps.
"sap\\.ui.*" // https://sapui5.hana.ondemand.com/#/api/sap.ui: The sap.ui namespace is the central OpenAjax compliant entry point for UI related JavaScript functionality provided by SAP.
]
|
qualifiedTypeUri.regexpMatch(namespace)
)
}

private newtype TUI5ControlProperty =
TXmlControlProperty(XmlAttribute property) or
TJsonControlProperty(JsonValue property) or
TJsControlProperty(ValueNode property)

class UI5ControlProperty extends TUI5ControlProperty {
XmlAttribute asXmlControlProperty() { this = TXmlControlProperty(result) }

JsonValue asJsonControlProperty() { this = TJsonControlProperty(result) }

ValueNode asJsControlProperty() { this = TJsControlProperty(result) }

string toString() {
result = this.asXmlControlProperty().getValue().toString() or
result = this.asJsonControlProperty().toString() or
result = this.asJsControlProperty().toString()
}

UI5Control getControl() {
result.asXmlControl() = this.asXmlControlProperty().getElement() or
result.asJsonControl() = this.asJsonControlProperty().getParent() or
result.asJsControl().getArgument(0).asExpr() = this.asJsControlProperty().getEnclosingExpr()
}

string getName() {
result = this.asXmlControlProperty().getName()
or
exists(JsonValue parent | parent.getPropValue(result) = this.asJsonControlProperty())
or
exists(Property property |
property.getAChildExpr() = this.asJsControlProperty().asExpr() and result = property.getName()
)
}

string getValue() {
result = this.asXmlControlProperty().getValue() or
result = this.asJsonControlProperty().getStringValue() or
result = this.asJsControlProperty().asExpr().(StringLiteral).getValue()
}
}

/**
* Utility predicate capturing the handler name.
*/
bindingset[notation]
private string handlerNotationCaptureName(string notation) {
result =
notation.replaceAll(" ", "").regexpCapture("\\.([\\w-]+)(?:\\([^)]*\\$(\\{[^}]+}).*)?", 1)
}

/**
* A function mentioned in a property of a UI5Control, usually an event handler.
*
* e.g. The function referred to by `doSomething()` as in `<Button press=".doSomething"/>`.
*/
class UI5Handler extends FunctionNode {
UI5Control control;

UI5Handler() {
this = control.getController().getAMethod() and
handlerNotationCaptureName(control.getProperty(_).getValue()) = this.getName()
}

UI5BindingPath getBindingPath() {
exists(string propName |
handlerNotationCaptureName(control.getProperty(propName).getValue()) = this.getName() and
result.getLiteralRepr() = control.getProperty(result.getPropertyName()).getValue()
)
}

UI5Control getControl() { result = control }
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import javascript
import advanced_security.javascript.frameworks.ui5.dataflow.DataFlow
import advanced_security.javascript.frameworks.ui5.dataflow.UI5DataFlow

/**
* Call to [`sap.ui.util.Storage.put`](https://sapui5.hana.ondemand.com/sdk/#/api/module:sap/ui/util/Storage%23methods/put)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import javascript
import advanced_security.javascript.frameworks.ui5.dataflow.DataFlow
import advanced_security.javascript.frameworks.ui5.dataflow.UI5DataFlow
import semmle.javascript.security.dataflow.LogInjectionQuery

module UI5LogInjection implements DataFlow::ConfigSig {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import javascript
import advanced_security.javascript.frameworks.ui5.dataflow.DataFlow
import advanced_security.javascript.frameworks.ui5.dataflow.UI5DataFlow
import advanced_security.javascript.frameworks.ui5.UI5LogInjectionQuery

class ClientRequestInjectionVector extends DataFlow::Node {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import javascript
import advanced_security.javascript.frameworks.ui5.dataflow.DataFlow
import advanced_security.javascript.frameworks.ui5.dataflow.UI5DataFlow
import semmle.javascript.security.dataflow.LogInjectionQuery

module UI5UnsafeLogAccess implements DataFlow::ConfigSig {
Expand Down
Loading
Loading