Skip to content

Commit 4ce7ec1

Browse files
author
Esben Sparre Andreasen
committed
JS: add XSS vector for Vue's v-html
1 parent 4c5e48f commit 4ce7ec1

File tree

6 files changed

+58
-0
lines changed

6 files changed

+58
-0
lines changed

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,39 @@ module DomBasedXss {
198198
}
199199
}
200200

201+
/**
202+
* A Vue `v-html` attribute, viewed as an XSS sink.
203+
*/
204+
class VHtmlSink extends DomBasedXss::Sink {
205+
HTML::Attribute attr;
206+
VHtmlSink() { this = DataFlow::THtmlAttributeNode(attr) and attr.getName() = "v-html" }
207+
HTML::Attribute getAttr() {
208+
result = attr
209+
}
210+
}
211+
212+
/**
213+
* A taint propagating data flow edge through a string interpolation of a
214+
* Vue instance property to a `v-html` attribute.
215+
*/
216+
class VHtmlSourceWrite extends TaintTracking::AdditionalTaintStep {
217+
VHtmlSink attr;
218+
219+
VHtmlSourceWrite() {
220+
exists(Vue::Instance instance, string expr |
221+
attr.getAttr().getRoot() = instance.getTemplateElement().(Vue::Template::HtmlElement).getElement() and
222+
expr = attr.getAttr().getValue() and
223+
// only support for simple identifier expressions
224+
expr.regexpMatch("(?i)[a-z0-9_]+") and
225+
this = instance.getAPropertyValue(expr)
226+
)
227+
}
228+
229+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
230+
pred = this and succ = attr
231+
}
232+
}
233+
201234
/**
202235
* A regexp replacement involving an HTML meta-character, viewed as a sanitizer for
203236
* XSS vulnerabilities.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
| single-component-file-1.vue:6:40:6:41 | 42 | single-component-file-1.vue:6:40:6:41 | 42 | single-component-file-1.vue:2:8:2:21 | v-html=dataA |
2+
| single-file-component-3-script.js:4:37:4:38 | 42 | single-file-component-3-script.js:4:37:4:38 | 42 | single-file-component-3.vue:2:8:2:21 | v-html=dataA |
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import javascript
2+
import semmle.javascript.security.dataflow.DomBasedXss
3+
4+
from DomBasedXss::VHtmlSourceWrite w, DataFlow::Node pred, DataFlow::Node succ
5+
where w.step(pred, succ)
6+
select w, pred, succ
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
1+
| single-component-file-1.vue:2:8:2:21 | v-html=dataA |
2+
| single-file-component-2.vue:2:8:2:21 | v-html=dataA |
3+
| single-file-component-3.vue:2:8:2:21 | v-html=dataA |
14
| tst.js:5:13:5:13 | a |
25
| tst.js:38:12:38:17 | danger |

javascript/ql/test/query-tests/Security/CWE-079/Xss.expected

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ nodes
178178
| tst.js:282:9:282:29 | tainted |
179179
| tst.js:282:19:282:29 | window.name |
180180
| tst.js:285:59:285:65 | tainted |
181+
| v-html.vue:2:8:2:23 | v-html=tainted |
182+
| v-html.vue:6:42:6:58 | document.location |
181183
| winjs.js:2:7:2:53 | tainted |
182184
| winjs.js:2:17:2:33 | document.location |
183185
| winjs.js:2:17:2:40 | documen ... .search |
@@ -318,6 +320,7 @@ edges
318320
| tst.js:272:16:272:32 | document.location | tst.js:272:9:272:32 | loc3 |
319321
| tst.js:282:9:282:29 | tainted | tst.js:285:59:285:65 | tainted |
320322
| tst.js:282:19:282:29 | window.name | tst.js:282:9:282:29 | tainted |
323+
| v-html.vue:6:42:6:58 | document.location | v-html.vue:2:8:2:23 | v-html=tainted |
321324
| winjs.js:2:7:2:53 | tainted | winjs.js:3:43:3:49 | tainted |
322325
| winjs.js:2:7:2:53 | tainted | winjs.js:4:43:4:49 | tainted |
323326
| winjs.js:2:17:2:33 | document.location | winjs.js:2:17:2:40 | documen ... .search |
@@ -394,5 +397,6 @@ edges
394397
| tst.js:285:59:285:65 | tainted | tst.js:282:9:282:29 | tainted | tst.js:285:59:285:65 | tainted | Cross-site scripting vulnerability due to $@. | tst.js:282:9:282:29 | tainted | user-provided value |
395398
| tst.js:285:59:285:65 | tainted | tst.js:282:19:282:29 | window.name | tst.js:285:59:285:65 | tainted | Cross-site scripting vulnerability due to $@. | tst.js:282:19:282:29 | window.name | user-provided value |
396399
| tst.js:285:59:285:65 | tainted | tst.js:285:59:285:65 | tainted | tst.js:285:59:285:65 | tainted | Cross-site scripting vulnerability due to $@. | tst.js:285:59:285:65 | tainted | user-provided value |
400+
| v-html.vue:2:8:2:23 | v-html=tainted | v-html.vue:6:42:6:58 | document.location | v-html.vue:2:8:2:23 | v-html=tainted | Cross-site scripting vulnerability due to $@. | v-html.vue:6:42:6:58 | document.location | user-provided value |
397401
| winjs.js:3:43:3:49 | tainted | winjs.js:2:17:2:33 | document.location | winjs.js:3:43:3:49 | tainted | Cross-site scripting vulnerability due to $@. | winjs.js:2:17:2:33 | document.location | user-provided value |
398402
| winjs.js:4:43:4:49 | tainted | winjs.js:2:17:2:33 | document.location | winjs.js:4:43:4:49 | tainted | Cross-site scripting vulnerability due to $@. | winjs.js:2:17:2:33 | document.location | user-provided value |
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<template>
2+
<p v-html="tainted"/>
3+
</template>
4+
<script>
5+
export default {
6+
data: function() { return { tainted: document.location } }
7+
}
8+
</script>
9+
<style>
10+
</style>

0 commit comments

Comments
 (0)