JS: Use return value of trusted type policy callback as a sink

This commit is contained in:
Asger Feldthaus
2021-12-14 13:21:19 +01:00
parent 9ffa236c51
commit 7e947b2a65
5 changed files with 98 additions and 0 deletions

View File

@@ -126,6 +126,7 @@ import semmle.javascript.frameworks.SocketIO
import semmle.javascript.frameworks.StringFormatters
import semmle.javascript.frameworks.TorrentLibraries
import semmle.javascript.frameworks.Typeahead
import semmle.javascript.frameworks.TrustedTypes
import semmle.javascript.frameworks.UriLibraries
import semmle.javascript.frameworks.Vue
import semmle.javascript.frameworks.Vuex

View File

@@ -0,0 +1,68 @@
/** Module for working with uses of the Trusted Types API. */
private import javascript
private import semmle.javascript.security.dataflow.Xss
private import semmle.javascript.security.dataflow.ClientSideUrlRedirectCustomizations
private import semmle.javascript.security.dataflow.CodeInjectionCustomizations
/** Module for working with uses of the Trusted Types API. */
module TrustedTypes {
private class TrustedTypesEntry extends API::EntryPoint {
TrustedTypesEntry() { this = "TrustedTypesEntry" }
override DataFlow::SourceNode getAUse() { result = DataFlow::globalVarRef("trustedTypes") }
override DataFlow::Node getARhs() { none() }
}
private API::Node trustedTypesObj() { result = any(TrustedTypesEntry entry).getNode() }
/** A call to `trustedTypes.createPolicy`. */
class PolicyCreation extends API::CallNode {
PolicyCreation() { this = trustedTypesObj().getMember("createPolicy").getACall() }
DataFlow::FunctionNode getPolicyCallback(string method) {
// Require local callback to avoid potential call/return mismatch in the uses below
result = getOptionArgument(1, method).getALocalSource()
}
}
/**
* A data-flow step from the use of a policy to its callback.
*/
private class PolicyInputStep extends DataFlow::SharedFlowStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(PolicyCreation policy, string method |
pred = policy.getReturn().getMember(method).getParameter(0).getARhs() and
succ = policy.getPolicyCallback(method).getParameter(0)
)
}
}
/**
* The creation of a trusted HTML object, as an XSS sink.
*/
private class XssSink extends DomBasedXss::Sink {
XssSink() { this = any(PolicyCreation creation).getPolicyCallback("createHTML").getAReturn() }
}
/**
* The creation of a trusted script, as a code-injection sink.
*/
private class CodeInjectionSink extends CodeInjection::Sink {
CodeInjectionSink() {
this = any(PolicyCreation creation).getPolicyCallback("createScript").getAReturn()
}
}
/**
* The creation of a trusted script URL, as a URL redirection sink.
*
* This is currently handled by the client-side URL redirection query, as this checks for untrusted hostname in the URL.
*/
private class UrlSink extends ClientSideUrlRedirect::Sink {
UrlSink() {
this = any(PolicyCreation creation).getPolicyCallback("createScriptURL").getAReturn()
}
}
}

View File

