diff --git a/change-notes/1.24/analysis-javascript.md b/change-notes/1.24/analysis-javascript.md index fa2af328d12..47fb2dc590b 100644 --- a/change-notes/1.24/analysis-javascript.md +++ b/change-notes/1.24/analysis-javascript.md @@ -38,6 +38,7 @@ | Regular expression always matches (`js/regex/always-matches`) | correctness, regular-expressions | Highlights regular expression checks that trivially succeed by matching an empty substring. Results are shown on LGTM by default. | | Missing await (`js/missing-await`) | correctness | Highlights expressions that operate directly on a promise object in a nonsensical way, instead of awaiting its result. Results are shown on LGTM by default. | | Prototype pollution in utility function (`js/prototype-pollution-utility`) | security, external/cwe/cwe-400, external/cwe/cwe-471 | Highlights recursive copying operations that are susceptible to prototype pollution. Results are shown on LGTM by default. | +| Unsafe jQuery plugin (`js/unsafe-jquery-plugin`) | Highlights potential XSS vulnerabilities in unsafely designed jQuery plugins. Results are shown on LGTM by default. | ## Changes to existing queries diff --git a/javascript/config/suites/javascript/security b/javascript/config/suites/javascript/security index 301cfc8ed05..59ba38b0435 100644 --- a/javascript/config/suites/javascript/security +++ b/javascript/config/suites/javascript/security @@ -14,6 +14,7 @@ + semmlecode-javascript-queries/Security/CWE-079/ReflectedXss.ql: /Security/CWE/CWE-079 + semmlecode-javascript-queries/Security/CWE-079/StoredXss.ql: /Security/CWE/CWE-079 + semmlecode-javascript-queries/Security/CWE-079/Xss.ql: /Security/CWE/CWE-079 ++ semmlecode-javascript-queries/Security/CWE-079/UnsafeJQueryPlugin.ql: /Security/CWE/CWE-079 + semmlecode-javascript-queries/Security/CWE-089/SqlInjection.ql: /Security/CWE/CWE-089 + semmlecode-javascript-queries/Security/CWE-094/CodeInjection.ql: /Security/CWE/CWE-094 + semmlecode-javascript-queries/Security/CWE-094/UnsafeDynamicMethodAccess.ql: /Security/CWE/CWE-094 diff --git a/javascript/ql/src/Security/CWE-079/UnsafeJQueryPlugin.qhelp b/javascript/ql/src/Security/CWE-079/UnsafeJQueryPlugin.qhelp new file mode 100644 index 00000000000..7920c7ee891 --- /dev/null +++ b/javascript/ql/src/Security/CWE-079/UnsafeJQueryPlugin.qhelp @@ -0,0 +1,101 @@ + + + + +

+ + Library plugins, such as those for the jQuery library, are often + configurable through options provided by the clients of the + plugin. + + + Clients, however, do not know the implementation details + of the plugin, so it is important to document the capabilities of each + option. The documentation for the plugin options that the client is + responsible for sanitizing is of particular importance. + + Otherwise, the plugin may write user input (for example, a URL query + parameter) to a web page without properly sanitizing it first, + which allows for a cross-site scripting vulnerability in the client + application through dynamic HTML construction. + +

+
+ + +

+ + Document all options that can lead to cross-site scripting + attacks, and guard against unsafe inputs where dynamic HTML + construction is not intended. + +

+
+ + +

+ + The following example shows a jQuery plugin that selects a + DOM element, and copies its text content to another DOM element. The + selection is performed by using the plugin option + sourceSelector as a CSS selector. + +

+ + + +

+ + This is, however, not a safe plugin, since the call to + jQuery interprets sourceSelector as HTML if + it is a string that starts with <. + +

+ +

+ + Instead of documenting that the client is responsible for + sanitizing sourceSelector, the plugin can use + jQuery.find to always interpret + sourceSelector as a CSS selector: + +

