Skip to content

Commit ef833de

Browse files
committed
JS: Replace DocumentUrl with TaintedUrlSuffix
1 parent e2b2d1c commit ef833de

File tree

7 files changed

+44
-32
lines changed

7 files changed

+44
-32
lines changed

javascript/ql/lib/semmle/javascript/security/dataflow/ClientSideUrlRedirectCustomizations.qll

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
import javascript
8+
private import semmle.javascript.security.TaintedUrlSuffix
89

910
module ClientSideUrlRedirect {
1011
/**
@@ -31,12 +32,12 @@ module ClientSideUrlRedirect {
3132
abstract class Sanitizer extends DataFlow::Node { }
3233

3334
/**
35+
* DEPRECATED. Replaced by functionality from the `TaintedUrlSuffix` library.
36+
*
3437
* A flow label for values that represent the URL of the current document, and
3538
* hence are only partially user-controlled.
3639
*/
37-
abstract class DocumentUrl extends DataFlow::FlowLabel {
38-
DocumentUrl() { this = "document.url" } // TODO: replace with TaintedUrlSuffix
39-
}
40+
deprecated class DocumentUrl = TaintedUrlSuffix::TaintedUrlSuffixLabel;
4041

4142
/**
4243
* DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead!
@@ -50,8 +51,8 @@ module ClientSideUrlRedirect {
5051
ActiveThreatModelSourceAsSource() { not this.(ClientSideRemoteFlowSource).getKind().isPath() }
5152

5253
override DataFlow::FlowLabel getAFlowLabel() {
53-
if this.(ClientSideRemoteFlowSource).getKind().isUrl()
54-
then result instanceof DocumentUrl
54+
if this = TaintedUrlSuffix::source()
55+
then result = TaintedUrlSuffix::label()
5556
else result.isTaint()
5657
}
5758
}
@@ -60,7 +61,7 @@ module ClientSideUrlRedirect {
6061
* Holds if `node` extracts a part of a URL that does not contain the suffix.
6162
*/
6263
pragma[inline]
63-
predicate isPrefixExtraction(DataFlow::MethodCallNode node) {
64+
deprecated predicate isPrefixExtraction(DataFlow::MethodCallNode node) {
6465
// Block flow through prefix-extraction `substring(0, ...)` and `split("#")[0]`
6566
node.getMethodName() = [StringOps::substringMethodName(), "split"] and
6667
not untrustedUrlSubstring(_, node)
@@ -70,7 +71,7 @@ module ClientSideUrlRedirect {
7071
* Holds if `substring` refers to a substring of `base` which is considered untrusted
7172
* when `base` is the current URL.
7273
*/
73-
predicate untrustedUrlSubstring(DataFlow::Node base, DataFlow::Node substring) {
74+
deprecated predicate untrustedUrlSubstring(DataFlow::Node base, DataFlow::Node substring) {
7475
exists(DataFlow::MethodCallNode mcn, string methodName |
7576
mcn = substring and mcn.calls(base, methodName)
7677
|

javascript/ql/lib/semmle/javascript/security/dataflow/ClientSideUrlRedirectQuery.qll

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@
1010
import javascript
1111
import UrlConcatenation
1212
import ClientSideUrlRedirectCustomizations::ClientSideUrlRedirect
13+
import semmle.javascript.security.TaintedUrlSuffix
1314

1415
// Materialize flow labels
15-
private class ConcreteDocumentUrl extends DocumentUrl {
16+
deprecated private class ConcreteDocumentUrl extends DocumentUrl {
1617
ConcreteDocumentUrl() { this = this }
1718
}
1819

@@ -35,8 +36,7 @@ module ClientSideUrlRedirectConfig implements DataFlow::StateConfigSig {
3536
}
3637

3738
predicate isBarrier(DataFlow::Node node, DataFlow::FlowLabel state) {
38-
isPrefixExtraction(node) and
39-
state instanceof DocumentUrl
39+
TaintedUrlSuffix::isBarrier(node, state)
4040
}
4141

4242
predicate isBarrierOut(DataFlow::Node node) { hostnameSanitizingPrefixEdge(node, _) }
@@ -47,9 +47,7 @@ module ClientSideUrlRedirectConfig implements DataFlow::StateConfigSig {
4747
DataFlow::Node node1, DataFlow::FlowLabel state1, DataFlow::Node node2,
4848
DataFlow::FlowLabel state2
4949
) {
50-
untrustedUrlSubstring(node1, node2) and
51-
state1 instanceof DocumentUrl and
52-
state2.isTaint()
50+
TaintedUrlSuffix::step(node1, node2, state1, state2)
5351
or
5452
exists(HtmlSanitizerCall call |
5553
node1 = call.getInput() and
Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import React from "react";
22
import {Helmet} from "react-helmet";
3-
3+
44
class Application extends React.Component {
55
render () {
66
return (
77
<div className="application">
88
<Helmet>
99
<title>My unsafe app</title>
10-
<script type="application/javascript" src={document.location.hash}/>
10+
<script type="application/javascript" src={document.location.hash.substr(1)}/> {/* NOT OK */}
1111
</Helmet>
1212
</div>
1313
);
@@ -18,28 +18,31 @@ export default Application
1818

1919
import Link from 'next/link'
2020
export function NextLink() {
21-
return <Link href={document.location.hash}><a>this page!</a></Link>;
21+
return <>
22+
<Link href={document.location.hash}><a>safe</a></Link> {/* OK */}
23+
<Link href={document.location.hash.substr(1)}><a>unsafe</a></Link> {/* NOT OK */}
24+
</>;
2225
}
2326

2427
import { useRouter } from 'next/router'
2528

2629
export function nextRouter() {
2730
const router = useRouter();
28-
return <span onClick={() => router.push(document.location.hash.substr(1))}>Click to XSS 1</span>
31+
return <span onClick={() => router.push(document.location.hash.substr(1))}>Click to XSS 1</span> // NOT OK
2932
}
3033

3134
import { withRouter } from 'next/router'
3235

3336
function Page({ router }) {
34-
return <span onClick={() => router.push(document.location.hash.substr(1))}>Click to XSS 2</span>
37+
return <span onClick={() => router.push(document.location.hash.substr(1))}>Click to XSS 2</span> // NOT OK
3538
}
3639

3740
export const pageWithRouter = withRouter(Page);
3841

3942
export function plainLink() {
40-
return <a href={document.location.hash.substr(1)}>my plain link!</a>;
43+
return <a href={document.location.hash.substr(1)}>my plain link!</a>; // NOT OK
4144
}
4245

4346
export function someUnknown() {
44-
return <FOO data={document.location.hash.substr(1)}>is safe.</FOO>;
45-
}
47+
return <FOO data={document.location.hash.substr(1)}>is safe.</FOO>; // OK
48+
}
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
// OK - cannot affect hostname
2-
location.href = '/foo' + document.location.search;
2+
location.href = '/foo' + document.location.search.substring(1);
33

44
// NOT OK
5-
location.href = '/' + document.location.search;
5+
location.href = '/' + document.location.search.substring(1);
66

77
// NOT OK
8-
location.href = '//' + document.location.search;
8+
location.href = '//' + document.location.search.substring(1);
99

1010
// NOT OK
11-
location.href = '//foo' + document.location.search;
11+
location.href = '//foo' + document.location.search.substring(1);
1212

1313
// NOT OK
14-
location.href = 'https://foo' + document.location.search;
14+
location.href = 'https://foo' + document.location.search.substring(1);
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
function foo() {
22
var urlParts = window.location.hash.split('?');
33
var loc = urlParts[0] + "?" + boxes.value;
4-
window.location = loc; // OK [INCONSISTENCY] - always starts with '#'
4+
window.location = loc; // OK - always starts with '#'
55
}

javascript/ql/test/query-tests/Security/CWE-601/ClientSideUrlRedirect/tst15.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
11
function foo() {
22
var url = document.location.toString();
3-
window.location = url.substring(0).substring(1); // OK
4-
window.location = url.substring(0, 10).substring(1); // OK
5-
window.location = url.substring(0, url.indexOf('/', 10)).substring(1); // OK
3+
window.location = url.substring(0).substring(1); // OK [INCONSISTENCY] - but not important
4+
window.location = url.substring(0, 10).substring(1); // OK [INCONSISTENCY]
5+
window.location = url.substring(0, url.indexOf('/', 10)).substring(1); // OK [INCONSISTENCY]
6+
7+
var url2 = document.location.toString();
8+
window.location = url2.substring(0).substring(unknown()); // NOT OK
9+
window.location = url2.substring(0, 10).substring(unknown()); // NOT OK
10+
window.location = url2.substring(0, url2.indexOf('/', 10)).substring(unknown()); // NOT OK
11+
12+
var search = document.location.search.toString();
13+
window.location = search.substring(0).substring(1); // NOT OK
14+
window.location = search.substring(0, 10).substring(1); // NOT OK
15+
window.location = search.substring(0, search.indexOf('/', 10)).substring(1); // NOT OK
616
}
717

818
function bar() {
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// NOT OK
2-
new Worker(document.location.search);
2+
new Worker(document.location.search.substring(1));
33

44
// NOT OK
5-
$("<script>").attr("src", document.location.search);
5+
$("<script>").attr("src", document.location.search.substring(1));

0 commit comments

Comments
 (0)