diff --git a/javascript/change-notes/2021-04-27-anser.md b/javascript/change-notes/2021-04-27-anser.md new file mode 100644 index 000000000000..f3808066f48f --- /dev/null +++ b/javascript/change-notes/2021-04-27-anser.md @@ -0,0 +1,4 @@ +lgtm,codescanning +* The security queries now track taint through the anser library. + Affected packages are + [anser](https://www.npmjs.com/package/anser) \ No newline at end of file diff --git a/javascript/ql/src/javascript.qll b/javascript/ql/src/javascript.qll index b565a7cd21f9..4660d7eaf93f 100644 --- a/javascript/ql/src/javascript.qll +++ b/javascript/ql/src/javascript.qll @@ -68,6 +68,7 @@ import semmle.javascript.dataflow.TaintTracking import semmle.javascript.dataflow.TypeInference import semmle.javascript.frameworks.Angular2 import semmle.javascript.frameworks.AngularJS +import semmle.javascript.frameworks.Anser import semmle.javascript.frameworks.AsyncPackage import semmle.javascript.frameworks.AWS import semmle.javascript.frameworks.Azure diff --git a/javascript/ql/src/semmle/javascript/frameworks/Anser.qll b/javascript/ql/src/semmle/javascript/frameworks/Anser.qll new file mode 100644 index 000000000000..77525689fa44 --- /dev/null +++ b/javascript/ql/src/semmle/javascript/frameworks/Anser.qll @@ -0,0 +1,31 @@ +/** + * Provides classes for working with applications using [anser](https://www.npmjs.com/package/anser). + */ + +import javascript + +/** + * A taint step for the [anser](https://www.npmjs.com/package/anser) library. + */ +private class AnserTaintStep extends TaintTracking::SharedTaintStep { + override predicate step(DataFlow::Node pred, DataFlow::Node succ) { + exists(API::CallNode call | + call = + API::moduleImport("anser") + .getMember(["linkify", "ansiToHtml", "ansiToText", "ansiToJson"]) + .getACall() + or + call = + API::moduleImport("anser") + .getInstance() + .getMember([ + "linkify", "ansiToHtml", "ansiToText", "ansiToJson", "process", "processChunkJson", + "processChunk" + ]) + .getACall() + | + succ = call and + pred = call.getArgument(0) + ) + } +} diff --git a/javascript/ql/test/query-tests/Security/CWE-079/XssThroughDom/XssThroughDom.expected b/javascript/ql/test/query-tests/Security/CWE-079/XssThroughDom/XssThroughDom.expected index b88c3eae8ef3..b726a851f09e 100644 --- a/javascript/ql/test/query-tests/Security/CWE-079/XssThroughDom/XssThroughDom.expected +++ b/javascript/ql/test/query-tests/Security/CWE-079/XssThroughDom/XssThroughDom.expected @@ -103,6 +103,15 @@ nodes | xss-through-dom.js:81:17:81:43 | $('#foo ... rText') | | xss-through-dom.js:81:17:81:43 | $('#foo ... rText') | | xss-through-dom.js:81:17:81:43 | $('#foo ... rText') | +| xss-through-dom.js:84:8:84:30 | text | +| xss-through-dom.js:84:15:84:30 | $("text").text() | +| xss-through-dom.js:84:15:84:30 | $("text").text() | +| xss-through-dom.js:86:16:86:37 | anser.a ... l(text) | +| xss-through-dom.js:86:16:86:37 | anser.a ... l(text) | +| xss-through-dom.js:86:33:86:36 | text | +| xss-through-dom.js:87:16:87:40 | new ans ... s(text) | +| xss-through-dom.js:87:16:87:40 | new ans ... s(text) | +| xss-through-dom.js:87:36:87:39 | text | edges | forms.js:8:23:8:28 | values | forms.js:9:31:9:36 | values | | forms.js:8:23:8:28 | values | forms.js:9:31:9:36 | values | @@ -161,6 +170,14 @@ edges | xss-through-dom.js:73:20:73:41 | $("inpu ... 0).name | xss-through-dom.js:73:9:73:41 | selector | | xss-through-dom.js:79:4:79:34 | documen ... t.value | xss-through-dom.js:79:4:79:34 | documen ... t.value | | xss-through-dom.js:81:17:81:43 | $('#foo ... rText') | xss-through-dom.js:81:17:81:43 | $('#foo ... rText') | +| xss-through-dom.js:84:8:84:30 | text | xss-through-dom.js:86:33:86:36 | text | +| xss-through-dom.js:84:8:84:30 | text | xss-through-dom.js:87:36:87:39 | text | +| xss-through-dom.js:84:15:84:30 | $("text").text() | xss-through-dom.js:84:8:84:30 | text | +| xss-through-dom.js:84:15:84:30 | $("text").text() | xss-through-dom.js:84:8:84:30 | text | +| xss-through-dom.js:86:33:86:36 | text | xss-through-dom.js:86:16:86:37 | anser.a ... l(text) | +| xss-through-dom.js:86:33:86:36 | text | xss-through-dom.js:86:16:86:37 | anser.a ... l(text) | +| xss-through-dom.js:87:36:87:39 | text | xss-through-dom.js:87:16:87:40 | new ans ... s(text) | +| xss-through-dom.js:87:36:87:39 | text | xss-through-dom.js:87:16:87:40 | new ans ... s(text) | #select | forms.js:9:31:9:40 | values.foo | forms.js:8:23:8:28 | values | forms.js:9:31:9:40 | values.foo | $@ is reinterpreted as HTML without escaping meta-characters. | forms.js:8:23:8:28 | values | DOM text | | forms.js:12:31:12:40 | values.bar | forms.js:11:24:11:29 | values | forms.js:12:31:12:40 | values.bar | $@ is reinterpreted as HTML without escaping meta-characters. | forms.js:11:24:11:29 | values | DOM text | @@ -190,3 +207,5 @@ edges | xss-through-dom.js:77:4:77:11 | selector | xss-through-dom.js:73:20:73:41 | $("inpu ... 0).name | xss-through-dom.js:77:4:77:11 | selector | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:73:20:73:41 | $("inpu ... 0).name | DOM text | | xss-through-dom.js:79:4:79:34 | documen ... t.value | xss-through-dom.js:79:4:79:34 | documen ... t.value | xss-through-dom.js:79:4:79:34 | documen ... t.value | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:79:4:79:34 | documen ... t.value | DOM text | | xss-through-dom.js:81:17:81:43 | $('#foo ... rText') | xss-through-dom.js:81:17:81:43 | $('#foo ... rText') | xss-through-dom.js:81:17:81:43 | $('#foo ... rText') | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:81:17:81:43 | $('#foo ... rText') | DOM text | +| xss-through-dom.js:86:16:86:37 | anser.a ... l(text) | xss-through-dom.js:84:15:84:30 | $("text").text() | xss-through-dom.js:86:16:86:37 | anser.a ... l(text) | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:84:15:84:30 | $("text").text() | DOM text | +| xss-through-dom.js:87:16:87:40 | new ans ... s(text) | xss-through-dom.js:84:15:84:30 | $("text").text() | xss-through-dom.js:87:16:87:40 | new ans ... s(text) | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:84:15:84:30 | $("text").text() | DOM text | diff --git a/javascript/ql/test/query-tests/Security/CWE-079/XssThroughDom/xss-through-dom.js b/javascript/ql/test/query-tests/Security/CWE-079/XssThroughDom/xss-through-dom.js index 9f85627ee7c7..0e6555fe5685 100644 --- a/javascript/ql/test/query-tests/Security/CWE-079/XssThroughDom/xss-through-dom.js +++ b/javascript/ql/test/query-tests/Security/CWE-079/XssThroughDom/xss-through-dom.js @@ -79,4 +79,10 @@ $(document.my_form.my_input.value); // NOT OK $("#id").html( $('#foo').prop('innerText') ); // NOT OK + + const anser = require("anser"); + const text = $("text").text(); + + $("#id").html(anser.ansiToHtml(text)); // NOT OK + $("#id").html(new anser().process(text)); // NOT OK })(); \ No newline at end of file