mirror of
https://github.com/github/codeql.git
synced 2026-04-28 18:25:24 +02:00
Merge pull request #4958 from asgerf/js/angular2
Approved by erik-krogh
This commit is contained in:
@@ -16,6 +16,16 @@ import javascript
|
||||
import semmle.javascript.frameworks.Templating
|
||||
import semmle.javascript.RestrictedLocations
|
||||
|
||||
/**
|
||||
* Holds if the `rel` attribute may be injected by an Angular2 directive.
|
||||
*/
|
||||
predicate maybeInjectedByAngular() {
|
||||
DataFlow::moduleMember("@angular/core", "HostBinding")
|
||||
.getACall()
|
||||
.getArgument(0)
|
||||
.mayHaveStringValue("attr.rel")
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the href attribute contains a host that we cannot determine statically.
|
||||
*/
|
||||
@@ -44,6 +54,8 @@ where
|
||||
e.getName() = "a" and
|
||||
// and the host in the href is not hard-coded
|
||||
hasDynamicHrefHostAttributeValue(e) and
|
||||
// disable for Angular applications that dynamically inject the 'rel' attribute
|
||||
not maybeInjectedByAngular() and
|
||||
e.getAttributeByName("target").getStringValue() = "_blank" and
|
||||
// there is no `rel` attribute specifying link type `noopener`/`noreferrer`;
|
||||
// `rel` attributes with non-constant value are handled conservatively
|
||||
|
||||
@@ -98,6 +98,7 @@ import semmle.javascript.frameworks.PropertyProjection
|
||||
import semmle.javascript.frameworks.React
|
||||
import semmle.javascript.frameworks.ReactNative
|
||||
import semmle.javascript.frameworks.Request
|
||||
import semmle.javascript.frameworks.RxJS
|
||||
import semmle.javascript.frameworks.ServerLess
|
||||
import semmle.javascript.frameworks.ShellJS
|
||||
import semmle.javascript.frameworks.SystemCommandExecutors
|
||||
|
||||
@@ -300,7 +300,11 @@ class InlineScript extends @inline_script, Script { }
|
||||
* ```
|
||||
*/
|
||||
class CodeInAttribute extends TopLevel {
|
||||
CodeInAttribute() { this instanceof @event_handler or this instanceof @javascript_url }
|
||||
CodeInAttribute() {
|
||||
this instanceof @event_handler or
|
||||
this instanceof @javascript_url or
|
||||
this instanceof @angular_template_toplevel
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,6 +10,27 @@ module HTML {
|
||||
HtmlFile() { getFileType().isHtml() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A file that may contain HTML elements.
|
||||
*
|
||||
* This is either an `.html` file or a source code file containing
|
||||
* embedded HTML snippets.
|
||||
*/
|
||||
private class FileContainingHtml extends File {
|
||||
FileContainingHtml() {
|
||||
getFileType().isHtml()
|
||||
or
|
||||
// The file contains an expression containing an HTML element
|
||||
exists(Expr e |
|
||||
e.getFile() = this and
|
||||
xml_element_parent_expression(_, e, _)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets `i`th root node of the HTML fragment embedded in the given expression, if any. */
|
||||
Element getHtmlElementFromExpr(Expr e, int i) { xml_element_parent_expression(result, e, i) }
|
||||
|
||||
/**
|
||||
* An HTML element.
|
||||
*
|
||||
@@ -20,7 +41,7 @@ module HTML {
|
||||
* ```
|
||||
*/
|
||||
class Element extends Locatable, @xmlelement {
|
||||
Element() { exists(HtmlFile f | xmlElements(this, _, _, _, f)) }
|
||||
Element() { exists(FileContainingHtml f | xmlElements(this, _, _, _, f)) }
|
||||
|
||||
override Location getLocation() { xmllocations(this, result) }
|
||||
|
||||
@@ -97,10 +118,15 @@ module HTML {
|
||||
* ```
|
||||
*/
|
||||
class Attribute extends Locatable, @xmlattribute {
|
||||
Attribute() { exists(HtmlFile f | xmlAttrs(this, _, _, _, _, f)) }
|
||||
Attribute() { exists(FileContainingHtml f | xmlAttrs(this, _, _, _, _, f)) }
|
||||
|
||||
override Location getLocation() { xmllocations(this, result) }
|
||||
|
||||
/**
|
||||
* Gets the inline script of this attribute, if any.
|
||||
*/
|
||||
CodeInAttribute getCodeInAttribute() { toplevel_parent_xml_node(result, this) }
|
||||
|
||||
/**
|
||||
* Gets the element to which this attribute belongs.
|
||||
*/
|
||||
@@ -127,32 +153,6 @@ module HTML {
|
||||
|
||||
override string toString() { result = getName() + "=" + getValue() }
|
||||
|
||||
/**
|
||||
* Gets the inline script of this attribute, if any.
|
||||
*/
|
||||
CodeInAttribute getCodeInAttribute() {
|
||||
exists(
|
||||
string f, Location l1, int sl1, int sc1, int el1, int ec1, Location l2, int sl2, int sc2,
|
||||
int el2, int ec2
|
||||
|
|
||||
l1 = getLocation() and
|
||||
l2 = result.getLocation() and
|
||||
l1.hasLocationInfo(f, sl1, sc1, el1, ec1) and
|
||||
l2.hasLocationInfo(f, sl2, sc2, el2, ec2)
|
||||
|
|
||||
(
|
||||
sl1 = sl2 and sc1 < sc2
|
||||
or
|
||||
sl1 < sl2
|
||||
) and
|
||||
(
|
||||
el1 = el2 and ec1 > ec2
|
||||
or
|
||||
el1 > el2
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override string getAPrimaryQlClass() { result = "HTML::Attribute" }
|
||||
}
|
||||
|
||||
@@ -226,26 +226,7 @@ module HTML {
|
||||
* Gets the inline script of this script element, if any.
|
||||
*/
|
||||
private InlineScript getInlineScript() {
|
||||
exists(
|
||||
string f, Location l1, int sl1, int sc1, int el1, int ec1, Location l2, int sl2, int sc2,
|
||||
int el2, int ec2
|
||||
|
|
||||
l1 = getLocation() and
|
||||
l2 = result.getLocation() and
|
||||
l1.hasLocationInfo(f, sl1, sc1, el1, ec1) and
|
||||
l2.hasLocationInfo(f, sl2, sc2, el2, ec2)
|
||||
|
|
||||
(
|
||||
sl1 = sl2 and sc1 < sc2
|
||||
or
|
||||
sl1 < sl2
|
||||
) and
|
||||
(
|
||||
el1 = el2 and ec1 > ec2
|
||||
or
|
||||
el1 > el2
|
||||
)
|
||||
) and
|
||||
toplevel_parent_xml_node(result, this) and
|
||||
// the src attribute has precedence
|
||||
not exists(getSourcePath())
|
||||
}
|
||||
@@ -295,7 +276,7 @@ module HTML {
|
||||
* Note that instances of this class are only available if extraction is done with `--html all` or `--experimental`.
|
||||
*/
|
||||
class TextNode extends Locatable, @xmlcharacters {
|
||||
TextNode() { exists(HtmlFile f | xmlChars(this, _, _, _, _, f)) }
|
||||
TextNode() { exists(FileContainingHtml f | xmlChars(this, _, _, _, _, f)) }
|
||||
|
||||
override string toString() { result = getText() }
|
||||
|
||||
@@ -334,7 +315,7 @@ module HTML {
|
||||
* ```
|
||||
*/
|
||||
class CommentNode extends Locatable, @xmlcomment {
|
||||
CommentNode() { exists(HtmlFile f | xmlComments(this, _, _, f)) }
|
||||
CommentNode() { exists(FileContainingHtml f | xmlComments(this, _, _, f)) }
|
||||
|
||||
/** Gets the element in which this comment occurs. */
|
||||
Element getParent() { xmlComments(this, _, result, _) }
|
||||
|
||||
@@ -236,6 +236,7 @@ abstract class Import extends ASTNode {
|
||||
* behavior of Node.js imports, which prefer core modules such as `fs` over any
|
||||
* source module of the same name.
|
||||
*/
|
||||
cached
|
||||
Module getImportedModule() {
|
||||
if exists(resolveExternsImport())
|
||||
then result = resolveExternsImport()
|
||||
|
||||
@@ -1308,6 +1308,20 @@ module DataFlow {
|
||||
nd = TFunctionReturnNode(function)
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use outside standard library.
|
||||
*
|
||||
* Gets a data flow node unique to the given field declaration.
|
||||
*
|
||||
* Note that this node defaults to being disconnected from the data flow
|
||||
* graph, as the individual property reads and writes affecting the field are
|
||||
* analyzed independently of the field declaration.
|
||||
*
|
||||
* Certain framework models may need this node to model the behavior of
|
||||
* class and field decorators.
|
||||
*/
|
||||
DataFlow::Node fieldDeclarationNode(FieldDeclaration field) { result = TPropNode(field) }
|
||||
|
||||
/**
|
||||
* Gets the data flow node corresponding the given l-value expression, if
|
||||
* such a node exists.
|
||||
@@ -1592,7 +1606,8 @@ module DataFlow {
|
||||
e instanceof E4X::XMLAttributeSelector or
|
||||
e instanceof E4X::XMLDotDotExpression or
|
||||
e instanceof E4X::XMLFilterExpression or
|
||||
e instanceof E4X::XMLQualifiedIdentifier
|
||||
e instanceof E4X::XMLQualifiedIdentifier or
|
||||
e instanceof Angular2::PipeRefExpr
|
||||
)
|
||||
or
|
||||
exists(Expr e | e = nd.asExpr() |
|
||||
|
||||
@@ -1007,6 +1007,11 @@ class ClassNode extends DataFlow::SourceNode {
|
||||
TypeAnnotation getFieldTypeAnnotation(string fieldName) {
|
||||
result = impl.getFieldTypeAnnotation(fieldName)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a decorator applied to this class.
|
||||
*/
|
||||
DataFlow::Node getADecorator() { result = impl.getADecorator() }
|
||||
}
|
||||
|
||||
module ClassNode {
|
||||
@@ -1064,6 +1069,9 @@ module ClassNode {
|
||||
* Gets the type annotation for the field `fieldName`, if any.
|
||||
*/
|
||||
TypeAnnotation getFieldTypeAnnotation(string fieldName) { none() }
|
||||
|
||||
/** Gets a decorator applied to this class. */
|
||||
DataFlow::Node getADecorator() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1131,6 +1139,10 @@ module ClassNode {
|
||||
result = field.getTypeAnnotation()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getADecorator() {
|
||||
result = astNode.getADecorator().getExpression().flow()
|
||||
}
|
||||
}
|
||||
|
||||
private DataFlow::PropRef getAPrototypeReferenceInFile(string name, File f) {
|
||||
|
||||
@@ -308,7 +308,9 @@ module SourceNode {
|
||||
astNode instanceof DynamicImportExpr or
|
||||
astNode instanceof ImportSpecifier or
|
||||
astNode instanceof ImportMetaExpr or
|
||||
astNode instanceof TaggedTemplateExpr
|
||||
astNode instanceof TaggedTemplateExpr or
|
||||
astNode instanceof Angular2::PipeRefExpr or
|
||||
astNode instanceof Angular2::TemplateVarRefExpr
|
||||
)
|
||||
or
|
||||
DataFlow::parameterNode(this, _)
|
||||
|
||||
@@ -307,6 +307,11 @@ class AnalyzedNegativeConditionGuard extends AnalyzedRefinement {
|
||||
}
|
||||
}
|
||||
|
||||
/** Holds if `v` is a variable in an Angular template. */
|
||||
private predicate isAngularTemplateVariable(LocalVariable v) {
|
||||
v = any(Angular2::TemplateTopLevel tl).getScope().getAVariable()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the abstract value representing the initial value of variable `v`.
|
||||
*
|
||||
@@ -325,7 +330,10 @@ private AbstractValue getImplicitInitValue(LocalVariable v) {
|
||||
then
|
||||
// model hoisting
|
||||
result = TAbstractFunction(getAFunDecl(v))
|
||||
else result = TAbstractUndefined()
|
||||
else
|
||||
if isAngularTemplateVariable(v)
|
||||
then result = TIndefiniteAbstractValue("heap")
|
||||
else result = TAbstractUndefined()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,7 @@ private import semmle.javascript.security.dataflow.Xss
|
||||
private import semmle.javascript.security.dataflow.CodeInjectionCustomizations
|
||||
private import semmle.javascript.security.dataflow.ClientSideUrlRedirectCustomizations
|
||||
private import semmle.javascript.DynamicPropertyAccess
|
||||
private import semmle.javascript.dataflow.internal.PreCallGraphStep
|
||||
|
||||
/**
|
||||
* Provides classes for working with Angular (also known as Angular 2.x) applications.
|
||||
@@ -196,6 +197,10 @@ module Angular2 {
|
||||
or
|
||||
result = getOptionArgument(argumentOffset + 1, "body")
|
||||
}
|
||||
|
||||
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
|
||||
result = this and responseType = "rxjs.observable" and promise = false
|
||||
}
|
||||
}
|
||||
|
||||
private string getInternalName(string name) {
|
||||
@@ -218,4 +223,371 @@ module Angular2 {
|
||||
private class DomAdapterLocation extends DOM::LocationSource::Range {
|
||||
DomAdapterLocation() { this = domAdapter().getAMethodCall("getLocation") }
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference to a pipe function, occurring in an Angular pipe expression
|
||||
* that has been desugared to a function call.
|
||||
*
|
||||
* For example, the expression `x | f: y` is desugared to `f(x, y)` where
|
||||
* `f` is a `PipeRefExpr`.
|
||||
*/
|
||||
class PipeRefExpr extends Expr, @angular_pipe_ref {
|
||||
/** Gets the identifier node naming the pipe. */
|
||||
Identifier getIdentifier() { result = getChildExpr(0) }
|
||||
|
||||
/** Gets the name of the pipe being referenced. */
|
||||
string getName() { result = getIdentifier().getName() }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "Angular2::PipeRefExpr" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference to a variable in a template expression, corresponding
|
||||
* to a property on the component class.
|
||||
*/
|
||||
class TemplateVarRefExpr extends Expr {
|
||||
TemplateVarRefExpr() { this = any(TemplateTopLevel tl).getScope().getAVariable().getAnAccess() }
|
||||
}
|
||||
|
||||
/** The top-level containing an Angular expression. */
|
||||
class TemplateTopLevel extends TopLevel, @angular_template_toplevel {
|
||||
/** Gets the expression in this top-level. */
|
||||
Expr getExpression() { result = getChildStmt(0).(ExprStmt).getExpr() }
|
||||
|
||||
/** Gets the data flow node representing the initialization of the given variable in this scope. */
|
||||
DataFlow::Node getVariableInit(string name) {
|
||||
result = DataFlow::ssaDefinitionNode(SSA::implicitInit(getScope().getVariable(name)))
|
||||
}
|
||||
|
||||
/** Gets a data flow node corresponding to a use of the given template variable within this top-level. */
|
||||
DataFlow::SourceNode getAVariableUse(string name) {
|
||||
result = getScope().getVariable(name).getAnAccess().flow()
|
||||
}
|
||||
}
|
||||
|
||||
/** The RHS of a `templateUrl` property, seen as a path expression. */
|
||||
private class TemplateUrlPath extends PathExpr {
|
||||
TemplateUrlPath() {
|
||||
exists(Property prop |
|
||||
prop.getName() = "templateUrl" and
|
||||
this = prop.getInit()
|
||||
)
|
||||
}
|
||||
|
||||
override string getValue() { result = this.(Expr).getStringValue() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the value of `attrib` is interpreted as an Angular expression.
|
||||
*/
|
||||
predicate isAngularExpressionAttribute(HTML::Attribute attrib) {
|
||||
attrib.getName().matches("(%)") or
|
||||
attrib.getName().matches("[%]") or
|
||||
attrib.getName().matches("*ng%")
|
||||
}
|
||||
|
||||
private DataFlow::Node getAttributeValueAsNode(HTML::Attribute attrib) {
|
||||
result = attrib.getCodeInAttribute().(TemplateTopLevel).getExpression().flow()
|
||||
}
|
||||
|
||||
/**
|
||||
* The class for an Angular component.
|
||||
*/
|
||||
class ComponentClass extends DataFlow::ClassNode {
|
||||
DataFlow::CallNode decorator;
|
||||
|
||||
ComponentClass() {
|
||||
decorator = getADecorator() and
|
||||
decorator = DataFlow::moduleMember("@angular/core", "Component").getACall()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data flow node representing the value of the declared
|
||||
* instance field of the given name.
|
||||
*/
|
||||
DataFlow::Node getFieldNode(string name) {
|
||||
exists(FieldDeclaration f |
|
||||
f.getName() = name and
|
||||
f.getDeclaringClass().flow() = this and
|
||||
result = DataFlow::fieldDeclarationNode(f)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data flow node representing data flowing into a field of
|
||||
* this component.
|
||||
*/
|
||||
DataFlow::Node getFieldInputNode(string name) {
|
||||
result = getFieldNode(name)
|
||||
or
|
||||
result = getInstanceMember(name, DataFlow::MemberKind::setter()).getParameter(0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data flow node representing data flowing out of a field
|
||||
* of this component.
|
||||
*/
|
||||
DataFlow::Node getFieldOutputNode(string name) {
|
||||
result = getFieldNode(name)
|
||||
or
|
||||
result = getInstanceMember(name, DataFlow::MemberKind::getter()).getReturnNode()
|
||||
or
|
||||
result = getInstanceMethod(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `selector` property of the `@Component` decorator.
|
||||
*/
|
||||
string getSelector() { decorator.getOptionArgument(0, "selector").mayHaveStringValue(result) }
|
||||
|
||||
/** Gets an HTML element that instantiates this component. */
|
||||
HTML::Element getATemplateInstantiation() { result.getName() = getSelector() }
|
||||
|
||||
/**
|
||||
* Gets an argument that flows into the `name` field of this component.
|
||||
*
|
||||
* For example, if the selector for this component is `"my-class"`, then this
|
||||
* predicate can match an attribute like: `<my-class [foo]="1+2"/>`.
|
||||
* The result of this predicate would be the `1+2` expression, and `name` would be `"foo"`.
|
||||
*/
|
||||
DataFlow::Node getATemplateArgument(string name) {
|
||||
result =
|
||||
getAttributeValueAsNode(getATemplateInstantiation().getAttributeByName("[" + name + "]"))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file referred to by `templateUrl`.
|
||||
*
|
||||
* Has no result if the template is given inline via a `template` property.
|
||||
*/
|
||||
pragma[noinline]
|
||||
File getTemplateFile() {
|
||||
result = decorator.getOptionArgument(0, "templateUrl").asExpr().(PathExpr).resolve()
|
||||
}
|
||||
|
||||
/** Gets an element in the HTML template of this component. */
|
||||
HTML::Element getATemplateElement() {
|
||||
result.getFile() = getTemplateFile()
|
||||
or
|
||||
result.getParent*() =
|
||||
HTML::getHtmlElementFromExpr(decorator.getOptionArgument(0, "template").asExpr(), _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an access to the given template variable within the template body of this component.
|
||||
*/
|
||||
DataFlow::SourceNode getATemplateVarAccess(string name) {
|
||||
result =
|
||||
getATemplateElement()
|
||||
.getAnAttribute()
|
||||
.getCodeInAttribute()
|
||||
.(TemplateTopLevel)
|
||||
.getAVariableUse(name)
|
||||
}
|
||||
}
|
||||
|
||||
/** A class with the `@Pipe` decorator. */
|
||||
class PipeClass extends DataFlow::ClassNode {
|
||||
DataFlow::CallNode decorator;
|
||||
|
||||
PipeClass() {
|
||||
decorator = DataFlow::moduleMember("@angular/core", "Pipe").getACall() and
|
||||
decorator = getADecorator()
|
||||
}
|
||||
|
||||
/** Gets the value of the `name` option passed to the `@Pipe` decorator. */
|
||||
string getPipeName() { decorator.getOptionArgument(0, "name").mayHaveStringValue(result) }
|
||||
|
||||
/** Gets a reference to this pipe. */
|
||||
DataFlow::Node getAPipeRef() { result.asExpr().(PipeRefExpr).getName() = getPipeName() }
|
||||
}
|
||||
|
||||
private class ComponentSteps extends PreCallGraphStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(ComponentClass cls, string name |
|
||||
// From <my-class [foo]="bar"/> to `foo` field in class
|
||||
pred = cls.getATemplateArgument(name) and
|
||||
succ = cls.getFieldInputNode(name)
|
||||
or
|
||||
// From `foo` field in class to <other-component [baz]="foo"/>
|
||||
pred = cls.getFieldOutputNode(name) and
|
||||
succ = cls.getATemplateVarAccess(name)
|
||||
or
|
||||
// From property write to the field input node
|
||||
pred = cls.getAReceiverNode().getAPropertyWrite(name).getRhs() and
|
||||
succ = cls.getFieldInputNode(name)
|
||||
or
|
||||
// From the field node to property read.
|
||||
// We use `getFieldNode` instead of `getFieldOutputNode` as the other two cases
|
||||
// from `getFieldOutputNode` are already handled by the general data flow library.
|
||||
pred = cls.getFieldNode(name) and
|
||||
succ = cls.getAReceiverNode().getAPropertyRead(name)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class PipeSteps extends PreCallGraphStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(PipeClass cls |
|
||||
pred = cls.getInstanceMethod("transform") and
|
||||
succ = cls.getAPipeRef()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An attribute of form `*ngFor="let var of EXPR"`.
|
||||
*
|
||||
* The `EXPR` has been extracted as the sole `CodeInAttribute` top-level for this
|
||||
* attribute. There is no AST node for the implied for-of loop.
|
||||
*/
|
||||
private class ForLoopAttribute extends HTML::Attribute {
|
||||
ForLoopAttribute() { getName() = "*ngFor" }
|
||||
|
||||
/** Gets a data-flow node holding the value being iterated over. */
|
||||
DataFlow::Node getIterationDomain() { result = getAttributeValueAsNode(this) }
|
||||
|
||||
/** Gets the name of the variable holding the element of the current iteration. */
|
||||
string getIteratorName() { result = getValue().regexpCapture(" *let +(\\w+).*", 1) }
|
||||
|
||||
/** Gets an HTML element in which the iterator variable is in scope. */
|
||||
HTML::Element getAnElementInScope() { result.getParent*() = getElement() }
|
||||
|
||||
/** Gets a reference to the iterator variable. */
|
||||
DataFlow::Node getAnIteratorAccess() {
|
||||
result =
|
||||
getAnElementInScope()
|
||||
.getAnAttribute()
|
||||
.getCodeInAttribute()
|
||||
.(TemplateTopLevel)
|
||||
.getAVariableUse(getIteratorName())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint step `array -> elem` in `*ngFor="let elem of array"`, or more precisely,
|
||||
* a step from `array` to each access to `elem`.
|
||||
*/
|
||||
private class ForLoopStep extends TaintTracking::AdditionalTaintStep {
|
||||
ForLoopAttribute attrib;
|
||||
|
||||
ForLoopStep() { this = attrib.getIterationDomain() }
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = this and
|
||||
succ = attrib.getAnIteratorAccess()
|
||||
}
|
||||
}
|
||||
|
||||
private class AnyCastStep extends PreCallGraphStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::CallNode call |
|
||||
call = any(TemplateTopLevel tl).getAVariableUse("$any").getACall() and
|
||||
pred = call.getArgument(0) and
|
||||
succ = call
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an invocation of the pipe of the given name.
|
||||
*
|
||||
* For example, the call generated from `items | async` would be found by `getAPipeCall("async")`.
|
||||
*/
|
||||
DataFlow::CallNode getAPipeCall(string name) {
|
||||
result.getCalleeNode().asExpr().(PipeRefExpr).getName() = name
|
||||
}
|
||||
|
||||
private class BuiltinPipeStep extends TaintTracking::AdditionalTaintStep, DataFlow::CallNode {
|
||||
string name;
|
||||
|
||||
BuiltinPipeStep() { this = getAPipeCall(name) }
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
succ = this and
|
||||
exists(int i | pred = getArgument(i) |
|
||||
i = 0 and
|
||||
name =
|
||||
[
|
||||
"async", "i18nPlural", "json", "keyvalue", "lowercase", "uppercase", "titlecase",
|
||||
"slice"
|
||||
]
|
||||
or
|
||||
i = 1 and name = "date" // date format string
|
||||
)
|
||||
or
|
||||
name = "translate" and
|
||||
succ = this and
|
||||
pred = [getArgument(1), getOptionArgument(1, _)]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A `<mat-table>` element.
|
||||
*/
|
||||
class MatTableElement extends HTML::Element {
|
||||
MatTableElement() { getName() = "mat-table" }
|
||||
|
||||
/** Gets the data flow node corresponding to the `[dataSource]` attribute. */
|
||||
DataFlow::Node getDataSourceNode() {
|
||||
result = getAttributeValueAsNode(getAttributeByName("[dataSource]"))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an element of form `<mat-cell *matCellDef="let rowBinding">` in this table.
|
||||
*/
|
||||
HTML::Element getATableCell(string rowBinding) {
|
||||
result.getName() = "mat-cell" and
|
||||
result.getParent+() = this and
|
||||
rowBinding =
|
||||
result.getAttributeByName("*matCellDef").getValue().regexpCapture(" *let +(\\w+).*", 1)
|
||||
}
|
||||
|
||||
/** Gets a data flow node that refers to one of the rows from the data source. */
|
||||
DataFlow::Node getARowRef() {
|
||||
exists(string rowBinding |
|
||||
result =
|
||||
getATableCell(rowBinding)
|
||||
.getChild*()
|
||||
.getAnAttribute()
|
||||
.getCodeInAttribute()
|
||||
.(TemplateTopLevel)
|
||||
.getAVariableUse(rowBinding)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint step from `x -> y` in code of form:
|
||||
* ```
|
||||
* <mat-table [dataSource]="x">
|
||||
* <mat-cell *matCellDef="let y">
|
||||
* <foo [prop]="y"/>
|
||||
* </mat-cell>
|
||||
* </mat-table>
|
||||
* ```
|
||||
*/
|
||||
private class MatTableTaintStep extends TaintTracking::AdditionalTaintStep {
|
||||
MatTableElement table;
|
||||
|
||||
MatTableTaintStep() { this = table.getDataSourceNode() }
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = this and
|
||||
succ = table.getARowRef()
|
||||
}
|
||||
}
|
||||
|
||||
/** A taint step into the data array of a `MatTableDataSource` instance. */
|
||||
private class MatTableDataSourceStep extends TaintTracking::AdditionalTaintStep, DataFlow::NewNode {
|
||||
MatTableDataSourceStep() {
|
||||
this =
|
||||
DataFlow::moduleMember("@angular/material/table", "MatTableDataSource").getAnInstantiation()
|
||||
}
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = [getArgument(0), getAPropertyWrite("data").getRhs()] and
|
||||
succ = this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
77
javascript/ql/src/semmle/javascript/frameworks/RxJS.qll
Normal file
77
javascript/ql/src/semmle/javascript/frameworks/RxJS.qll
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Provides taint steps modeling flow through `rxjs` Observable objects.
|
||||
*/
|
||||
|
||||
private import javascript
|
||||
|
||||
/**
|
||||
* A step `x -> y` in `x.subscribe(y => ...)`, modeling flow out of an rxjs Observable.
|
||||
*/
|
||||
private class RxJsSubscribeStep extends TaintTracking::AdditionalTaintStep, DataFlow::MethodCallNode {
|
||||
RxJsSubscribeStep() { getMethodName() = "subscribe" }
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = getReceiver() and
|
||||
succ = getCallback(0).getParameter(0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data flow node that can take the value of any input sent to `pipe`.
|
||||
*
|
||||
* For example, in `map(x => ...)`, `x` refers to any value sent to the pipe
|
||||
* created by the `map` call.
|
||||
*/
|
||||
private DataFlow::Node pipeInput(DataFlow::CallNode pipe) {
|
||||
pipe = DataFlow::moduleMember("rxjs/operators", ["map", "filter"]).getACall() and
|
||||
result = pipe.getCallback(0).getParameter(0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data flow node whose value becomes the output of the given `pipe`.
|
||||
*
|
||||
* For example, in `map(x => x + 1)`, the `x + 1` node becomes the output of
|
||||
* the pipe.
|
||||
*/
|
||||
private DataFlow::Node pipeOutput(DataFlow::CallNode pipe) {
|
||||
pipe = DataFlow::moduleMember("rxjs/operators", "map").getACall() and
|
||||
result = pipe.getCallback(0).getReturnNode()
|
||||
or
|
||||
pipe = DataFlow::moduleMember("rxjs/operators", "filter").getACall() and
|
||||
result = pipe.getCallback(0).getParameter(0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `pipe` acts as the identity function for success values.
|
||||
*
|
||||
* We currently lack a data-flow node to represent its input/ouput so it must
|
||||
* be special-cased.
|
||||
*/
|
||||
private predicate isIdentityPipe(DataFlow::CallNode pipe) {
|
||||
pipe = DataFlow::moduleMember("rxjs/operators", "catchError").getACall()
|
||||
}
|
||||
|
||||
/**
|
||||
* A step in or out of the map callback in a call of form `x.pipe(map(y => ...))`.
|
||||
*/
|
||||
private class RxJsPipeMapStep extends TaintTracking::AdditionalTaintStep, DataFlow::MethodCallNode {
|
||||
RxJsPipeMapStep() { getMethodName() = "pipe" }
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = getReceiver() and
|
||||
succ = pipeInput(getArgument(0).getALocalSource())
|
||||
or
|
||||
exists(int i |
|
||||
pred = pipeOutput(getArgument(i).getALocalSource()) and
|
||||
succ = pipeInput(getArgument(i + 1).getALocalSource())
|
||||
)
|
||||
or
|
||||
pred = pipeOutput(getLastArgument().getALocalSource()) and
|
||||
succ = this
|
||||
or
|
||||
// Handle a common case where the last step is `catchError`.
|
||||
isIdentityPipe(getLastArgument().getALocalSource()) and
|
||||
pred = pipeOutput(getArgument(getNumArgument() - 2)) and
|
||||
succ = this
|
||||
}
|
||||
}
|
||||
@@ -124,13 +124,24 @@ case @toplevel.kind of
|
||||
0 = @script
|
||||
| 1 = @inline_script
|
||||
| 2 = @event_handler
|
||||
| 3 = @javascript_url;
|
||||
| 3 = @javascript_url
|
||||
| 4 = @angular_template_toplevel;
|
||||
|
||||
is_module (int tl: @toplevel ref);
|
||||
is_nodejs (int tl: @toplevel ref);
|
||||
is_es2015_module (int tl: @toplevel ref);
|
||||
is_closure_module (int tl: @toplevel ref);
|
||||
|
||||
@xml_node_with_code = @xmlelement | @xmlattribute;
|
||||
toplevel_parent_xml_node(
|
||||
unique int toplevel: @toplevel ref,
|
||||
int xmlnode: @xml_node_with_code ref);
|
||||
|
||||
xml_element_parent_expression(
|
||||
unique int xmlnode: @xmlelement ref,
|
||||
int expression: @expr ref,
|
||||
int index: int ref);
|
||||
|
||||
// statements
|
||||
#keyset[parent, idx]
|
||||
stmts (unique int id: @stmt,
|
||||
@@ -354,6 +365,7 @@ case @expr.kind of
|
||||
| 116 = @assignlogandexpr
|
||||
| 117 = @assignlogorexpr
|
||||
| 118 = @assignnullishcoalescingexpr
|
||||
| 119 = @angular_pipe_ref
|
||||
;
|
||||
|
||||
@varaccess = @proper_varaccess | @export_varaccess;
|
||||
|
||||
@@ -50,6 +50,10 @@
|
||||
<v>3</v>
|
||||
</e>
|
||||
<e>
|
||||
<k>@angular_template_toplevel</k>
|
||||
<v>100</v>
|
||||
</e>
|
||||
<e>
|
||||
<k>@stmt</k>
|
||||
<v>1096691</v>
|
||||
</e>
|
||||
@@ -434,6 +438,10 @@
|
||||
<v>1</v>
|
||||
</e>
|
||||
<e>
|
||||
<k>@angular_pipe_ref</k>
|
||||
<v>100</v>
|
||||
</e>
|
||||
<e>
|
||||
<k>@preinc_expr</k>
|
||||
<v>1792</v>
|
||||
</e>
|
||||
@@ -9264,6 +9272,170 @@
|
||||
<dependencies/>
|
||||
</relation>
|
||||
<relation>
|
||||
<name>toplevel_parent_xml_node</name>
|
||||
<cardinality>43</cardinality>
|
||||
<columnsizes>
|
||||
<e>
|
||||
<k>toplevel</k>
|
||||
<v>43</v>
|
||||
</e>
|
||||
<e>
|
||||
<k>xmlnode</k>
|
||||
<v>43</v>
|
||||
</e>
|
||||
</columnsizes>
|
||||
<dependencies>
|
||||
<dep>
|
||||
<src>toplevel</src>
|
||||
<trg>xmlnode</trg>
|
||||
<val>
|
||||
<hist>
|
||||
<budget>12</budget>
|
||||
<bs>
|
||||
<b>
|
||||
<a>1</a>
|
||||
<b>2</b>
|
||||
<v>43</v>
|
||||
</b>
|
||||
</bs>
|
||||
</hist>
|
||||
</val>
|
||||
</dep>
|
||||
<dep>
|
||||
<src>xmlnode</src>
|
||||
<trg>toplevel</trg>
|
||||
<val>
|
||||
<hist>
|
||||
<budget>12</budget>
|
||||
<bs>
|
||||
<b>
|
||||
<a>1</a>
|
||||
<b>2</b>
|
||||
<v>43</v>
|
||||
</b>
|
||||
</bs>
|
||||
</hist>
|
||||
</val>
|
||||
</dep>
|
||||
</dependencies>
|
||||
</relation>
|
||||
<relation>
|
||||
<name>xml_element_parent_expression</name>
|
||||
<cardinality>1</cardinality>
|
||||
<columnsizes>
|
||||
<e>
|
||||
<k>xmlnode</k>
|
||||
<v>1</v>
|
||||
</e>
|
||||
<e>
|
||||
<k>expression</k>
|
||||
<v>1</v>
|
||||
</e>
|
||||
<e>
|
||||
<k>index</k>
|
||||
<v>1</v>
|
||||
</e>
|
||||
</columnsizes>
|
||||
<dependencies>
|
||||
<dep>
|
||||
<src>xmlnode</src>
|
||||
<trg>expression</trg>
|
||||
<val>
|
||||
<hist>
|
||||
<budget>12</budget>
|
||||
<bs>
|
||||
<b>
|
||||
<a>1</a>
|
||||
<b>2</b>
|
||||
<v>1</v>
|
||||
</b>
|
||||
</bs>
|
||||
</hist>
|
||||
</val>
|
||||
</dep>
|
||||
<dep>
|
||||
<src>xmlnode</src>
|
||||
<trg>index</trg>
|
||||
<val>
|
||||
<hist>
|
||||
<budget>12</budget>
|
||||
<bs>
|
||||
<b>
|
||||
<a>1</a>
|
||||
<b>2</b>
|
||||
<v>1</v>
|
||||
</b>
|
||||
</bs>
|
||||
</hist>
|
||||
</val>
|
||||
</dep>
|
||||
<dep>
|
||||
<src>expression</src>
|
||||
<trg>xmlnode</trg>
|
||||
<val>
|
||||
<hist>
|
||||
<budget>12</budget>
|
||||
<bs>
|
||||
<b>
|
||||
<a>1</a>
|
||||
<b>2</b>
|
||||
<v>1</v>
|
||||
</b>
|
||||
</bs>
|
||||
</hist>
|
||||
</val>
|
||||
</dep>
|
||||
<dep>
|
||||
<src>expression</src>
|
||||
<trg>index</trg>
|
||||
<val>
|
||||
<hist>
|
||||
<budget>12</budget>
|
||||
<bs>
|
||||
<b>
|
||||
<a>1</a>
|
||||
<b>2</b>
|
||||
<v>1</v>
|
||||
</b>
|
||||
</bs>
|
||||
</hist>
|
||||
</val>
|
||||
</dep>
|
||||
<dep>
|
||||
<src>index</src>
|
||||
<trg>xmlnode</trg>
|
||||
<val>
|
||||
<hist>
|
||||
<budget>12</budget>
|
||||
<bs>
|
||||
<b>
|
||||
<a>1</a>
|
||||
<b>2</b>
|
||||
<v>1</v>
|
||||
</b>
|
||||
</bs>
|
||||
</hist>
|
||||
</val>
|
||||
</dep>
|
||||
<dep>
|
||||
<src>index</src>
|
||||
<trg>expression</trg>
|
||||
<val>
|
||||
<hist>
|
||||
<budget>12</budget>
|
||||
<bs>
|
||||
<b>
|
||||
<a>1</a>
|
||||
<b>2</b>
|
||||
<v>1</v>
|
||||
</b>
|
||||
</bs>
|
||||
</hist>
|
||||
</val>
|
||||
</dep>
|
||||
</dependencies>
|
||||
</relation>
|
||||
<relation>
|
||||
<name>is_nodejs</name>
|
||||
<cardinality>12</cardinality>
|
||||
<columnsizes>
|
||||
|
||||
Reference in New Issue
Block a user