mirror of
https://github.com/github/codeql.git
synced 2026-04-26 09:15:12 +02:00
Added query for detecting XSS that happens through an exception
This commit is contained in:
54
javascript/ql/src/Security/CWE-079/ExceptionXss.qhelp
Normal file
54
javascript/ql/src/Security/CWE-079/ExceptionXss.qhelp
Normal 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>
|
||||
26
javascript/ql/src/Security/CWE-079/ExceptionXss.ql
Normal file
26
javascript/ql/src/Security/CWE-079/ExceptionXss.ql
Normal 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"
|
||||
10
javascript/ql/src/Security/CWE-079/examples/ExceptionXss.js
Normal file
10
javascript/ql/src/Security/CWE-079/examples/ExceptionXss.js
Normal 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 + ".");
|
||||
}
|
||||
}
|
||||
@@ -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() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user