Merge pull request #3336 from erik-krogh/MoarJQuery

Approved by esbena
This commit is contained in:
semmle-qlci
2020-04-25 15:17:55 +01:00
committed by GitHub
11 changed files with 162 additions and 69 deletions

View File

@@ -16,7 +16,8 @@ import semmle.javascript.security.dataflow.UnsafeJQueryPlugin::UnsafeJQueryPlugi
import DataFlow::PathGraph
from
Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, JQueryPluginMethod plugin
Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink,
JQuery::JQueryPluginMethod plugin
where
cfg.hasFlowPath(source, sink) and
source.getNode().(Source).getPlugin() = plugin and

View File

@@ -305,6 +305,13 @@ module DOM {
call.getNumArgument() = 1 and
forex(InferredType t | t = call.getArgument(0).analyze().getAType() | t = TTNumber())
)
or
// A `this` node from a callback given to a `$().each(callback)` call.
// purposely not using JQuery::MethodCall to avoid `jquery.each()`.
exists(DataFlow::CallNode eachCall | eachCall = JQuery::objectRef().getAMethodCall("each") |
this = DataFlow::thisNode(eachCall.getCallback(0).getFunction()) or
this = eachCall.getABoundCallbackParameter(0, 1)
)
}
}
}

View File

@@ -496,6 +496,15 @@ module JQuery {
hasUnderlyingType("jQuery")
}
}
/**
* A `this` node in a JQuery plugin function, which is a JQuery object.
*/
private class JQueryPluginThisObject extends Range {
JQueryPluginThisObject() {
this = DataFlow::thisNode(any(JQueryPluginMethod method).getFunction())
}
}
}
/** A source of jQuery objects from the AST-based `JQueryObject` class. */
@@ -590,4 +599,64 @@ module JQuery {
node = getArgument(0)
}
}
/**
* Holds for jQuery plugin definitions of the form `$.fn.<pluginName> = <plugin>` or `$.extend($.fn, {<pluginName>, <plugin>})`.
*/
private predicate jQueryPluginDefinition(string pluginName, DataFlow::Node plugin) {
exists(DataFlow::PropRead fn, DataFlow::PropWrite write |
fn = jquery().getAPropertyRead("fn") and
(
write = fn.getAPropertyWrite()
or
exists(ExtendCall extend, DataFlow::SourceNode source |
fn.flowsTo(extend.getDestinationOperand()) and
source = extend.getASourceOperand() and
write = source.getAPropertyWrite()
)
) and
plugin = write.getRhs() and
(
pluginName = write.getPropertyName() or
write.getPropertyNameExpr().flow().mayHaveStringValue(pluginName)
)
)
}
/**
* Gets a node that is registered as a jQuery plugin method at `def`.
*/
private DataFlow::SourceNode getAJQueryPluginMethod(
DataFlow::TypeBackTracker t, DataFlow::Node def
) {
t.start() and jQueryPluginDefinition(_, def) and result.flowsTo(def)
or
exists(DataFlow::TypeBackTracker t2 | result = getAJQueryPluginMethod(t2, def).backtrack(t2, t))
}
/**
* Gets a function that is registered as a jQuery plugin method at `def`.
*/
private DataFlow::FunctionNode getAJQueryPluginMethod(DataFlow::Node def) {
result = getAJQueryPluginMethod(DataFlow::TypeBackTracker::end(), def)
}
/**
* A function that is registered as a jQuery plugin method.
*/
class JQueryPluginMethod extends DataFlow::FunctionNode {
string pluginName;
JQueryPluginMethod() {
exists(DataFlow::Node def |
jQueryPluginDefinition(pluginName, def) and
this = getAJQueryPluginMethod(def)
)
}
/**
* Gets the name of this plugin.
*/
string getPluginName() { result = pluginName }
}
}

View File

