|
4 | 4 | */
|
5 | 5 |
|
6 | 6 | import javascript
|
7 |
| -private import semmle.javascript.dataflow.internal.DataFlowPrivate as DataFlowPrivate |
8 | 7 |
|
9 | 8 | /**
|
10 | 9 | * Provides a flow label for reasoning about URLs with a tainted query and fragment part,
|
11 | 10 | * which we collectively refer to as the "suffix" of the URL.
|
12 | 11 | */
|
13 | 12 | module TaintedUrlSuffix {
|
14 |
| - private import DataFlow |
| 13 | + import TaintedUrlSuffixCustomizations::TaintedUrlSuffix |
15 | 14 |
|
16 |
| - /** |
17 |
| - * The flow label representing a URL with a tainted query and fragment part. |
18 |
| - * |
19 |
| - * Can also be accessed using `TaintedUrlSuffix::label()`. |
20 |
| - */ |
21 |
| - class TaintedUrlSuffixLabel extends FlowLabel { |
22 |
| - TaintedUrlSuffixLabel() { this = "tainted-url-suffix" } |
23 |
| - } |
24 |
| - |
25 |
| - /** |
26 |
| - * Gets the flow label representing a URL with a tainted query and fragment part. |
27 |
| - */ |
28 |
| - FlowLabel label() { result instanceof TaintedUrlSuffixLabel } |
29 |
| - |
30 |
| - /** Gets a remote flow source that is a tainted URL query or fragment part from `window.location`. */ |
31 |
| - ClientSideRemoteFlowSource source() { |
32 |
| - result = DOM::locationRef().getAPropertyRead(["search", "hash"]) |
33 |
| - or |
34 |
| - result = DOM::locationSource() |
35 |
| - or |
36 |
| - result.getKind().isUrl() |
37 |
| - } |
38 |
| - |
39 |
| - /** |
40 |
| - * Holds if `node` should be a barrier for the given `label`. |
41 |
| - * |
42 |
| - * This should be used in the `isBarrier` predicate of a configuration that uses the tainted-url-suffix |
43 |
| - * label. |
44 |
| - */ |
45 |
| - predicate isBarrier(Node node, FlowLabel label) { |
46 |
| - label = label() and |
47 |
| - DataFlowPrivate::optionalBarrier(node, "split-url-suffix") |
48 |
| - } |
49 |
| - |
50 |
| - /** |
51 |
| - * Holds if there is a flow step `src -> dst` involving the URL suffix taint label. |
52 |
| - * |
53 |
| - * This handles steps through string operations, promises, URL parsers, and URL accessors. |
54 |
| - */ |
55 |
| - predicate step(Node src, Node dst, FlowLabel srclbl, FlowLabel dstlbl) { |
56 |
| - // Transition from tainted-url-suffix to general taint when entering the second array element |
57 |
| - // of a split('#') or split('?') array. |
58 |
| - // |
59 |
| - // x [tainted-url-suffix] --> x.split('#') [array element 1] [taint] |
60 |
| - // |
61 |
| - // Technically we should also preverse tainted-url-suffix when entering the first array element of such |
62 |
| - // a split, but this mostly leads to FPs since we currently don't track if the taint has been through URI-decoding. |
63 |
| - // (The query/fragment parts are often URI-decoded in practice, but not the other URL parts are not) |
64 |
| - srclbl = label() and |
65 |
| - dstlbl.isTaint() and |
66 |
| - DataFlowPrivate::optionalStep(src, "split-url-suffix-post", dst) |
67 |
| - or |
68 |
| - // Transition from URL suffix to full taint when extracting the query/fragment part. |
69 |
| - srclbl = label() and |
70 |
| - dstlbl.isTaint() and |
71 |
| - ( |
72 |
| - exists(MethodCallNode call, string name | |
73 |
| - src = call.getReceiver() and |
74 |
| - dst = call and |
75 |
| - name = call.getMethodName() |
76 |
| - | |
77 |
| - // Substring that is not a prefix |
78 |
| - name = StringOps::substringMethodName() and |
79 |
| - not call.getArgument(0).getIntValue() = 0 |
80 |
| - or |
81 |
| - // Replace '#' and '?' with nothing |
82 |
| - name = "replace" and |
83 |
| - call.getArgument(0).getStringValue() = ["#", "?"] and |
84 |
| - call.getArgument(1).getStringValue() = "" |
85 |
| - or |
86 |
| - // The `get` call in `url.searchParams.get(x)` and `url.hashParams.get(x)` |
87 |
| - // The step should be safe since nothing else reachable by this flow label supports a method named 'get'. |
88 |
| - name = "get" |
89 |
| - or |
90 |
| - // Methods on URL objects from the Closure library |
91 |
| - name = "getDecodedQuery" |
92 |
| - or |
93 |
| - name = "getFragment" |
94 |
| - or |
95 |
| - name = "getParameterValue" |
96 |
| - or |
97 |
| - name = "getParameterValues" |
98 |
| - or |
99 |
| - name = "getQueryData" |
100 |
| - ) |
101 |
| - or |
102 |
| - exists(PropRead read | |
103 |
| - src = read.getBase() and |
104 |
| - dst = read and |
105 |
| - // Unlike the `search` property, the `query` property from `url.parse` does not include the `?`. |
106 |
| - read.getPropertyName() = "query" |
107 |
| - ) |
108 |
| - or |
109 |
| - // Assume calls to regexp.exec always extract query/fragment parameters. |
110 |
| - exists(MethodCallNode call | |
111 |
| - call = any(DataFlow::RegExpCreationNode re).getAMethodCall("exec") and |
112 |
| - src = call.getArgument(0) and |
113 |
| - dst = call |
114 |
| - ) |
115 |
| - ) |
| 15 | + private class ConcreteTaintedUrlSuffixLabel extends TaintedUrlSuffixLabel { |
| 16 | + ConcreteTaintedUrlSuffixLabel() { this = this } |
116 | 17 | }
|
117 | 18 | }
|
0 commit comments