Skip to content

Commit f958916

Browse files
authored
Merge pull request #2330 from erik-krogh/exceptionXss
JS: Added query for detecting XSS that happens through an exception
2 parents 73e08eb + 9351cd4 commit f958916

File tree

19 files changed

+598
-57
lines changed

19 files changed

+598
-57
lines changed

change-notes/1.24/analysis-javascript.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111

1212
## New queries
1313

14-
| **Query** | **Tags** | **Purpose** |
15-
|---------------------------------------------------------------------------|-------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
16-
14+
| **Query** | **Tags** | **Purpose** |
15+
|---------------------------------------------------------------------------------|-------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
16+
| Cross-site scripting through exception (`js/xss-through-exception`) | security, external/cwe/cwe-079, external/cwe/cwe-116 | Highlights potential XSS vulnerabilities where an exception is written to the DOM. Results are not shown on LGTM by default. |
1717

1818
## Changes to existing queries
1919

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
6+
<overview>
7+
<p>
8+
Directly writing exceptions to a webpage without sanitization allows for a cross-site scripting
9+
vulnerability if the value of the exception can be influenced by a user.
10+
</p>
11+
</overview>
12+
13+
<recommendation>
14+
<p>
15+
To guard against cross-site scripting, consider using contextual output encoding/escaping before
16+
writing user input to the page, or one of the other solutions that are mentioned in the
17+
references.
18+
</p>
19+
</recommendation>
20+
21+
<example>
22+
<p>
23+
The following example shows an exception being written directly to the document,
24+
and this exception can potentially be influenced by the page URL,
25+
leaving the website vulnerable to cross-site scripting.
26+
</p>
27+
<sample src="examples/ExceptionXss.js" />
28+
</example>
29+
30+
<references>
31+
<li>
32+
OWASP:
33+
<a href="https://cheatsheetseries.owasp.org/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.html">DOM based
34+
XSS Prevention Cheat Sheet</a>.
35+
</li>
36+
<li>
37+
OWASP:
38+
<a href="https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html">XSS
39+
(Cross Site Scripting) Prevention Cheat Sheet</a>.
40+
</li>
41+
<li>
42+
OWASP
43+
<a href="https://www.owasp.org/index.php/DOM_Based_XSS">DOM Based XSS</a>.
44+
</li>
45+
<li>
46+
OWASP
47+
<a href="https://www.owasp.org/index.php/Types_of_Cross-Site_Scripting">Types of Cross-Site
48+
Scripting</a>.
49+
</li>
50+
<li>
51+
Wikipedia: <a href="http://en.wikipedia.org/wiki/Cross-site_scripting">Cross-site scripting</a>.
52+
</li>
53+
</references>
54+
</qhelp>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* @name Cross-site scripting through exception
3+
* @description Inserting data from an exception containing user
4+
* input into the DOM may enable cross-site scripting.
5+
* @kind path-problem
6+
* @problem.severity error
7+
* @precision medium
8+
* @id js/xss-through-exception
9+
* @tags security
10+
* external/cwe/cwe-079
11+
* external/cwe/cwe-116
12+
*/
13+
14+
import javascript
15+
import semmle.javascript.security.dataflow.ExceptionXss::ExceptionXss
16+
import DataFlow::PathGraph
17+
18+
from
19+
Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
20+
where
21+
cfg.hasFlowPath(source, sink)
22+
select sink.getNode(), source, sink,
23+
sink.getNode().(Xss::Shared::Sink).getVulnerabilityKind() + " vulnerability due to $@.", source.getNode(),
24+
"user-provided value"
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
function setLanguageOptions() {
2+
var href = document.location.href,
3+
deflt = href.substring(href.indexOf("default=")+8);
4+
5+
try {
6+
var parsed = unknownParseFunction(deflt);
7+
} catch(e) {
8+
document.write("Had an error: " + e + ".");
9+
}
10+
}

javascript/ql/src/semmle/javascript/Expr.qll

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,21 @@ class Expr extends @expr, ExprOrStmt, ExprOrType, AST::ValueNode {
245245
ctx.(ConditionalExpr).inNullSensitiveContext()
246246
)
247247
}
248+
249+
/**
250+
* Gets the data-flow node where exceptions thrown by this expression will
251+
* propagate if this expression causes an exception to be thrown.
252+
*/
253+
DataFlow::Node getExceptionTarget() {
254+
if exists(this.getEnclosingStmt().getEnclosingTryCatchStmt())
255+
then
256+
result = DataFlow::parameterNode(this
257+
.getEnclosingStmt()
258+
.getEnclosingTryCatchStmt()
259+
.getACatchClause()
260+
.getAParameter())
261+
else result = any(DataFlow::FunctionNode f | f.getFunction() = this.getContainer()).getExceptionalReturn()
262+
}
248263
}
249264

