在 JavaScript 中呈现一个安全的 HTML 子集

Render a safe HTML subset in JavaScript

本文关键字:一个 安全 子集 HTML JavaScript      更新时间:2023-09-26

作为markdown的替代方案,我正在寻找一种在JavaScript中安全地解析HTML的可配置子集的方法。

例如,对于 (不受信任的) 输入

<b onclick="alert('XSS');" data-myapp="asdf" style="color:red">Hello</b>
<h1><i style="color:expression(alert('XSS'));"> World</i></h1>

使用参数

allowTags: b, i
allowAttrs: data-myapp
allowSafeStyle: color

我期待输出

<b data-myapp="asdf" style="color:red">Hello</b>
<i> World</i>

Markdown似乎无法表达更复杂的属性。Caja 似乎非常接近我想要的,但需要服务器端渲染。那么,如何在 JavaScript 中渲染一个安全的(根据上述参数allowTagsallowAttrs 等)HTML 子集呢?

我使用 jQuery 来缩短我的答案并包含更少的样板代码,但它不相关。

我正在使用.innerHTML因为它不会在 html 中执行可能的脚本或 css。

演示在这里 http://jsfiddle.net/QCaGq/

function filterData(data, options ){
    var root;
    try {
        root = document.implementation.createHTMLDocument().body;
    }
    catch(e) {
        root = document.createElement("body");
    }
    root.innerHTML = data;
    $(root).find("*").filter(function(){
        return options.allowTags.indexOf(this.tagName.toLowerCase()) === -1;
    }).each( function() {
        $(this).children().detach().insertBefore( this );
        $(this).remove();
    });
    function removeStyle( node, attr ) {
        var style = node.style,
            prop,
            name,
            len = style.length,
            i, val = "";
        for( i = 0; i < len; ++i ) {
            name = style[i];
            prop = style[name];
            if( options.allowSafeStyle.indexOf( name ) > -1 ) {
                val += (name + ":" + prop + ";");
            }
        }
        if( val ) {
            attr.nodeValue = val;
        }
        else {
            node.removeAttribute("style");
        }
    }
    function removeAttrs( node ) {
        $.each( node.attributes, function( index, attr ) {
            if( !attr ) {
                return;
            }
            if( attr.name.toLowerCase() === "style" ) {
                return removeStyle( node, attr );
            }
            if( options.allowAttrs.indexOf(attr.name.toLowerCase()) === -1 ) {
                node.removeAttribute(attr.name);
            }
        });
    }
    function walk( root ) {
        removeAttrs(root);
        $( root.childNodes ).each( function() {
            if( this.nodeType === 8 ) { //Remove html comments
                $(this).remove();
            }
            else if( this.nodeType === 1 ) {
                walk(this);
            }
        });
    }
    walk(root);
    return root.innerHTML; 
}

var opts = {
    allowTags: ["b", "i"],
    allowAttrs: ["data-myapp"],
    allowSafeStyle: ["color"]
}
filterData( '<b onclick="alert(''XSS'');" data-myapp="asdf" style="color:red">Hello</b>'n<h1><i style="color:expression(alert(''XSS''));"> World</i></h1>', opts );

结果:

<b data-myapp="asdf" style="color:red;">Hello</b>
<i> World</i>

这应该让你开始。

相关文章: