blob: a2b91fdf893fec6c4cce8acb2724626f9e95fc33 [file] [edit]
<!doctype html>
<html>
<head>
<script src="../dist/purify.js"></script>
</head>
<body>
<!-- Our DIV to receive content -->
<div id="sanitized"></div>
<!-- Now let's sanitize that content -->
<script>
/* jshint globalstrict:true */
/* global DOMPurify */
'use strict';
window.onload = function(){
// Specify dirty HTML
let dirty = document.getElementById('payload').value;
// We can allow all (default elements) but SVG
const config = {
FORBID_TAGS: ['svg'] // SVG is not yet supported. Too messy.
};
// Specify CSS property whitelist
const allowed_properties = [
'color',
'background',
'border',
'padding',
'margin',
'font-family',
'content',
'padding'
];
// Specify if CSS functions are permitted
const allow_css_functions = true;
/**
* Take CSS property-value pairs and validate against white-list,
* then add the styles to an array of property-value pairs
*/
function validateStyles(output, styles) {
Object.keys(styles).forEach(function(index) {
if (styles.hasOwnProperty(index)) {
let normalizedKey = styles[index].replace(/([A-Z])/g, '-$1').toLowerCase();
if (allowed_properties.includes(normalizedKey)) {
let value = styles[normalizedKey];
output.push(`${normalizedKey}:${value};`);
}
}
});
}
/**
* Take CSS rules and analyze them, create string wrapper to
* apply them to the DOM later on. Note that only selector rules
* are supported right now
*/
function addCSSRules(output, cssRules) {
Array.from(cssRules).reverse().forEach(rule => {
// check for rules with selector
if (rule.type === 1 && rule.selectorText) {
output.push(`${rule.selectorText}{`);
if (rule.style) {
validateStyles(output, rule.style);
}
output.push('}');
}
});
}
// Add a hook to enforce CSS element sanitization
DOMPurify.addHook('uponSanitizeElement', function(node, data) {
if (data.tagName === 'style') {
let output = [];
addCSSRules(output, node.sheet.cssRules);
node.textContent = output.join("\n");
}
});
// Add a hook to enforce CSS attribute sanitization
DOMPurify.addHook('afterSanitizeAttributes', function(node) {
// Nasty hack to fix baseURI + CSS problems in Chrome
if (!node.ownerDocument.baseURI) {
let base = document.createElement('base');
base.href = document.baseURI;
node.ownerDocument.head.appendChild(base);
}
// Check all style attribute values and validate them
if (node.hasAttribute('style')) {
let output = [];
validateStyles(output, node.style);
// re-add styles in case any are left
if (output.length) {
node.setAttribute('style', output.join(""));
} else {
node.removeAttribute('style');
}
}
});
// Clean HTML string and write into our DIV
let clean = DOMPurify.sanitize(dirty, config);
document.getElementById('sanitized').innerHTML = clean;
}
</script>
<!-- Here we cage our payload in a TEXTAREA -->
<textarea id="payload">
<a href="#" style="border:1ps solid blue;color:red;background:url(/images/test.gif)">ABC</a>
<style>
big {
list-style: url(https://leaking.via/css-list-style);
list-style-image: url(https://leaking.via/css-list-style-image);
background: url(https://leaking.via/css-background);
background-image: url(https://leaking.via/css-background-image);
border-image: url(https://leaking.via/css-border-image);
border-image-source: url(https://leaking.via/css-border-image-source);
shape-outside: url(https://leaking.via/css-shape-outside);
cursor: url(https://leaking.via/css-cursor), auto;
color:red;
}
</style>
<big>DEF</big>
<svg>
<style>
circle {
fill: green;
mask: url(https://leaking.via/svg-css-mask#foo);
filter: url(https://leaking.via/svg-css-filter#foo);
clip-path: url(https://leaking.via/svg-css-clip-path#foo);
}
</style>
<circle r="40"></circle>
</svg>
<style>
@media all, not print and not monochrome, (min-width: 1px) {
body {
background: url(https://leaking.via/css-media-query);
font-family: expression(alert('url()'));
-webkit-animation: rotate-a-bit;
-webkit-animation-duration: 1s;
}
}
@font-face {
font-family: leak;
src: url(test.woff);
}
strong {
font-family: leak;
color:blue;
background: url("/images/test.gif");
}
strong:after {
content: 'hola!';
}
</style>
<strong>GHI</strong>
</textarea>
</body>
</html>