@@ -427,6 +427,11 @@ nodes
| translate.js:7:42:7:60 | target.substring(1) |
| translate.js:9:27:9:50 | searchP ... 'term') |
| translate.js:9:27:9:50 | searchP ... 'term') |
| trusted-types.js:2:66:2:66 | x |
| trusted-types.js:2:71:2:71 | x |
| trusted-types.js:2:71:2:71 | x |
| trusted-types.js:3:24:3:34 | window.name |
| trusted-types.js:3:24:3:34 | window.name |
| tst3.js:2:12:2:75 | JSON.pa ... tr(1))) |
| tst3.js:2:23:2:74 | decodeU ... str(1)) |
| tst3.js:2:42:2:63 | window. ... .search |
@@ -1183,6 +1188,10 @@ edges
| translate.js:7:42:7:47 | target | translate.js:7:42:7:60 | target.substring(1) |
| translate.js:7:42:7:60 | target.substring(1) | translate.js:9:27:9:50 | searchP ... 'term') |
| translate.js:7:42:7:60 | target.substring(1) | translate.js:9:27:9:50 | searchP ... 'term') |
| trusted-types.js:2:66:2:66 | x | trusted-types.js:2:71:2:71 | x |
| trusted-types.js:2:66:2:66 | x | trusted-types.js:2:71:2:71 | x |
| trusted-types.js:3:24:3:34 | window.name | trusted-types.js:2:66:2:66 | x |
| trusted-types.js:3:24:3:34 | window.name | trusted-types.js:2:66:2:66 | x |
| tst3.js:2:12:2:75 | JSON.pa ... tr(1))) | tst3.js:4:25:4:28 | data |
| tst3.js:2:12:2:75 | JSON.pa ... tr(1))) | tst3.js:5:26:5:29 | data |
| tst3.js:2:12:2:75 | JSON.pa ... tr(1))) | tst3.js:7:32:7:35 | data |
@@ -1612,6 +1621,7 @@ edges
| tooltip.jsx:10:25:10:30 | source | tooltip.jsx:6:20:6:30 | window.name | tooltip.jsx:10:25:10:30 | source | Cross-site scripting vulnerability due to $@. | tooltip.jsx:6:20:6:30 | window.name | user-provided value |
| tooltip.jsx:11:25:11:30 | source | tooltip.jsx:6:20:6:30 | window.name | tooltip.jsx:11:25:11:30 | source | Cross-site scripting vulnerability due to $@. | tooltip.jsx:6:20:6:30 | window.name | user-provided value |
| translate.js:9:27:9:50 | searchP ... 'term') | translate.js:6:16:6:39 | documen ... .search | translate.js:9:27:9:50 | searchP ... 'term') | Cross-site scripting vulnerability due to $@. | translate.js:6:16:6:39 | documen ... .search | user-provided value |
| trusted-types.js:2:71:2:71 | x | trusted-types.js:3:24:3:34 | window.name | trusted-types.js:2:71:2:71 | x | Cross-site scripting vulnerability due to $@. | trusted-types.js:3:24:3:34 | window.name | user-provided value |
| tst3.js:4:25:4:32 | data.src | tst3.js:2:42:2:63 | window. ... .search | tst3.js:4:25:4:32 | data.src | Cross-site scripting vulnerability due to $@. | tst3.js:2:42:2:63 | window. ... .search | user-provided value |
| tst3.js:5:26:5:31 | data.p | tst3.js:2:42:2:63 | window. ... .search | tst3.js:5:26:5:31 | data.p | Cross-site scripting vulnerability due to $@. | tst3.js:2:42:2:63 | window. ... .search | user-provided value |
| tst3.js:7:32:7:37 | data.p | tst3.js:2:42:2:63 | window. ... .search | tst3.js:7:32:7:37 | data.p | Cross-site scripting vulnerability due to $@. | tst3.js:2:42:2:63 | window. ... .search | user-provided value |

View File

@@ -434,6 +434,11 @@ nodes
| translate.js:7:42:7:60 | target.substring(1) |
| translate.js:9:27:9:50 | searchP ... 'term') |
| translate.js:9:27:9:50 | searchP ... 'term') |
| trusted-types.js:2:66:2:66 | x |
| trusted-types.js:2:71:2:71 | x |
| trusted-types.js:2:71:2:71 | x |
| trusted-types.js:3:24:3:34 | window.name |
| trusted-types.js:3:24:3:34 | window.name |
| tst3.js:2:12:2:75 | JSON.pa ... tr(1))) |
| tst3.js:2:23:2:74 | decodeU ... str(1)) |
| tst3.js:2:42:2:63 | window. ... .search |
@@ -1218,6 +1223,10 @@ edges
| translate.js:7:42:7:47 | target | translate.js:7:42:7:60 | target.substring(1) |
| translate.js:7:42:7:60 | target.substring(1) | translate.js:9:27:9:50 | searchP ... 'term') |
| translate.js:7:42:7:60 | target.substring(1) | translate.js:9:27:9:50 | searchP ... 'term') |
| trusted-types.js:2:66:2:66 | x | trusted-types.js:2:71:2:71 | x |
| trusted-types.js:2:66:2:66 | x | trusted-types.js:2:71:2:71 | x |
| trusted-types.js:3:24:3:34 | window.name | trusted-types.js:2:66:2:66 | x |
| trusted-types.js:3:24:3:34 | window.name | trusted-types.js:2:66:2:66 | x |
| tst3.js:2:12:2:75 | JSON.pa ... tr(1))) | tst3.js:4:25:4:28 | data |
| tst3.js:2:12:2:75 | JSON.pa ... tr(1))) | tst3.js:5:26:5:29 | data |
| tst3.js:2:12:2:75 | JSON.pa ... tr(1))) | tst3.js:7:32:7:35 | data |

View File

@@ -0,0 +1,10 @@
(function() {
const policy1 = trustedTypes.createPolicy('x', { createHTML: x => x }); // NOT OK
policy1.createHTML(window.name);
const policy2 = trustedTypes.createPolicy('x', { createHTML: x => 'safe' }); // OK
policy2.createHTML(window.name);
const policy3 = trustedTypes.createPolicy('x', { createHTML: x => x }); // OK
policy3.createHTML('safe');
})();