250265
/**

javascript/ql/src/semmle/javascript/Stmt.qll

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,18 @@ class Stmt extends @stmt, ExprOrStmt, Documentable {
5555
}
5656

5757
override predicate isAmbient() { hasDeclareKeyword(this) or getParent().isAmbient() }
58+
59+
/**
60+
* Gets the `try` statement with a catch block containing this statement without
61+
* crossing function boundaries or other `try ` statements with catch blocks.
62+
*/
63+
TryStmt getEnclosingTryCatchStmt() {
64+
getParentStmt+() = result.getBody() and
65+
exists(result.getACatchClause()) and
66+
not exists(TryStmt mid | exists(mid.getACatchClause()) |
67+
getParentStmt+() = mid.getBody() and mid.getParentStmt+() = result.getBody()
68+
)
69+
}
5870
}
5971

6072
/**

javascript/ql/src/semmle/javascript/dataflow/Configuration.qll

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,6 +1125,9 @@ class MidPathNode extends PathNode, MkMidNode {
11251125
// Skip to the top of big left-leaning string concatenation trees.
11261126
nd = any(AddExpr add).flow() and
11271127
nd = any(AddExpr add).getAnOperand().flow()
1128+
or
1129+
// Skip the exceptional return on functions, as this highlights the entire function.
1130+
nd = any(DataFlow::FunctionNode f).getExceptionalReturn()
11281131
}
11291132
}
11301133

javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,23 @@ module TaintTracking {
558558
succ = this
559559
}
560560
}
561+
562+
563+
/**
564+
* A taint propagating data flow edge arising from calling `String.prototype.match()`.
565+
*/
566+
private class StringMatchTaintStep extends AdditionalTaintStep, DataFlow::MethodCallNode {
567+
StringMatchTaintStep() {
568+
this.getMethodName() = "match" and
569+
this.getNumArgument() = 1 and
570+
this.getArgument(0) .analyze().getAType() = TTRegExp()
571+
}
572+
573+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
574+
pred = this.getReceiver() and
575+
succ = this
576+
}
577+
}
561578

562579
/**
563580
* A taint propagating data flow edge arising from JSON unparsing.

javascript/ql/src/semmle/javascript/dataflow/internal/FlowSteps.qll

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -66,19 +66,7 @@ predicate localExceptionStep(DataFlow::Node pred, DataFlow::Node succ) {
6666
or
6767
DataFlow::exceptionalInvocationReturnNode(pred, expr)
6868
|
69-
// Propagate out of enclosing function.
70-
not exists(getEnclosingTryStmt(expr.getEnclosingStmt())) and
71-
exists(Function f |
72-
f = expr.getEnclosingFunction() and
73-
DataFlow::exceptionalFunctionReturnNode(succ, f)
74-
)
75-
or
76-
// Propagate to enclosing try/catch.
77-
// To avoid false flow, we only propagate to an unguarded catch clause.
78-
exists(TryStmt try |
79-
try = getEnclosingTryStmt(expr.getEnclosingStmt()) and
80-
DataFlow::parameterNode(succ, try.getCatchClause().getAParameter())
81-
)
69+
succ = expr.getExceptionTarget()
8270
)
8371
}
8472

@@ -156,19 +144,6 @@ private module CachedSteps {
156144
cached
157145
predicate callStep(DataFlow::Node pred, DataFlow::Node succ) { argumentPassing(_, pred, _, succ) }
158146

159-
/**
160-
* Gets the `try` statement containing `stmt` without crossing function boundaries
161-
* or other `try ` statements.
162-
*/
163-
cached
164-
TryStmt getEnclosingTryStmt(Stmt stmt) {
165-
result.getBody() = stmt
166-
or
167-
not stmt instanceof Function and
168-
not stmt = any(TryStmt try).getBody() and
169-
result = getEnclosingTryStmt(stmt.getParentStmt())
170-
}
171-
172147
/**
173148
* Holds if there is a flow step from `pred` to `succ` through:
174149
* - returning a value from a function call, or

javascript/ql/src/semmle/javascript/security/dataflow/DomBasedXss.qll

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import javascript
77

88
module DomBasedXss {
9-
import Xss::DomBasedXss
9+
import DomBasedXssCustomizations::DomBasedXss
1010

1111
/**
1212
* A taint-tracking configuration for reasoning about XSS.
@@ -33,16 +33,4 @@ module DomBasedXss {
3333
node instanceof Sanitizer
3434
}
3535
}
36-
37-
/** A source of remote user input, considered as a flow source for DOM-based XSS. */
38-
class RemoteFlowSourceAsSource extends Source {
39-
RemoteFlowSourceAsSource() { this instanceof RemoteFlowSource }
40-
}
41-
42-
/**
43-
* An access of the URL of this page, or of the referrer to this page.
44-
*/
45-
class LocationSource extends Source {
46-
LocationSource() { this = DOM::locationSource() }
47-
}
4836
}

0 commit comments

Comments
 (0)