显示用户上传的html内容-安全问题


Displaying user uploaded html content - Security Issue

我有一个功能,用户可以上传html文件,然后我通过PHP读取其内容,并将其作为字符串发送到第三方API。现在,在我把它发送给API之前,我想要生成一个他们上传到用户的HTML预览,这样他们就可以按下确认按钮来发送它。

HTML文件应该主要是字母模板,但用户可以修改HTML和添加一些脚本标签或注入其他恶意代码,可能会损害我的网站,同时显示预览。有什么办法可以避免吗?

我想剥离标签,但如果他们有onclick事件在html元素?

我从这样的东西开始,剥离脚本和注释:

$htmlblacklist[] = '@<script[^>]*?>.*?</script>@si'; //bye bye javascript
$htmlblacklist[] = '@<!['s'S]*?--[ 't'n'r]*>@'; //goodbye comments
//now apply blacklist
$value = preg_replace($htmlblacklist, '', $value);

对于内联事件,你应该使用DOMDocument,因为它理解HTML,而Regex是在黑暗中拍摄。

实际上,您可以对所有这些使用DOMDocument,而根本不使用Regex。在DOMDocument对象中加载HTML,并遍历树,删除您想要的内容。

这不是100%适合您,但似乎将HTML作为SVG呈现到画布上将限制内容在您的需求范围内(没有脚本,没有加载外部源)。

查看更多文档:https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Drawing_DOM_objects_into_a_canvas

你可能想知道这怎么可能是安全的,考虑到对从画布中读取敏感数据的可能性。的答案是:这个解决方案依赖于这样一个事实SVG图像的实现是非常受限的。SVG图像则不是允许加载任何外部资源,例如,甚至是那些似乎来自同一个地区。光栅图像等资源(如JPEG图像)或s必须内联为data: uri。

此外,您不能在SVG图像中包含脚本,因此没有从其他脚本访问DOM的风险,以及SVG中的DOM元素图像不能接收输入事件,因此无法加载将特权信息放入表单控件中(例如到控件的完整路径)文件元素)并呈现它,然后通过读取像素

我可能已经找到了处理这个问题的库。我还没有完全测试过它,但根据它的描述,它可能是:http://htmlpurifier.org/

使用FileReader读取文件的内容,使用iframe安全地(或不安全地)查看它:

document.querySelector("button").addEventListener(
    'click',
    function() {
      let iframe = document.createElement("iframe"),
        holder = document.querySelector("#iframeholder"),
        sandboxFlags = [
          ...document.querySelectorAll('.sandbox-flags:checked')
        ].map(_ => _.value).join(','),
        file = document.querySelector('input[type=file]').files[0],
        reader = new FileReader();
      reader.addEventListener("load", function() {
        iframe.setAttribute("scrolling", "no");
        iframe.setAttribute("frameborder", "0");
        iframe.setAttribute("srcdoc", this.result);
        /*
         * Sandboxing is not allowed in code snippets
         * iframe.setAttribute("sandbox", sandboxFlags);
         *
         */
        console.log(`sandbox=${sandboxFlags}`);
        while (holder.firstChild)
          holder.removeChild(holder.firstChild);
        holder.appendChild(iframe);
        
      }, false);
      reader.readAsText(file);
  },
  false);
label {
  display: block
}
#iframeholder>iframe {
  border:1px solid black;
  height:400px;
  width:400px;
}
<div>
  <input id="browse" type="file" >
</div>
  <label>
    <input type="checkbox" class="sandbox-flags" value='allow-script' />allow-scripts
  </label>
  <label>
    <input type="checkbox" class="sandbox-flags" value='allow-popups-to-escape-sandbox' />allow-popups-to-escape-sandbox
  </label>
  <label>
    <input type="checkbox" class="sandbox-flags" value='allow-forms' />allow-forms
  </label>
  <label>
    <input type="checkbox" class="sandbox-flags" value='allow-modals' />allow-modals
  </label>
<div>
  <button type="button">Preview</button>
</div>
<div id="iframeholder"></div>