+ + + + +
+ + +
  • + OWASP: + DOM based + XSS Prevention Cheat Sheet. +
  • +
  • + OWASP: + XSS + (Cross Site Scripting) Prevention Cheat Sheet. +
  • +
  • + OWASP + DOM Based XSS. +
  • +
  • + OWASP + Types of Cross-Site + Scripting. +
  • +
  • + Wikipedia: Cross-site scripting. +
  • +
  • + jQuery: Plugin creation. +
  • +
  • + Bootstrap: XSS vulnerable bootstrap plugins. +
  • +
    +
    diff --git a/javascript/ql/src/Security/CWE-079/UnsafeJQueryPlugin.ql b/javascript/ql/src/Security/CWE-079/UnsafeJQueryPlugin.ql new file mode 100644 index 00000000000..2df7769906d --- /dev/null +++ b/javascript/ql/src/Security/CWE-079/UnsafeJQueryPlugin.ql @@ -0,0 +1,25 @@ +/** + * @name Unsafe jQuery plugin + * @description A jQuery plugin that unintentionally constructs HTML from some of its options may be unsafe to use for clients. + * @kind path-problem + * @problem.severity warning + * @precision high + * @id js/unsafe-jquery-plugin + * @tags security + * external/cwe/cwe-079 + * external/cwe/cwe-116 + * frameworks/jquery + */ + +import javascript +import semmle.javascript.security.dataflow.UnsafeJQueryPlugin::UnsafeJQueryPlugin +import DataFlow::PathGraph + +from + Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, JQueryPluginMethod plugin +where + cfg.hasFlowPath(source, sink) and + source.getNode().(Source).getPlugin() = plugin and + not isLikelyIntentionalHtmlSink(plugin, sink.getNode()) +select sink.getNode(), source, sink, "Potential XSS vulnerability in the $@.", plugin, + "'$.fn." + plugin.getPluginName() + "' plugin" diff --git a/javascript/ql/src/Security/CWE-079/examples/UnsafeJQueryPlugin.js b/javascript/ql/src/Security/CWE-079/examples/UnsafeJQueryPlugin.js new file mode 100644 index 00000000000..eb5ae7f9704 --- /dev/null +++ b/javascript/ql/src/Security/CWE-079/examples/UnsafeJQueryPlugin.js @@ -0,0 +1,6 @@ +jQuery.fn.copyText = function(options) { + // BAD may evaluate `options.sourceSelector` as HTML + var source = jQuery(options.sourceSelector), + text = source.text(); + jQuery(this).text(text); +} diff --git a/javascript/ql/src/Security/CWE-079/examples/UnsafeJQueryPlugin_safe.js b/javascript/ql/src/Security/CWE-079/examples/UnsafeJQueryPlugin_safe.js new file mode 100644 index 00000000000..ea613642d0f --- /dev/null +++ b/javascript/ql/src/Security/CWE-079/examples/UnsafeJQueryPlugin_safe.js @@ -0,0 +1,6 @@ +jQuery.fn.copyText = function(options) { + // GOOD may not evaluate `options.sourceSelector` as HTML + var source = jQuery.find(options.sourceSelector), + text = source.text(); + jQuery(this).text(text); +} diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/UnsafeJQueryPlugin.qll b/javascript/ql/src/semmle/javascript/security/dataflow/UnsafeJQueryPlugin.qll new file mode 100644 index 00000000000..b08cbb2f5f8 --- /dev/null +++ b/javascript/ql/src/semmle/javascript/security/dataflow/UnsafeJQueryPlugin.qll @@ -0,0 +1,55 @@ +/** + * Provides a taint-tracking configuration for reasoning about DOM-based + * cross-site scripting vulnerabilities in unsafe jQuery plugins. + */ + +import javascript +import semmle.javascript.security.dataflow.Xss + +module UnsafeJQueryPlugin { + import UnsafeJQueryPluginCustomizations::UnsafeJQueryPlugin + + /** + * A taint-tracking configuration for reasoning about XSS in unsafe jQuery plugins. + */ + class Configuration extends TaintTracking::Configuration { + Configuration() { this = "UnsafeJQueryPlugin" } + + override predicate isSource(DataFlow::Node source) { source instanceof Source } + + override predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + override predicate isSanitizer(DataFlow::Node node) { + super.isSanitizer(node) + or + node instanceof DomBasedXss::Sanitizer + or + node instanceof Sanitizer + } + + override predicate isAdditionalTaintStep(DataFlow::Node src, DataFlow::Node sink) { + // jQuery plugins tend to be implemented as classes that store data in fields initialized by the constructor. + DataFlow::localFieldStep(src, sink) + } + + override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) { + // prefixing prevents forced html/css confusion: + + // prefixing through concatenation: + StringConcatenation::taintStep(pred, succ, _, any(int i | i >= 1)) + or + // prefixing through a poor-mans templating system: + exists(DataFlow::MethodCallNode replace | + replace = succ and + pred = replace.getArgument(1) and + replace.getMethodName() = "replace" + ) + } + + override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode node) { + super.isSanitizerGuard(node) or + node instanceof IsElementSanitizer or + node instanceof PropertyPresenceSanitizer + } + } +} diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/UnsafeJQueryPluginCustomizations.qll b/javascript/ql/src/semmle/javascript/security/dataflow/UnsafeJQueryPluginCustomizations.qll new file mode 100644 index 00000000000..0da1f81beb9 --- /dev/null +++ b/javascript/ql/src/semmle/javascript/security/dataflow/UnsafeJQueryPluginCustomizations.qll @@ -0,0 +1,258 @@ +/** + * Provides default sources, sinks and sanitizers for reasoning about + * unsafe jQuery plugins, as well as extension points for adding your + * own. + */ + +import javascript +private import semmle.javascript.dataflow.InferredTypes +import semmle.javascript.security.dataflow.Xss + +module UnsafeJQueryPlugin { + private import DataFlow::FlowLabel + + /** + * A data flow source for unsafe jQuery plugins. + */ + abstract class Source extends DataFlow::Node { + /** + * Gets the plugin that this source is used in. + */ + abstract JQueryPluginMethod getPlugin(); + } + + /** + * A data flow sink for unsafe jQuery plugins. + */ + abstract class Sink extends DataFlow::Node { } + + /** + * A sanitizer for unsafe jQuery plugins. + */ + abstract class Sanitizer extends DataFlow::Node { } + + /** + * An argument that may act as a HTML fragment rather than a CSS selector, as a sink for remote unsafe jQuery plugins. + */ + class AmbiguousHtmlOrSelectorArgument extends DataFlow::Node { + AmbiguousHtmlOrSelectorArgument() { + exists(JQuery::MethodCall call | + call.interpretsArgumentAsSelector(this) and call.interpretsArgumentAsHtml(this) + ) and + // the $-function in particular will not construct HTML for non-string values + analyze().getAType() = TTString() and + // any fixed prefix makes the call unambiguous + not exists(DataFlow::Node prefix | + DomBasedXss::isPrefixOfJQueryHtmlString(this, prefix) and + prefix.mayHaveStringValue(_) + ) + } + } + + /** + * Holds for jQuery plugin definitions of the form `$.fn. = `. + */ + 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`. + */ + private DataFlow::SourceNode getAnExtendOperand(DataFlow::TypeBackTracker t, ExtendCall extend) { + t.start() and + result.flowsTo(extend.getAnOperand()) + or + exists(DataFlow::TypeBackTracker t2 | result = getAnExtendOperand(t2, extend).backtrack(t2, t)) + } + + /** + * Gets an operand to `extend`. + */ + private DataFlow::SourceNode getAnExtendOperand(ExtendCall extend) { + 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) { + exists(ExtendCall extend, JQueryPluginOptions options, DataFlow::SourceNode default | + options.getPlugin() = plugin and + options = getAnExtendOperand(extend) and + default = getAnExtendOperand(extend) and + default.getAPropertyWrite() = def + ) + } + + /** + * The client-provided options object for a jQuery plugin. + */ + class JQueryPluginOptions extends DataFlow::ParameterNode { + JQueryPluginMethod method; + + JQueryPluginOptions() { + exists(string optionsPattern | + optionsPattern = "(?i)(opt(ion)?s?)" and + if method.getAParameter().getName().regexpMatch(optionsPattern) + then ( + // use the last parameter named something like "options" if it exists ... + getName().regexpMatch(optionsPattern) and + this = method.getAParameter() + ) else ( + // ... otherwise, use the last parameter, unless it looks like a DOM node + this = method.getLastParameter() and + not getName().regexpMatch("(?i)(e(l(em(ent(s)?)?)?)?)") + ) + ) + } + + /** + * Gets the plugin method that these options are used in. + */ + JQueryPluginMethod getPlugin() { result = method } + } + + /** + * Expression of form `isElement(x)`, which sanitizes `x`. + */ + class IsElementSanitizer extends TaintTracking::SanitizerGuardNode, DataFlow::CallNode { + IsElementSanitizer() { + // common ad hoc sanitizing calls + exists(string name | getCalleeName() = name | + name = "isElement" or name = "isDocument" or name = "isWindow" + ) + } + + override predicate sanitizes(boolean outcome, Expr e) { + outcome = true and e = getArgument(0).asExpr() + } + } + + /** + * Expression like `typeof x. !== "undefined"` or `x.`, which sanitizes `x`, as it is unlikely to be a string afterwards. + */ + class PropertyPresenceSanitizer extends TaintTracking::SanitizerGuardNode, DataFlow::ValueNode { + DataFlow::Node input; + boolean polarity; + + PropertyPresenceSanitizer() { + exists(DataFlow::PropRead read, string name | + not name = "length" and read.accesses(input, name) + | + exists(EqualityTest test | + polarity = test.getPolarity().booleanNot() and + this = test.flow() + | + exists(Expr undef | test.hasOperands(read.asExpr(), undef) | + SyntacticConstants::isUndefined(undef) + ) + or + exists(Expr op1, Expr op2 | test.hasOperands(op1, op2) | + read.asExpr() = op1.(TypeofExpr).getOperand() and + op2.mayHaveStringValue(any(InferredType t | t = TTUndefined()).getTypeofTag()) + ) + ) + or + polarity = true and + this = read + ) + } + + override predicate sanitizes(boolean outcome, Expr e) { + outcome = polarity and + e = input.asExpr() + } + } + + /** + * 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() } + } + + /** + * An argument that may act as a HTML fragment rather than a CSS selector, as a sink for remote unsafe jQuery plugins. + */ + class AmbiguousHtmlOrSelectorArgumentAsSink extends Sink { + AmbiguousHtmlOrSelectorArgumentAsSink() { this instanceof AmbiguousHtmlOrSelectorArgument } + } + + /** + * A hint that a value is expected to be treated as a HTML fragment later. + */ + class IntentionalHtmlFragmentHint extends Sanitizer { + IntentionalHtmlFragmentHint() { + this.(DataFlow::PropRead).getPropertyName().regexpMatch("(?i).*(html|template).*") + } + } + + /** + * Holds if `plugin` likely expects `sink` to be treated as a HTML fragment. + */ + predicate isLikelyIntentionalHtmlSink(JQueryPluginMethod plugin, Sink sink) { + exists(DataFlow::PropWrite defaultDef, string default, DataFlow::PropRead finalRead | + hasDefaultOption(plugin, defaultDef) and + defaultDef.getPropertyName() = finalRead.getPropertyName() and + defaultDef.getRhs().mayHaveStringValue(default) and + default.regexpMatch("\\s*<.*") and + finalRead.flowsTo(sink) + ) + } +} diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll b/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll index badcf326f2c..d9567862fc6 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll @@ -106,7 +106,7 @@ module DomBasedXss { * Holds if `prefix` is a prefix of `htmlString`, which may be intepreted as * HTML by a jQuery method. */ - private predicate isPrefixOfJQueryHtmlString(DataFlow::Node htmlString, DataFlow::Node prefix) { + predicate isPrefixOfJQueryHtmlString(DataFlow::Node htmlString, DataFlow::Node prefix) { any(JQuery::MethodCall call).interpretsArgumentAsHtml(htmlString) and prefix = htmlString or diff --git a/javascript/ql/test/query-tests/Security/CWE-079/UnsafeJQueryPlugin.expected b/javascript/ql/test/query-tests/Security/CWE-079/UnsafeJQueryPlugin.expected new file mode 100644 index 00000000000..bc46d640340 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-079/UnsafeJQueryPlugin.expected @@ -0,0 +1,263 @@ +nodes +| unsafe-jquery-plugin.js:2:38:2:44 | options | +| unsafe-jquery-plugin.js:2:38:2:44 | options | +| unsafe-jquery-plugin.js:3:5:3:11 | options | +| unsafe-jquery-plugin.js:3:5:3:11 | options | +| unsafe-jquery-plugin.js:5:5:5:11 | options | +| unsafe-jquery-plugin.js:5:5:5:18 | options.target | +| unsafe-jquery-plugin.js:5:5:5:18 | options.target | +| unsafe-jquery-plugin.js:11:7:11:29 | target | +| unsafe-jquery-plugin.js:11:16:11:22 | options | +| unsafe-jquery-plugin.js:11:16:11:29 | options.target | +| unsafe-jquery-plugin.js:22:6:22:11 | target | +| unsafe-jquery-plugin.js:22:6:22:11 | target | +| unsafe-jquery-plugin.js:30:6:30:11 | target | +| unsafe-jquery-plugin.js:30:6:30:11 | target | +| unsafe-jquery-plugin.js:36:6:36:11 | target | +| unsafe-jquery-plugin.js:36:6:36:11 | target | +| unsafe-jquery-plugin.js:40:6:40:11 | target | +| unsafe-jquery-plugin.js:40:6:40:11 | target | +| unsafe-jquery-plugin.js:48:6:48:11 | target | +| unsafe-jquery-plugin.js:48:6:48:11 | target | +| unsafe-jquery-plugin.js:52:6:52:11 | target | +| unsafe-jquery-plugin.js:52:6:52:11 | target | +| unsafe-jquery-plugin.js:60:6:60:11 | target | +| unsafe-jquery-plugin.js:60:6:60:11 | target | +| unsafe-jquery-plugin.js:71:38:71:44 | options | +| unsafe-jquery-plugin.js:71:38:71:44 | options | +| unsafe-jquery-plugin.js:72:5:72:11 | options | +| unsafe-jquery-plugin.js:72:5:72:15 | options.foo | +| unsafe-jquery-plugin.js:72:5:72:19 | options.foo.bar | +| unsafe-jquery-plugin.js:72:5:72:23 | options.foo.bar.baz | +| unsafe-jquery-plugin.js:72:5:72:23 | options.foo.bar.baz | +| unsafe-jquery-plugin.js:76:38:76:44 | options | +| unsafe-jquery-plugin.js:76:38:76:44 | options | +| unsafe-jquery-plugin.js:77:17:77:23 | options | +| unsafe-jquery-plugin.js:77:17:77:27 | options.foo | +| unsafe-jquery-plugin.js:77:17:77:31 | options.foo.bar | +| unsafe-jquery-plugin.js:77:17:77:35 | options.foo.bar.baz | +| unsafe-jquery-plugin.js:77:17:77:35 | options.foo.bar.baz | +| unsafe-jquery-plugin.js:84:38:84:44 | options | +| unsafe-jquery-plugin.js:84:38:84:44 | options | +| unsafe-jquery-plugin.js:85:14:85:14 | o | +| unsafe-jquery-plugin.js:86:13:86:27 | $.extend({}, o) | +| unsafe-jquery-plugin.js:86:22:86:23 | {} | +| unsafe-jquery-plugin.js:86:26:86:26 | o | +| unsafe-jquery-plugin.js:87:8:87:24 | t | +| unsafe-jquery-plugin.js:87:12:87:17 | this.o | +| unsafe-jquery-plugin.js:87:12:87:24 | this.o.target | +| unsafe-jquery-plugin.js:90:6:90:6 | t | +| unsafe-jquery-plugin.js:90:6:90:6 | t | +| unsafe-jquery-plugin.js:92:5:92:11 | options | +| unsafe-jquery-plugin.js:101:38:101:44 | options | +| unsafe-jquery-plugin.js:101:38:101:44 | options | +| unsafe-jquery-plugin.js:102:3:105:13 | options | +| unsafe-jquery-plugin.js:102:13:105:13 | $.exten ... ptions) | +| unsafe-jquery-plugin.js:102:22:105:3 | {\\n\\t\\t\\tme ... in'\\n\\t\\t} | +| unsafe-jquery-plugin.js:105:6:105:12 | options | +| unsafe-jquery-plugin.js:106:5:106:11 | options | +| unsafe-jquery-plugin.js:106:5:106:16 | options.menu | +| unsafe-jquery-plugin.js:106:5:106:16 | options.menu | +| unsafe-jquery-plugin.js:107:5:107:11 | options | +| unsafe-jquery-plugin.js:107:5:107:18 | options.target | +| unsafe-jquery-plugin.js:107:5:107:18 | options.target | +| unsafe-jquery-plugin.js:114:38:114:44 | options | +| unsafe-jquery-plugin.js:114:38:114:44 | options | +| unsafe-jquery-plugin.js:115:3:115:58 | options | +| unsafe-jquery-plugin.js:115:13:115:58 | $.exten ... ptions) | +| unsafe-jquery-plugin.js:115:22:115:23 | {} | +| unsafe-jquery-plugin.js:115:51:115:57 | options | +| unsafe-jquery-plugin.js:116:5:116:11 | options | +| unsafe-jquery-plugin.js:116:5:116:16 | options.menu | +| unsafe-jquery-plugin.js:116:5:116:16 | options.menu | +| unsafe-jquery-plugin.js:117:5:117:11 | options | +| unsafe-jquery-plugin.js:117:5:117:18 | options.target | +| unsafe-jquery-plugin.js:117:5:117:18 | options.target | +| unsafe-jquery-plugin.js:121:40:121:46 | options | +| unsafe-jquery-plugin.js:121:40:121:46 | options | +| unsafe-jquery-plugin.js:122:5:122:11 | options | +| unsafe-jquery-plugin.js:122:5:122:18 | options.target | +| unsafe-jquery-plugin.js:122:5:122:18 | options.target | +| unsafe-jquery-plugin.js:126:33:126:39 | options | +| unsafe-jquery-plugin.js:126:33:126:39 | options | +| unsafe-jquery-plugin.js:127:6:127:12 | options | +| unsafe-jquery-plugin.js:127:6:127:19 | options.target | +| unsafe-jquery-plugin.js:127:6:127:19 | options.target | +| unsafe-jquery-plugin.js:131:34:131:40 | options | +| unsafe-jquery-plugin.js:131:34:131:40 | options | +| unsafe-jquery-plugin.js:132:5:132:11 | options | +| unsafe-jquery-plugin.js:132:5:132:18 | options.target | +| unsafe-jquery-plugin.js:132:5:132:18 | options.target | +| unsafe-jquery-plugin.js:135:36:135:42 | options | +| unsafe-jquery-plugin.js:135:36:135:42 | options | +| unsafe-jquery-plugin.js:136:5:136:11 | options | +| unsafe-jquery-plugin.js:136:5:136:20 | options.viewport | +| unsafe-jquery-plugin.js:136:5:136:29 | options ... elector | +| unsafe-jquery-plugin.js:136:5:136:29 | options ... elector | +| unsafe-jquery-plugin.js:153:38:153:44 | options | +| unsafe-jquery-plugin.js:153:38:153:44 | options | +| unsafe-jquery-plugin.js:154:7:154:29 | target | +| unsafe-jquery-plugin.js:154:16:154:22 | options | +| unsafe-jquery-plugin.js:154:16:154:29 | options.target | +| unsafe-jquery-plugin.js:155:33:155:38 | target | +| unsafe-jquery-plugin.js:155:33:155:38 | target | +| unsafe-jquery-plugin.js:156:41:156:47 | options | +| unsafe-jquery-plugin.js:156:41:156:54 | options.target | +| unsafe-jquery-plugin.js:156:41:156:54 | options.target | +| unsafe-jquery-plugin.js:157:44:157:50 | options | +| unsafe-jquery-plugin.js:157:44:157:57 | options.target | +| unsafe-jquery-plugin.js:157:44:157:59 | options.target.a | +| unsafe-jquery-plugin.js:157:44:157:59 | options.target.a | +| unsafe-jquery-plugin.js:160:38:160:44 | options | +| unsafe-jquery-plugin.js:160:38:160:44 | options | +| unsafe-jquery-plugin.js:165:7:165:29 | target | +| unsafe-jquery-plugin.js:165:16:165:22 | options | +| unsafe-jquery-plugin.js:165:16:165:29 | options.target | +| unsafe-jquery-plugin.js:170:6:170:11 | target | +| unsafe-jquery-plugin.js:170:6:170:11 | target | +| unsafe-jquery-plugin.js:178:27:178:33 | options | +| unsafe-jquery-plugin.js:178:27:178:33 | options | +| unsafe-jquery-plugin.js:179:5:179:11 | options | +| unsafe-jquery-plugin.js:179:5:179:18 | options.target | +| unsafe-jquery-plugin.js:179:5:179:18 | options.target | +edges +| unsafe-jquery-plugin.js:2:38:2:44 | options | unsafe-jquery-plugin.js:3:5:3:11 | options | +| unsafe-jquery-plugin.js:2:38:2:44 | options | unsafe-jquery-plugin.js:3:5:3:11 | options | +| unsafe-jquery-plugin.js:2:38:2:44 | options | unsafe-jquery-plugin.js:3:5:3:11 | options | +| unsafe-jquery-plugin.js:2:38:2:44 | options | unsafe-jquery-plugin.js:3:5:3:11 | options | +| unsafe-jquery-plugin.js:2:38:2:44 | options | unsafe-jquery-plugin.js:5:5:5:11 | options | +| unsafe-jquery-plugin.js:2:38:2:44 | options | unsafe-jquery-plugin.js:5:5:5:11 | options | +| unsafe-jquery-plugin.js:2:38:2:44 | options | unsafe-jquery-plugin.js:11:16:11:22 | options | +| unsafe-jquery-plugin.js:2:38:2:44 | options | unsafe-jquery-plugin.js:11:16:11:22 | options | +| unsafe-jquery-plugin.js:5:5:5:11 | options | unsafe-jquery-plugin.js:5:5:5:18 | options.target | +| unsafe-jquery-plugin.js:5:5:5:11 | options | unsafe-jquery-plugin.js:5:5:5:18 | options.target | +| unsafe-jquery-plugin.js:11:7:11:29 | target | unsafe-jquery-plugin.js:22:6:22:11 | target | +| unsafe-jquery-plugin.js:11:7:11:29 | target | unsafe-jquery-plugin.js:22:6:22:11 | target | +| unsafe-jquery-plugin.js:11:7:11:29 | target | unsafe-jquery-plugin.js:30:6:30:11 | target | +| unsafe-jquery-plugin.js:11:7:11:29 | target | unsafe-jquery-plugin.js:30:6:30:11 | target | +| unsafe-jquery-plugin.js:11:7:11:29 | target | unsafe-jquery-plugin.js:36:6:36:11 | target | +| unsafe-jquery-plugin.js:11:7:11:29 | target | unsafe-jquery-plugin.js:36:6:36:11 | target | +| unsafe-jquery-plugin.js:11:7:11:29 | target | unsafe-jquery-plugin.js:40:6:40:11 | target | +| unsafe-jquery-plugin.js:11:7:11:29 | target | unsafe-jquery-plugin.js:40:6:40:11 | target | +| unsafe-jquery-plugin.js:11:7:11:29 | target | unsafe-jquery-plugin.js:48:6:48:11 | target | +| unsafe-jquery-plugin.js:11:7:11:29 | target | unsafe-jquery-plugin.js:48:6:48:11 | target | +| unsafe-jquery-plugin.js:11:7:11:29 | target | unsafe-jquery-plugin.js:52:6:52:11 | target | +| unsafe-jquery-plugin.js:11:7:11:29 | target | unsafe-jquery-plugin.js:52:6:52:11 | target | +| unsafe-jquery-plugin.js:11:7:11:29 | target | unsafe-jquery-plugin.js:60:6:60:11 | target | +| unsafe-jquery-plugin.js:11:7:11:29 | target | unsafe-jquery-plugin.js:60:6:60:11 | target | +| unsafe-jquery-plugin.js:11:16:11:22 | options | unsafe-jquery-plugin.js:11:16:11:29 | options.target | +| unsafe-jquery-plugin.js:11:16:11:29 | options.target | unsafe-jquery-plugin.js:11:7:11:29 | target | +| unsafe-jquery-plugin.js:71:38:71:44 | options | unsafe-jquery-plugin.js:72:5:72:11 | options | +| unsafe-jquery-plugin.js:71:38:71:44 | options | unsafe-jquery-plugin.js:72:5:72:11 | options | +| unsafe-jquery-plugin.js:72:5:72:11 | options | unsafe-jquery-plugin.js:72:5:72:15 | options.foo | +| unsafe-jquery-plugin.js:72:5:72:15 | options.foo | unsafe-jquery-plugin.js:72:5:72:19 | options.foo.bar | +| unsafe-jquery-plugin.js:72:5:72:19 | options.foo.bar | unsafe-jquery-plugin.js:72:5:72:23 | options.foo.bar.baz | +| unsafe-jquery-plugin.js:72:5:72:19 | options.foo.bar | unsafe-jquery-plugin.js:72:5:72:23 | options.foo.bar.baz | +| unsafe-jquery-plugin.js:76:38:76:44 | options | unsafe-jquery-plugin.js:77:17:77:23 | options | +| unsafe-jquery-plugin.js:76:38:76:44 | options | unsafe-jquery-plugin.js:77:17:77:23 | options | +| unsafe-jquery-plugin.js:77:17:77:23 | options | unsafe-jquery-plugin.js:77:17:77:27 | options.foo | +| unsafe-jquery-plugin.js:77:17:77:27 | options.foo | unsafe-jquery-plugin.js:77:17:77:31 | options.foo.bar | +| unsafe-jquery-plugin.js:77:17:77:31 | options.foo.bar | unsafe-jquery-plugin.js:77:17:77:35 | options.foo.bar.baz | +| unsafe-jquery-plugin.js:77:17:77:31 | options.foo.bar | unsafe-jquery-plugin.js:77:17:77:35 | options.foo.bar.baz | +| unsafe-jquery-plugin.js:84:38:84:44 | options | unsafe-jquery-plugin.js:92:5:92:11 | options | +| unsafe-jquery-plugin.js:84:38:84:44 | options | unsafe-jquery-plugin.js:92:5:92:11 | options | +| unsafe-jquery-plugin.js:85:14:85:14 | o | unsafe-jquery-plugin.js:86:26:86:26 | o | +| unsafe-jquery-plugin.js:86:13:86:27 | $.extend({}, o) | unsafe-jquery-plugin.js:87:12:87:17 | this.o | +| unsafe-jquery-plugin.js:86:22:86:23 | {} | unsafe-jquery-plugin.js:86:13:86:27 | $.extend({}, o) | +| unsafe-jquery-plugin.js:86:26:86:26 | o | unsafe-jquery-plugin.js:86:13:86:27 | $.extend({}, o) | +| unsafe-jquery-plugin.js:86:26:86:26 | o | unsafe-jquery-plugin.js:86:22:86:23 | {} | +| unsafe-jquery-plugin.js:87:8:87:24 | t | unsafe-jquery-plugin.js:90:6:90:6 | t | +| unsafe-jquery-plugin.js:87:8:87:24 | t | unsafe-jquery-plugin.js:90:6:90:6 | t | +| unsafe-jquery-plugin.js:87:12:87:17 | this.o | unsafe-jquery-plugin.js:87:12:87:24 | this.o.target | +| unsafe-jquery-plugin.js:87:12:87:24 | this.o.target | unsafe-jquery-plugin.js:87:8:87:24 | t | +| unsafe-jquery-plugin.js:92:5:92:11 | options | unsafe-jquery-plugin.js:85:14:85:14 | o | +| unsafe-jquery-plugin.js:101:38:101:44 | options | unsafe-jquery-plugin.js:105:6:105:12 | options | +| unsafe-jquery-plugin.js:101:38:101:44 | options | unsafe-jquery-plugin.js:105:6:105:12 | options | +| unsafe-jquery-plugin.js:102:3:105:13 | options | unsafe-jquery-plugin.js:106:5:106:11 | options | +| unsafe-jquery-plugin.js:102:3:105:13 | options | unsafe-jquery-plugin.js:107:5:107:11 | options | +| unsafe-jquery-plugin.js:102:13:105:13 | $.exten ... ptions) | unsafe-jquery-plugin.js:102:3:105:13 | options | +| unsafe-jquery-plugin.js:102:22:105:3 | {\\n\\t\\t\\tme ... in'\\n\\t\\t} | unsafe-jquery-plugin.js:102:13:105:13 | $.exten ... ptions) | +| unsafe-jquery-plugin.js:105:6:105:12 | options | unsafe-jquery-plugin.js:102:13:105:13 | $.exten ... ptions) | +| unsafe-jquery-plugin.js:105:6:105:12 | options | unsafe-jquery-plugin.js:102:22:105:3 | {\\n\\t\\t\\tme ... in'\\n\\t\\t} | +| unsafe-jquery-plugin.js:106:5:106:11 | options | unsafe-jquery-plugin.js:106:5:106:16 | options.menu | +| unsafe-jquery-plugin.js:106:5:106:11 | options | unsafe-jquery-plugin.js:106:5:106:16 | options.menu | +| unsafe-jquery-plugin.js:107:5:107:11 | options | unsafe-jquery-plugin.js:107:5:107:18 | options.target | +| unsafe-jquery-plugin.js:107:5:107:11 | options | unsafe-jquery-plugin.js:107:5:107:18 | options.target | +| unsafe-jquery-plugin.js:114:38:114:44 | options | unsafe-jquery-plugin.js:115:51:115:57 | options | +| unsafe-jquery-plugin.js:114:38:114:44 | options | unsafe-jquery-plugin.js:115:51:115:57 | options | +| unsafe-jquery-plugin.js:115:3:115:58 | options | unsafe-jquery-plugin.js:116:5:116:11 | options | +| unsafe-jquery-plugin.js:115:3:115:58 | options | unsafe-jquery-plugin.js:117:5:117:11 | options | +| unsafe-jquery-plugin.js:115:13:115:58 | $.exten ... ptions) | unsafe-jquery-plugin.js:115:3:115:58 | options | +| unsafe-jquery-plugin.js:115:22:115:23 | {} | unsafe-jquery-plugin.js:115:13:115:58 | $.exten ... ptions) | +| unsafe-jquery-plugin.js:115:51:115:57 | options | unsafe-jquery-plugin.js:115:13:115:58 | $.exten ... ptions) | +| unsafe-jquery-plugin.js:115:51:115:57 | options | unsafe-jquery-plugin.js:115:22:115:23 | {} | +| unsafe-jquery-plugin.js:116:5:116:11 | options | unsafe-jquery-plugin.js:116:5:116:16 | options.menu | +| unsafe-jquery-plugin.js:116:5:116:11 | options | unsafe-jquery-plugin.js:116:5:116:16 | options.menu | +| unsafe-jquery-plugin.js:117:5:117:11 | options | unsafe-jquery-plugin.js:117:5:117:18 | options.target | +| unsafe-jquery-plugin.js:117:5:117:11 | options | unsafe-jquery-plugin.js:117:5:117:18 | options.target | +| unsafe-jquery-plugin.js:121:40:121:46 | options | unsafe-jquery-plugin.js:122:5:122:11 | options | +| unsafe-jquery-plugin.js:121:40:121:46 | options | unsafe-jquery-plugin.js:122:5:122:11 | options | +| unsafe-jquery-plugin.js:122:5:122:11 | options | unsafe-jquery-plugin.js:122:5:122:18 | options.target | +| unsafe-jquery-plugin.js:122:5:122:11 | options | unsafe-jquery-plugin.js:122:5:122:18 | options.target | +| unsafe-jquery-plugin.js:126:33:126:39 | options | unsafe-jquery-plugin.js:127:6:127:12 | options | +| unsafe-jquery-plugin.js:126:33:126:39 | options | unsafe-jquery-plugin.js:127:6:127:12 | options | +| unsafe-jquery-plugin.js:127:6:127:12 | options | unsafe-jquery-plugin.js:127:6:127:19 | options.target | +| unsafe-jquery-plugin.js:127:6:127:12 | options | unsafe-jquery-plugin.js:127:6:127:19 | options.target | +| unsafe-jquery-plugin.js:131:34:131:40 | options | unsafe-jquery-plugin.js:132:5:132:11 | options | +| unsafe-jquery-plugin.js:131:34:131:40 | options | unsafe-jquery-plugin.js:132:5:132:11 | options | +| unsafe-jquery-plugin.js:132:5:132:11 | options | unsafe-jquery-plugin.js:132:5:132:18 | options.target | +| unsafe-jquery-plugin.js:132:5:132:11 | options | unsafe-jquery-plugin.js:132:5:132:18 | options.target | +| unsafe-jquery-plugin.js:135:36:135:42 | options | unsafe-jquery-plugin.js:136:5:136:11 | options | +| unsafe-jquery-plugin.js:135:36:135:42 | options | unsafe-jquery-plugin.js:136:5:136:11 | options | +| unsafe-jquery-plugin.js:136:5:136:11 | options | unsafe-jquery-plugin.js:136:5:136:20 | options.viewport | +| unsafe-jquery-plugin.js:136:5:136:20 | options.viewport | unsafe-jquery-plugin.js:136:5:136:29 | options ... elector | +| unsafe-jquery-plugin.js:136:5:136:20 | options.viewport | unsafe-jquery-plugin.js:136:5:136:29 | options ... elector | +| unsafe-jquery-plugin.js:153:38:153:44 | options | unsafe-jquery-plugin.js:154:16:154:22 | options | +| unsafe-jquery-plugin.js:153:38:153:44 | options | unsafe-jquery-plugin.js:154:16:154:22 | options | +| unsafe-jquery-plugin.js:153:38:153:44 | options | unsafe-jquery-plugin.js:156:41:156:47 | options | +| unsafe-jquery-plugin.js:153:38:153:44 | options | unsafe-jquery-plugin.js:156:41:156:47 | options | +| unsafe-jquery-plugin.js:153:38:153:44 | options | unsafe-jquery-plugin.js:157:44:157:50 | options | +| unsafe-jquery-plugin.js:153:38:153:44 | options | unsafe-jquery-plugin.js:157:44:157:50 | options | +| unsafe-jquery-plugin.js:154:7:154:29 | target | unsafe-jquery-plugin.js:155:33:155:38 | target | +| unsafe-jquery-plugin.js:154:7:154:29 | target | unsafe-jquery-plugin.js:155:33:155:38 | target | +| unsafe-jquery-plugin.js:154:16:154:22 | options | unsafe-jquery-plugin.js:154:16:154:29 | options.target | +| unsafe-jquery-plugin.js:154:16:154:29 | options.target | unsafe-jquery-plugin.js:154:7:154:29 | target | +| unsafe-jquery-plugin.js:156:41:156:47 | options | unsafe-jquery-plugin.js:156:41:156:54 | options.target | +| unsafe-jquery-plugin.js:156:41:156:47 | options | unsafe-jquery-plugin.js:156:41:156:54 | options.target | +| unsafe-jquery-plugin.js:157:44:157:50 | options | unsafe-jquery-plugin.js:157:44:157:57 | options.target | +| unsafe-jquery-plugin.js:157:44:157:57 | options.target | unsafe-jquery-plugin.js:157:44:157:59 | options.target.a | +| unsafe-jquery-plugin.js:157:44:157:57 | options.target | unsafe-jquery-plugin.js:157:44:157:59 | options.target.a | +| unsafe-jquery-plugin.js:160:38:160:44 | options | unsafe-jquery-plugin.js:165:16:165:22 | options | +| unsafe-jquery-plugin.js:160:38:160:44 | options | unsafe-jquery-plugin.js:165:16:165:22 | options | +| unsafe-jquery-plugin.js:165:7:165:29 | target | unsafe-jquery-plugin.js:170:6:170:11 | target | +| unsafe-jquery-plugin.js:165:7:165:29 | target | unsafe-jquery-plugin.js:170:6:170:11 | target | +| unsafe-jquery-plugin.js:165:16:165:22 | options | unsafe-jquery-plugin.js:165:16:165:29 | options.target | +| unsafe-jquery-plugin.js:165:16:165:29 | options.target | unsafe-jquery-plugin.js:165:7:165:29 | target | +| unsafe-jquery-plugin.js:178:27:178:33 | options | unsafe-jquery-plugin.js:179:5:179:11 | options | +| unsafe-jquery-plugin.js:178:27:178:33 | options | unsafe-jquery-plugin.js:179:5:179:11 | options | +| unsafe-jquery-plugin.js:179:5:179:11 | options | unsafe-jquery-plugin.js:179:5:179:18 | options.target | +| unsafe-jquery-plugin.js:179:5:179:11 | options | unsafe-jquery-plugin.js:179:5:179:18 | options.target | +#select +| unsafe-jquery-plugin.js:3:5:3:11 | options | unsafe-jquery-plugin.js:2:38:2:44 | options | unsafe-jquery-plugin.js:3:5:3:11 | options | Potential XSS vulnerability in the $@. | unsafe-jquery-plugin.js:2:19:63:2 | functio ... \\t\\t}\\n\\n\\t} | '$.fn.my_plugin' plugin | +| unsafe-jquery-plugin.js:5:5:5:18 | options.target | unsafe-jquery-plugin.js:2:38:2:44 | options | unsafe-jquery-plugin.js:5:5:5:18 | options.target | Potential XSS vulnerability in the $@. | unsafe-jquery-plugin.js:2:19:63:2 | functio ... \\t\\t}\\n\\n\\t} | '$.fn.my_plugin' plugin | +| unsafe-jquery-plugin.js:22:6:22:11 | target | unsafe-jquery-plugin.js:2:38:2:44 | options | unsafe-jquery-plugin.js:22:6:22:11 | target | Potential XSS vulnerability in the $@. | unsafe-jquery-plugin.js:2:19:63:2 | functio ... \\t\\t}\\n\\n\\t} | '$.fn.my_plugin' plugin | +| unsafe-jquery-plugin.js:30:6:30:11 | target | unsafe-jquery-plugin.js:2:38:2:44 | options | unsafe-jquery-plugin.js:30:6:30:11 | target | Potential XSS vulnerability in the $@. | unsafe-jquery-plugin.js:2:19:63:2 | functio ... \\t\\t}\\n\\n\\t} | '$.fn.my_plugin' plugin | +| unsafe-jquery-plugin.js:36:6:36:11 | target | unsafe-jquery-plugin.js:2:38:2:44 | options | unsafe-jquery-plugin.js:36:6:36:11 | target | Potential XSS vulnerability in the $@. | unsafe-jquery-plugin.js:2:19:63:2 | functio ... \\t\\t}\\n\\n\\t} | '$.fn.my_plugin' plugin | +| unsafe-jquery-plugin.js:40:6:40:11 | target | unsafe-jquery-plugin.js:2:38:2:44 | options | unsafe-jquery-plugin.js:40:6:40:11 | target | Potential XSS vulnerability in the $@. | unsafe-jquery-plugin.js:2:19:63:2 | functio ... \\t\\t}\\n\\n\\t} | '$.fn.my_plugin' plugin | +| unsafe-jquery-plugin.js:48:6:48:11 | target | unsafe-jquery-plugin.js:2:38:2:44 | options | unsafe-jquery-plugin.js:48:6:48:11 | target | Potential XSS vulnerability in the $@. | unsafe-jquery-plugin.js:2:19:63:2 | functio ... \\t\\t}\\n\\n\\t} | '$.fn.my_plugin' plugin | +| unsafe-jquery-plugin.js:52:6:52:11 | target | unsafe-jquery-plugin.js:2:38:2:44 | options | unsafe-jquery-plugin.js:52:6:52:11 | target | Potential XSS vulnerability in the $@. | unsafe-jquery-plugin.js:2:19:63:2 | functio ... \\t\\t}\\n\\n\\t} | '$.fn.my_plugin' plugin | +| unsafe-jquery-plugin.js:60:6:60:11 | target | unsafe-jquery-plugin.js:2:38:2:44 | options | unsafe-jquery-plugin.js:60:6:60:11 | target | Potential XSS vulnerability in the $@. | unsafe-jquery-plugin.js:2:19:63:2 | functio ... \\t\\t}\\n\\n\\t} | '$.fn.my_plugin' plugin | +| unsafe-jquery-plugin.js:72:5:72:23 | options.foo.bar.baz | unsafe-jquery-plugin.js:71:38:71:44 | options | unsafe-jquery-plugin.js:72:5:72:23 | options.foo.bar.baz | Potential XSS vulnerability in the $@. | unsafe-jquery-plugin.js:71:19:74:2 | functio ... / OK\\n\\t} | '$.fn.my_plugin' plugin | +| unsafe-jquery-plugin.js:77:17:77:35 | options.foo.bar.baz | unsafe-jquery-plugin.js:76:38:76:44 | options | unsafe-jquery-plugin.js:77:17:77:35 | options.foo.bar.baz | Potential XSS vulnerability in the $@. | unsafe-jquery-plugin.js:76:19:78:2 | functio ... T OK\\n\\t} | '$.fn.my_plugin' plugin | +| unsafe-jquery-plugin.js:90:6:90:6 | t | unsafe-jquery-plugin.js:84:38:84:44 | options | unsafe-jquery-plugin.js:90:6:90:6 | t | Potential XSS vulnerability in the $@. | unsafe-jquery-plugin.js:84:19:93:2 | functio ... ns);\\n\\t} | '$.fn.my_plugin' plugin | +| unsafe-jquery-plugin.js:107:5:107:18 | options.target | unsafe-jquery-plugin.js:101:38:101:44 | options | unsafe-jquery-plugin.js:107:5:107:18 | options.target | Potential XSS vulnerability in the $@. | unsafe-jquery-plugin.js:101:19:108:2 | functio ... T OK\\n\\t} | '$.fn.my_plugin' plugin | +| unsafe-jquery-plugin.js:117:5:117:18 | options.target | unsafe-jquery-plugin.js:114:38:114:44 | options | unsafe-jquery-plugin.js:117:5:117:18 | options.target | Potential XSS vulnerability in the $@. | unsafe-jquery-plugin.js:114:19:118:2 | functio ... T OK\\n\\t} | '$.fn.my_plugin' plugin | +| unsafe-jquery-plugin.js:122:5:122:18 | options.target | unsafe-jquery-plugin.js:121:40:121:46 | options | unsafe-jquery-plugin.js:122:5:122:18 | options.target | Potential XSS vulnerability in the $@. | unsafe-jquery-plugin.js:121:21:123:2 | functio ... T OK\\n\\t} | '$.fn.my_plugin' plugin | +| unsafe-jquery-plugin.js:127:6:127:19 | options.target | unsafe-jquery-plugin.js:126:33:126:39 | options | unsafe-jquery-plugin.js:127:6:127:19 | options.target | Potential XSS vulnerability in the $@. | unsafe-jquery-plugin.js:126:14:128:3 | functio ... OK\\n\\t\\t} | '$.fn.my_plugin' plugin | +| unsafe-jquery-plugin.js:132:5:132:18 | options.target | unsafe-jquery-plugin.js:131:34:131:40 | options | unsafe-jquery-plugin.js:132:5:132:18 | options.target | Potential XSS vulnerability in the $@. | unsafe-jquery-plugin.js:131:15:133:2 | functio ... T OK\\n\\t} | '$.fn.affix' plugin | +| unsafe-jquery-plugin.js:136:5:136:29 | options ... elector | unsafe-jquery-plugin.js:135:36:135:42 | options | unsafe-jquery-plugin.js:136:5:136:29 | options ... elector | Potential XSS vulnerability in the $@. | unsafe-jquery-plugin.js:135:17:137:2 | functio ... T OK\\n\\t} | '$.fn.tooltip' plugin | +| unsafe-jquery-plugin.js:155:33:155:38 | target | unsafe-jquery-plugin.js:153:38:153:44 | options | unsafe-jquery-plugin.js:155:33:155:38 | target | Potential XSS vulnerability in the $@. | unsafe-jquery-plugin.js:153:19:158:2 | functio ... gged\\n\\t} | '$.fn.my_plugin' plugin | +| unsafe-jquery-plugin.js:156:41:156:54 | options.target | unsafe-jquery-plugin.js:153:38:153:44 | options | unsafe-jquery-plugin.js:156:41:156:54 | options.target | Potential XSS vulnerability in the $@. | unsafe-jquery-plugin.js:153:19:158:2 | functio ... gged\\n\\t} | '$.fn.my_plugin' plugin | +| unsafe-jquery-plugin.js:157:44:157:59 | options.target.a | unsafe-jquery-plugin.js:153:38:153:44 | options | unsafe-jquery-plugin.js:157:44:157:59 | options.target.a | Potential XSS vulnerability in the $@. | unsafe-jquery-plugin.js:153:19:158:2 | functio ... gged\\n\\t} | '$.fn.my_plugin' plugin | +| unsafe-jquery-plugin.js:170:6:170:11 | target | unsafe-jquery-plugin.js:160:38:160:44 | options | unsafe-jquery-plugin.js:170:6:170:11 | target | Potential XSS vulnerability in the $@. | unsafe-jquery-plugin.js:160:19:173:2 | functio ... \\t\\t}\\n\\n\\t} | '$.fn.my_plugin' plugin | +| unsafe-jquery-plugin.js:179:5:179:18 | options.target | unsafe-jquery-plugin.js:178:27:178:33 | options | unsafe-jquery-plugin.js:179:5:179:18 | options.target | Potential XSS vulnerability in the $@. | unsafe-jquery-plugin.js:178:18:180:2 | functio ... T OK\\n\\t} | '$.fn.my_plugin' plugin | diff --git a/javascript/ql/test/query-tests/Security/CWE-079/UnsafeJQueryPlugin.qlref b/javascript/ql/test/query-tests/Security/CWE-079/UnsafeJQueryPlugin.qlref new file mode 100644 index 00000000000..66c19069e07 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-079/UnsafeJQueryPlugin.qlref @@ -0,0 +1 @@ +Security/CWE-079/UnsafeJQueryPlugin.ql \ No newline at end of file diff --git a/javascript/ql/test/query-tests/Security/CWE-079/unsafe-jquery-plugin.js b/javascript/ql/test/query-tests/Security/CWE-079/unsafe-jquery-plugin.js new file mode 100644 index 00000000000..a6eae277811 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-079/unsafe-jquery-plugin.js @@ -0,0 +1,185 @@ +(function(){ + $.fn.my_plugin = function my_plugin(options) { + $(options); // NOT OK (or is it?) + + $(options.target); // NOT OK + + if (isElement(options.target)) { + $(options.target); // OK + } + + var target = options.target; + + if (isElement(target)) { + $(target); // OK + } + + if (typeof target != "string") { + $(target); // OK + } + + if (target.jquery === undefined) { + $(target); // NOT OK + } else { + $(target); // OK + } + + if (target.jquery !== undefined) { + $(target); // OK + } else { + $(target); // NOT OK + } + + if (typeof target.jquery !== "undefined") { + $(target); // OK + } else { + $(target); // NOT OK + } + + if (typeof target.jquery === "undefined") { + $(target); // NOT OK + } else { + $(target); // OK + } + + if (target.jquery) { + $(target); // OK + } else { + $(target); // NOT OK + } + + if (!target.jquery) { + $(target); // NOT OK + } else { + $(target); // OK + } + + if (!!target.jquery) { + $(target); // OK + } else { + $(target); // NOT OK + } + + }; + + $.fn.my_plugin = function my_plugin(element, options) { + this.$element = $(element); + this.options = $.extend({}, options); + if (this.options.parent) this.$parent = $(this.options.parent) // NOT OK + }; + + $.fn.my_plugin = function my_plugin(options) { + $(options.foo.bar.baz); // NOT OK + $(options.html); // OK + }; + + $.fn.my_plugin = function my_plugin(options) { + $(x).appendTo(options.foo.bar.baz); // NOT OK + }; + + $.fn.my_plugin = function my_plugin(options) { + $("#" + options.target); // OK + }; + + $.fn.my_plugin = function my_plugin(options) { + function f(o) { + this.o = $.extend({}, o); + var t = this.o.target; + + console.log(t); + $(t); // NOT OK + } + f(options); + }; + + $.fn.my_plugin = function my_plugin(options) { + var target = options.target; + if (safe.has(target)) + $(target); // OK + }; + + $.fn.my_plugin = function my_plugin(options) { + options = $.extend({ + menu: '
    ', + target: '.my_plugin' + }, options); + $(options.menu); // OK + $(options.target); // NOT OK + }; + + $.fn.my_plugin.defaults = { + menu: '
    ', + target: '.my_plugin' + }; + $.fn.my_plugin = function my_plugin(options) { + options = $.extend({}, $.fn.my_plugin.defaults, options); + $(options.menu); // OK + $(options.target); // NOT OK + }; + + var pluginName = "my_plugin"; + $.fn[pluginName] = function my_plugin(options) { + $(options.target); // NOT OK + }; + + $.extend($.fn, { + my_plugin: function my_plugin(options) { + $(options.target); // NOT OK + } + }); + + $.fn.affix = function my_plugin(options) { + $(options.target); // NOT OK + }; + + $.fn.tooltip = function my_plugin(options) { + $(options.viewport.selector); // NOT OK + }; + + $.fn.my_plugin = function my_plugin(options) { + let intentional1 = options.target || `
    hello
    `; + $(intentional1); // OK + + let intentional2 = `
    ${options.target}
    `; + $(intentional2); // OK + + let intentional3 = `
    ` + options.target `
    `; + $(intentional3); // OK + + let unintentional = `
    `; + $(unintentional); // OK - but should be flagged by another query + } + + $.fn.my_plugin = function my_plugin(options) { + let target = options.target; + target === DEFAULTS.target? $(target): $(document).find(target); // OK - but still flagged + options.target === DEFAULTS.target? $(options.target): $(document).find(options.target); // OK - but still flagged + options.targets.a === DEFAULTS.target? $(options.target.a): $(document).find(options.target.a); // OK - but still flagged + } + + $.fn.my_plugin = function my_plugin(options) { + $(anyPrefix + options.target); // OK (unlikely to be a html/css prefix confusion) + + $(something.replace("%PLACEHOLDER%", options.target)); // OK (unlikely to be a html/css prefix confusion); + + let target = options.target; + if (target.foo) { + $(target); // OK (unlikely to be a string) + } + if (target.length) { + $(target); // NOT OK (can still be a string) + } + + } + + function setupPlugin(o) { + $.fn.my_plugin = o.f + } + setupPlugin({f: function(options) { + $(options.target); // NOT OK + }}); + setupPlugin({f:function(options) { + $(document).find(options.target); // OK + }}); + +});