@@ -18,7 +18,7 @@ module UnsafeJQueryPlugin {
/**
* Gets the plugin that this source is used in.
*/
abstract JQueryPluginMethod getPlugin();
abstract JQuery::JQueryPluginMethod getPlugin();
}
/**
@@ -49,49 +49,6 @@ module UnsafeJQueryPlugin {
}
}
/**
* Holds for jQuery plugin definitions of the form `$.fn.<pluginName> = <plugin>`.
*/
private predicate jQueryPluginDefinition(string pluginName, DataFlow::Node plugin) {
exists(DataFlow::PropRead fn, DataFlow::PropWrite write |
fn = jquery().getAPropertyRead("fn") and
(
write = fn.getAPropertyWrite()
or
exists(ExtendCall extend, DataFlow::SourceNode source |
fn.flowsTo(extend.getDestinationOperand()) and
source = extend.getASourceOperand() and
write = source.getAPropertyWrite()
)
) and
plugin = write.getRhs() and
(
pluginName = write.getPropertyName() or
write.getPropertyNameExpr().flow().mayHaveStringValue(pluginName)
)
)
}
/**
* Gets a node that is registered as a jQuery plugin method at `def`.
*/
private DataFlow::SourceNode getAJQueryPluginMethod(
DataFlow::TypeBackTracker t, DataFlow::Node def
) {
t.start() and
jQueryPluginDefinition(_, def) and
result.flowsTo(def)
or
exists(DataFlow::TypeBackTracker t2 | result = getAJQueryPluginMethod(t2, def).backtrack(t2, t))
}
/**
* Gets a function that is registered as a jQuery plugin method at `def`.
*/
private DataFlow::FunctionNode getAJQueryPluginMethod(DataFlow::Node def) {
result = getAJQueryPluginMethod(DataFlow::TypeBackTracker::end(), def)
}
/**
* Gets an operand to `extend`.
*/
@@ -109,29 +66,10 @@ module UnsafeJQueryPlugin {
result = getAnExtendOperand(DataFlow::TypeBackTracker::end(), extend)
}
/**
* A function that is registered as a jQuery plugin method.
*/
class JQueryPluginMethod extends DataFlow::FunctionNode {
string pluginName;
JQueryPluginMethod() {
exists(DataFlow::Node def |
jQueryPluginDefinition(pluginName, def) and
this = getAJQueryPluginMethod(def)
)
}
/**
* Gets the name of this plugin.
*/
string getPluginName() { result = pluginName }
}
/**
* Holds if `plugin` has a default option defined at `def`.
*/
private predicate hasDefaultOption(JQueryPluginMethod plugin, DataFlow::PropWrite def) {
private predicate hasDefaultOption(JQuery::JQueryPluginMethod plugin, DataFlow::PropWrite def) {
exists(ExtendCall extend, JQueryPluginOptions options, DataFlow::SourceNode default |
options.getPlugin() = plugin and
options = getAnExtendOperand(extend) and
@@ -144,7 +82,7 @@ module UnsafeJQueryPlugin {
* The client-provided options object for a jQuery plugin.
*/
class JQueryPluginOptions extends DataFlow::ParameterNode {
JQueryPluginMethod method;
JQuery::JQueryPluginMethod method;
JQueryPluginOptions() {
exists(string optionsPattern |
@@ -165,7 +103,7 @@ module UnsafeJQueryPlugin {
/**
* Gets the plugin method that these options are used in.
*/
JQueryPluginMethod getPlugin() { result = method }
JQuery::JQueryPluginMethod getPlugin() { result = method }
}
/**
@@ -224,7 +162,9 @@ module UnsafeJQueryPlugin {
* The client-provided options object for a jQuery plugin, considered as a source for unsafe jQuery plugins.
*/
class JQueryPluginOptionsAsSource extends Source, JQueryPluginOptions {
override JQueryPluginMethod getPlugin() { result = JQueryPluginOptions.super.getPlugin() }
override JQuery::JQueryPluginMethod getPlugin() {
result = JQueryPluginOptions.super.getPlugin()
}
}
/**
@@ -246,7 +186,7 @@ module UnsafeJQueryPlugin {
/**
* Holds if `plugin` likely expects `sink` to be treated as a HTML fragment.
*/
predicate isLikelyIntentionalHtmlSink(JQueryPluginMethod plugin, Sink sink) {
predicate isLikelyIntentionalHtmlSink(JQuery::JQueryPluginMethod plugin, Sink sink) {
exists(DataFlow::PropWrite defaultDef, string default, DataFlow::PropRead finalRead |
hasDefaultOption(plugin, defaultDef) and
defaultDef.getPropertyName() = finalRead.getPropertyName() and

View File

@@ -80,6 +80,7 @@ module DomBasedXss {
not exists(DataFlow::Node prefix, string strval |
isPrefixOfJQueryHtmlString(this, prefix) and
strval = prefix.getStringValue() and
not strval = "" and
not strval.regexpMatch("\\s*<.*")
) and
not DOM::locationRef().flowsTo(this)