Merge pull request #941 from esben-semmle/js/vue-support-2

JS: Vue security improvements
This commit is contained in:
Max Schaefer
2019-02-26 16:49:38 +00:00
committed by GitHub
16 changed files with 232 additions and 3 deletions

View File

@@ -40,7 +40,8 @@ module DataFlow {
} or
TDestructuredModuleImportNode(ImportDeclaration decl) {
exists(decl.getASpecifier().getImportedName())
}
} or
THtmlAttributeNode(HTML::Attribute attr)
/**
* A node in the data flow graph.
@@ -115,7 +116,9 @@ module DataFlow {
int getIntValue() { result = asExpr().getIntValue() }
/** Gets a function value that may reach this node. */
FunctionNode getAFunctionValue() { result.getAstNode() = analyze().getAValue().(AbstractCallable).getFunction() }
FunctionNode getAFunctionValue() {
result.getAstNode() = analyze().getAValue().(AbstractCallable).getFunction()
}
/**
* Holds if this expression may refer to the initial value of parameter `p`.
@@ -738,6 +741,26 @@ module DataFlow {
}
}
/**
* A data flow node representing an HTML attribute.
*/
class HtmlAttributeNode extends DataFlow::Node, THtmlAttributeNode {
HTML::Attribute attr;
HtmlAttributeNode() { this = THtmlAttributeNode(attr) }
override string toString() { result = attr.toString() }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
attr.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
/** Gets the attribute corresponding to this data flow node. */
HTML::Attribute getAttribute() { result = attr }
}
/**
* Provides classes representing various kinds of calls.
*
@@ -1134,7 +1157,7 @@ module DataFlow {
nd.asExpr() instanceof ExternalModuleReference and
cause = "import"
or
exists (Expr e | e = nd.asExpr() and cause = "heap" |
exists(Expr e | e = nd.asExpr() and cause = "heap" |
e instanceof PropAccess or
e instanceof E4X::XMLAnyName or
e instanceof E4X::XMLAttributeSelector or

View File

@@ -94,6 +94,11 @@ module Vue {
)
}
/**
* Gets the template element used by this instance, if any.
*/
abstract Template::Element getTemplateElement();
/**
* Gets the node for the `data` option object of this instance.
*/
@@ -245,6 +250,8 @@ module Vue {
}
override DataFlow::Node getOwnOption(string name) { result = def.getOptionArgument(0, name) }
override Template::Element getTemplateElement() { none() }
}
/**
@@ -264,6 +271,8 @@ module Vue {
}
override DataFlow::Node getOwnOption(string name) { result = extend.getOptionArgument(0, name) }
override Template::Element getTemplateElement() { none() }
}
/**
@@ -291,6 +300,8 @@ module Vue {
or
result = MkExtendedVue(extend).(ExtendedVue).getOption(name)
}
override Template::Element getTemplateElement() { none() }
}
/**
@@ -310,6 +321,8 @@ module Vue {
}
override DataFlow::Node getOwnOption(string name) { result = def.getOptionArgument(1, name) }
override Template::Element getTemplateElement() { none() }
}
/**
@@ -320,6 +333,14 @@ module Vue {
SingleFileComponent() { this = MkSingleFileComponent(file) }
override Template::Element getTemplateElement() {
exists(HTML::Element e | result.(Template::HtmlElement).getElement() = e |
e.getFile() = file and
e.getName() = "template" and
e.isTopLevel()
)
}
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
@@ -366,4 +387,85 @@ module Vue {
class VueFile extends File {
VueFile() { getExtension() = "vue" }
}
/**
* A taint propagating data flow edge through a Vue instance property.
*/
class InstanceHeapStep extends TaintTracking::AdditionalTaintStep {
DataFlow::Node src;
InstanceHeapStep() {
exists(Instance i, string name, DataFlow::FunctionNode bound |
bound.flowsTo(i.getABoundFunction()) and
not bound.getFunction() instanceof ArrowFunctionExpr and
bound.getReceiver().getAPropertyRead(name) = this and
src = i.getAPropertyValue(name)
)
}
override predicate step(DataFlow::Node pred, DataFlow::Node succ) { pred = src and succ = this }
}
/*
* Provides classes for working with Vue templates.
*/
module Template {
// Currently only supports HTML elements, but it may be possible to parse simple string templates later
private newtype TElement = MkHtmlElement(HTML::Element e) { e.getFile() instanceof VueFile }
/**
* An element of a template.
*/
abstract class Element extends TElement {
/** Gets a textual representation of this element. */
string toString() { result = "<" + getName() + ">...</>" }
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
filepath = "" and
startline = 0 and
startcolumn = 0 and
endline = 0 and
endcolumn = 0
}
/**
* Gets the name of this element.
*
* For example, the name of `<br>` is `br`.
*/
abstract string getName();
}
/**
* An HTML element as a template element.
*/
class HtmlElement extends Element, MkHtmlElement {
HTML::Element elem;
HtmlElement() { this = MkHtmlElement(elem) }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
elem.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
override string getName() { result = elem.getName() }
/**
* Gets the HTML element of this element.
*/
HTML::Element getElement() { result = elem }
}
}
}

View File

@@ -198,6 +198,51 @@ module DomBasedXss {
}
}
/**
* A Vue `v-html` attribute, viewed as an XSS sink.
*/
class VHtmlSink extends DomBasedXss::Sink {
HTML::Attribute attr;
VHtmlSink() {
this.(DataFlow::HtmlAttributeNode).getAttribute() = attr and attr.getName() = "v-html"
}
/**
* Gets the HTML attribute of this sink.
*/
HTML::Attribute getAttr() { result = attr }
}
/**
* A taint propagating data flow edge through a string interpolation of a
* Vue instance property to a `v-html` attribute.
*
* As an example, `<div v-html="prop"/>` reads the `prop` property
* of `inst = new Vue({ ..., data: { prop: source } })`, if the
* `div` element is part of the template for `inst`.
*/
class VHtmlSourceWrite extends TaintTracking::AdditionalTaintStep {
VHtmlSink attr;
VHtmlSourceWrite() {
exists(Vue::Instance instance, string expr |
attr.getAttr().getRoot() = instance
.getTemplateElement()
.(Vue::Template::HtmlElement)
.getElement() and
expr = attr.getAttr().getValue() and
// only support for simple identifier expressions
expr.regexpMatch("(?i)[a-z0-9_]+") and
this = instance.getAPropertyValue(expr)
)
}
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred = this and succ = attr
}
}
/**
* A regexp replacement involving an HTML meta-character, viewed as a sanitizer for
* XSS vulnerabilities.