I. 代码实现

1.1 原生DOM API的安全操作

1.1.1【必须】HTML标签操作,限定/过滤传入变量值

  • 使用innerHTML=outerHTML=document.write()document.writeln()时,如变量值外部可控,应对特殊字符(&, <, >, ", ')做编码转义,或使用安全的DOM API替代,包括:innerText=
  1. // 假设 params 为用户输入, text 为 DOM 节点
  2. // bad:将不可信内容带入HTML标签操作
  3. const { user } = params;
  4. // ...
  5. text.innerHTML = `Follow @${user}`;
  6. // good: innerHTML操作前,对特殊字符编码转义
  7. function htmlEncode(iStr) {
  8. let sStr = iStr;
  9. sStr = sStr.replace(/&/g, "&amp;");
  10. sStr = sStr.replace(/>/g, "&gt;");
  11. sStr = sStr.replace(/</g, "&lt;");
  12. sStr = sStr.replace(/"/g, "&quot;");
  13. sStr = sStr.replace(/'/g, "&#39;");
  14. return sStr;
  15. }
  16. const { user } = params;
  17. user = htmlEncode(user);
  18. // ...
  19. text.innerHTML = `Follow @${user}`;
  20. // good: 使用安全的DOM API替代innerHTML
  21. const { user } = params;
  22. // ...
  23. text.innerText = `Follow @${user}`;

1.1.2【必须】HTML属性操作,限定/过滤传入变量值

  • 使用element.setAttribute(name, value);时,如第一个参数值name外部可控,应用白名单限定允许操作的属性范围。

  • 使用element.setAttribute(name, value);时,操作a.hrefifame.srcform.actionembed.srcobject.datalink.hrefarea.hrefinput.formactionbutton.formaction属性时,如第二个参数值value外部可控,应参考JavaScript页面类规范1.3.1部分,限定页面重定向或引入资源的目标地址。

  1. // good: setAttribute操作前,限定引入资源的目标地址
  2. function addExternalCss(e) {
  3. const t = document.createElement('link');
  4. t.setAttribute('href', e),
  5. t.setAttribute('rel', 'stylesheet'),
  6. t.setAttribute('type', 'text/css'),
  7. document.head.appendChild(t)
  8. }
  9. function validURL(sUrl) {
  10. return !!((/^(https?:\/\/)?[\w\-.]+\.(qq|tencent)\.com($|\/|\\)/i).test(sUrl) || (/^[\w][\w/.\-_%]+$/i).test(sUrl) || (/^[/\\][^/\\]/i).test(sUrl));
  11. }
  12. let sUrl = "https://evil.com/1.css"
  13. if (validURL(sUrl)) {
  14. addExternalCss(sUrl);
  15. }

1.2 流行框架/库的安全操作

1.2.1【必须】限定/过滤传入jQuery不安全函数的变量值

  • 使用.html().append().prepend().wrap().replaceWith().wrapAll().wrapInner().after().before()时,如变量值外部可控,应对特殊字符(&, <, >, ", ')做编码转义。
  • 引入jQuery 1.x(等于或低于1.12)、jQuery2.x(等于或低于2.2),且使用$()时,应优先考虑替换为最新版本。如一定需要使用,应对传入参数值中的特殊字符(&, <, >, ", ')做编码转义。
  1. // bad:将不可信内容,带入jQuery不安全函数.after()操作
  2. const { user } = params;
  3. // ...
  4. $("p").after(user);
  5. // good: jQuery不安全函数.html()操作前,对特殊字符编码转义
  6. function htmlEncode(iStr) {
  7. let sStr = iStr;
  8. sStr = sStr.replace(/&/g, "&amp;");
  9. sStr = sStr.replace(/>/g, "&gt;");
  10. sStr = sStr.replace(/</g, "&lt;");
  11. sStr = sStr.replace(/"/g, "&quot;");
  12. sStr = sStr.replace(/'/g, "&#39;");
  13. return sStr;
  14. }
  15. // const user = params.user;
  16. user = htmlEncode(user);
  17. // ...
  18. $("p").html(user);
  • 使用.attr()操作a.hrefifame.srcform.actionembed.srcobject.datalink.hrefarea.hrefinput.formactionbutton.formaction属性时,应参考JavaScript页面类规范1.3.1部分,限定重定向的资源目标地址。

  • 使用.attr(attributeName, value)时,如第一个参数值attributeName外部可控,应用白名单限定允许操作的属性范围。

  • 使用$.getScript(url [, success ])时,如第一个参数值url外部可控(如:从URL取值拼接,请求jsonp接口),应限定可控变量值的字符集范围为:[a-zA-Z0-9_-]+

1.2.2【必须】限定/过滤传入Vue.js不安全函数的变量值

  • 使用v-html时,不允许对用户提供的内容使用HTML插值。如业务需要,应先对不可信内容做富文本过滤。
  1. // bad:直接渲染外部传入的不可信内容
  2. <div v-html="userProvidedHtml"></div>
  3. // good:使用富文本过滤库处理不可信内容后渲染
  4. <!-- 使用 -->
  5. <div v-xss-html="{'mode': 'whitelist', dirty: html, options: options}" ></div>
  6. <!-- 配置 -->
  7. <script>
  8. new Vue({
  9. el: "#app",
  10. data: {
  11. options: {
  12. whiteList: {
  13. a: ["href", "title", "target", "class", "id"],
  14. div: ["class", "id"],
  15. span: ["class", "id"],
  16. img: ["src", "alt"],
  17. },
  18. },
  19. },
  20. });
  21. </script>
  • 使用v-bind操作a.hrefifame.srcform.actionembed.srcobject.datalink.hrefarea.hrefinput.formactionbutton.formaction时,应确保后端已参考JavaScript页面类规范1.3.1部分,限定了供前端调用的重定向目标地址。

  • 使用v-bind操作style属性时,应只允许外部控制特定、可控的CSS属性值

  1. // bad:v-bind允许外部可控值,自定义CSS属性及数值
  2. <a v-bind:href="sanitizedUrl" v-bind:style="userProvidedStyles">
  3. click me
  4. </a>
  5. // good:v-bind只允许外部提供特性、可控的CSS属性值
  6. <a v-bind:href="sanitizedUrl" v-bind:style="{
  7. color: userProvidedColor,
  8. background: userProvidedBackground
  9. }" >
  10. click me
  11. </a>

1.3 页面重定向

1.3.1【必须】限定跳转目标地址

  • 使用白名单,限定重定向地址的协议前缀(默认只允许HTTP、HTTPS)、域名(默认只允许公司根域),或指定为固定值;

  • 适用场景包括,使用函数方法:location.hrefwindow.open()location.assign()location.replace();赋值或更新HTML属性:a.hrefifame.srcform.actionembed.srcobject.datalink.hrefarea.hrefinput.formactionbutton.formaction

  1. // bad: 跳转至外部可控的不可信地址
  2. const sTargetUrl = getURLParam("target");
  3. location.replace(sTargetUrl);
  4. // good: 白名单限定重定向地址
  5. function validURL(sUrl) {
  6. return !!((/^(https?:\/\/)?[\w\-.]+\.(qq|tencent)\.com($|\/|\\)/i).test(sUrl) || (/^[\w][\w/.\-_%]+$/i).test(sUrl) || (/^[/\\][^/\\]/i).test(sUrl));
  7. }
  8. const sTargetUrl = getURLParam("target");
  9. if (validURL(sTargetUrl)) {
  10. location.replace(sTargetUrl);
  11. }
  12. // good: 制定重定向地址为固定值
  13. const sTargetUrl = "http://www.qq.com";
  14. location.replace(sTargetUrl);

1.4 JSON解析/动态执行

1.4.1【必须】使用安全的JSON解析方式

  • 应使用JSON.parse()解析JSON字符串。低版本浏览器,应使用安全的Polyfill封装
  1. // bad: 直接调用eval解析json
  2. const sUserInput = getURLParam("json_val");
  3. const jsonstr1 = `{"name":"a","company":"b","value":"${sUserInput}"}`;
  4. const json1 = eval(`(${jsonstr1})`);
  5. // good: 使用JSON.parse解析
  6. const sUserInput = getURLParam("json_val");
  7. JSON.parse(sUserInput, (k, v) => {
  8. if (k === "") return v;
  9. return v * 2;
  10. });
  11. // good: 低版本浏览器,使用安全的Polyfill封装(基于eval)
  12. <script src="https://github.com/douglascrockford/JSON-js/blob/master/json2.js"></script>;
  13. const sUserInput = getURLParam("json_val");
  14. JSON.parse(sUserInput);

1.5 跨域通讯

1.5.1【必须】使用安全的前端跨域通信方式

  • 具有隔离登录态(如:p_skey)、涉及用户高敏感信息的业务(如:微信网页版、QQ空间、QQ邮箱、公众平台),禁止通过document.domain降域,实现前端跨域通讯,应使用postMessage替代。

1.5.2【必须】使用postMessage应限定Origin

  • 在message事件监听回调中,应先使用event.origin校验来源,再执行具体操作。

  • 校验来源时,应使用===判断,禁止使用indexOf()

  1. // bad: 使用indexOf校验Origin值
  2. window.addEventListener("message", (e) => {
  3. if (~e.origin.indexOf("https://a.qq.com")) {
  4. // ...
  5. } else {
  6. // ...
  7. }
  8. });
  9. // good: 使用postMessage时,限定Origin,且使用===判断
  10. window.addEventListener("message", (e) => {
  11. if (e.origin === "https://a.qq.com") {
  12. // ...
  13. }
  14. });