Added query for detecting XSS that happens through an exception

This commit is contained in:
Erik Krogh Kristensen
2019-11-06 14:15:37 +01:00
parent 1159344972
commit 7137a64b7d
9 changed files with 1662 additions and 13 deletions

View File

@@ -0,0 +1,54 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Directly writing exceptions to a webpage with sanitization allows for a cross-site scripting
vulnerability if the value of the exception can be influenzed by a user.
</p>
</overview>
<recommendation>
<p>
To guard against cross-site scripting, consider using contextual output encoding/escaping before
writing user input to the page, or one of the other solutions that are mentioned in the
references.
</p>
</recommendation>
<example>
<p>
The following example shows an exception being written directly to the document,
and this exception can potentially be influenzed the page URL,
leaving the website vulnerable to cross-site scripting.
</p>
<sample src="examples/ExceptionXss.js" />
</example>
<references>
<li>
OWASP:
<a href="https://cheatsheetseries.owasp.org/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.html">DOM based
XSS Prevention Cheat Sheet</a>.
</li>
<li>
OWASP:
<a href="https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html">XSS
(Cross Site Scripting) Prevention Cheat Sheet</a>.
</li>
<li>
OWASP
<a href="https://www.owasp.org/index.php/DOM_Based_XSS">DOM Based XSS</a>.
</li>
<li>
OWASP
<a href="https://www.owasp.org/index.php/Types_of_Cross-Site_Scripting">Types of Cross-Site
Scripting</a>.
</li>
<li>
Wikipedia: <a href="http://en.wikipedia.org/wiki/Cross-site_scripting">Cross-site scripting</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,26 @@
/**
* @name Client-side cross-site scripting through exception
* @description User input being part of an exception allows for
* cross-site scripting if that exception ends as input
* to the DOM.
* @kind path-problem
* @problem.severity error
* @precision low
* @id js/xss-through-exception
* @tags security
* external/cwe/cwe-079
* external/cwe/cwe-116
*/
import javascript
import semmle.javascript.security.dataflow.ExceptionXss::ExceptionXss
import DataFlow::PathGraph
from
Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where
cfg.hasFlowPath(source, sink) and
not any(ConfigurationNoException c).hasFlow(source.getNode(), sink.getNode())
select sink.getNode(), source, sink,
sink.getNode().(Sink).getVulnerabilityKind() + " vulnerability due to $@.", source.getNode(),
"user-provided value"

View File

@@ -0,0 +1,10 @@
function setLanguageOptions() {
var href = document.location.href,
deflt = href.substring(href.indexOf("default=")+8);
try {
var parsed = unknownParseFunction(deflt);
} catch(e) {
document.write("Had an error: " + e + ".");
}
}

View File

@@ -6,7 +6,7 @@
import javascript
module DomBasedXss {
import Xss::DomBasedXss
import DomBasedXssCustomizations::DomBasedXss
/**
* A taint-tracking configuration for reasoning about XSS.
@@ -33,16 +33,4 @@ module DomBasedXss {
node instanceof Sanitizer
}
}
/** A source of remote user input, considered as a flow source for DOM-based XSS. */
class RemoteFlowSourceAsSource extends Source {
RemoteFlowSourceAsSource() { this instanceof RemoteFlowSource }
}
/**
* An access of the URL of this page, or of the referrer to this page.
*/
class LocationSource extends Source {
LocationSource() { this = DOM::locationSource() }
}
}

View File

@@ -0,0 +1,23 @@
/**
* Provides default sources for reasoning about DOM-based
* cross-site scripting vulnerabilities.
*/
import javascript
module DomBasedXss {
import Xss::DomBasedXss
/** A source of remote user input, considered as a flow source for DOM-based XSS. */
class RemoteFlowSourceAsSource extends Source {
RemoteFlowSourceAsSource() { this instanceof RemoteFlowSource }
}
/**
* An access of the URL of this page, or of the referrer to this page.
*/
class LocationSource extends Source {
LocationSource() { this = DOM::locationSource() }
}
}

View File

@@ -0,0 +1,112 @@
/**
* Provides a taint-tracking configuration for TODO:
*/
import javascript
module ExceptionXss {
import Xss::DomBasedXss // imports sinks
import DomBasedXssCustomizations::DomBasedXss // imports sources
DataFlow::Node getExceptionalSuccssor(DataFlow::Node pred) {
exists(DataFlow::FunctionNode func |
pred.getContainer() = func.getFunction() and
if exists(getEnclosingTryStmt(pred.asExpr().getEnclosingStmt()))
then
result.(DataFlow::ParameterNode).getParameter() = getEnclosingTryStmt(pred
.asExpr()
.getEnclosingStmt()).getACatchClause().getAParameter()
else result = getExceptionalSuccssor(func.getExceptionalReturn())
or
pred = func.getExceptionalReturn() and
exists(DataFlow::InvokeNode call |
not call.isImprecise() and
func.getFunction() = call.(DataFlow::InvokeNode).getACallee() and
result = getExceptionalSuccssor(call)
)
)
}
predicate canThrowSensitiveInformation(DataFlow::Node node) {
// i have to do this "dual" check because there are two data-flow nodes associated with reflective calls.
node = any(DataFlow::InvokeNode call).getAnArgument() and
not node = any(DataFlow::InvokeNode call | exists(call.getACallee())).getAnArgument()
or
node.asExpr().getEnclosingStmt() instanceof ThrowStmt
or
// TODO: Do some flow label for deeply tainted objects?
exists(DataFlow::ObjectLiteralNode obj |
obj.asExpr().(ObjectExpr).getAProperty().getInit() = node.asExpr() and
canThrowSensitiveInformation(obj)
)
or
exists(DataFlow::ArrayCreationNode arr |
arr.getAnElement() = node and
canThrowSensitiveInformation(arr)
)
}
TryStmt getEnclosingTryStmt(Stmt stmt) {
stmt.getParentStmt+() = result.getBody() and
not exists(TryStmt mid |
stmt.getParentStmt+() = mid.getBody() and mid.getParentStmt+() = result.getBody()
)
}
/**
* A taint-tracking configuration for reasoning about XSS with possible exceptional flow.
*/
abstract class BaseConfiguration extends TaintTracking::Configuration {
BaseConfiguration() { this = "ExceptionXss" or this = "ExceptionXssNoException" }
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 Sanitizer
}
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
succ = getExceptionalSuccssor(pred) and
(
canThrowSensitiveInformation(pred)
or
pred = any(DataFlow::FunctionNode func).getExceptionalReturn()
)
or
// String.prototype.match()
exists(DataFlow::MethodCallNode call |
call = succ and
pred = call.getReceiver() and
call.getMethodName() = "match" and
call.getNumArgument() = 1 and
// TODO: Better way of detecting regExp / String.prototype.match() calls?
(
call.getArgument(0).getALocalSource().asExpr() instanceof RegExpLiteral or
call.getArgument(0).getALocalSource().(DataFlow::NewNode).getCalleeName() = "RegExp"
)
)
}
}
/**
* Instantiation of BaseConfiguration.
*/
class Configuration extends BaseConfiguration {
Configuration() { this = "ExceptionXss" }
}
/**
* Same as configuration, except that all additional exceptional flows has been removed.
*/
class ConfigurationNoException extends BaseConfiguration {
ConfigurationNoException() { this = "ExceptionXssNoException" }
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
super.isAdditionalTaintStep(pred, succ) and
not succ = getExceptionalSuccssor(pred)
}
}
}