安全

174 阅读10分钟

XSS(Cross-site scripting)

XSS是如何分类的?

  • Where, 恶意代码存储在何处?
  • Who, 恶意代码被谁拼接插入页面中的?

存储型

1. 攻击者将恶意代码提交到目标网站的数据库中。
2. 用户打开目标网站时,网站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器。
3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。
这种攻击常见于带有用户保存数据的网站功能,如论坛发帖、商品评论、用户私信等。

很明显, 储存型 XSS , 恶意代码储存在 数据库 中, 由 服务端 插入到页面中.

反射型

1. 攻击者构造包含恶意代码的特殊 URL
2. 用户打开 URL 后, 网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器
3. 同上
4. 同上
反射型 XSS 漏洞常见于通过 URL 传递参数的功能,如网站搜索、跳转等.

反射型XSS, 恶意代码存储在 URL 中, 由 服务端 插入到页面中.

DOM型

1. 攻击者构造包含恶意代码的特殊 URL
2. 用户打开带有恶意代码的 URL
3. 用户浏览器接收到响应后解析执行,前端 JavaScript 取出 URL 中的恶意代码并执行.
4. 同上

DOMXSS, 恶意代码存储在 URL 中, 由 浏览器 取出并执行.

DOM 型 XSS 由浏览器取出并执行恶意代码, 属于前端 JavaScript 自身的安全漏洞, 存储型和反射性属于服务端的安全漏洞.

如何预防 XSS?

预防着力点:

  • 防止恶意代码被存储即控制输入input
    • 对输入进行过滤
      • 前端过滤意义不大, 易被绕过
      • 后端转义后再存入数据库, 但服务端并不知道内容要输出的位置, 不同的位置可能有不同的编码, 易带来很大的不确定性和乱码问题, 因此可行性不大.
  • 防止恶意代码被执行即控制输出output
    • Content-Security-Policy, CSP 本质上也是建立白名单,规定了浏览器只能够执行特定来源的代码
      • 只允许加载本站资源: Content-Security-Policy: default-src ‘self’
      • 只允许加载 https 图片: Content-Security-Policy: img-src https://* ...
    • 防止 HTML 中出现注入
      • 前端渲染, 即前后端分离, 将数据与代码分隔开
    • HTML 充分转义
      • 若拼接 HTML 是必要的,就需要采用合适的转义库,对 HTML 模板各处插入点进行充分的转义
function escape(str) {
  str = str.replace(/&/g, '&')
  str = str.replace(/</g, '&lt;')
  str = str.replace(/>/g, '&gt;')
  str = str.replace(/"/g, '&quto;')
  str = str.replace(/'/g, '&#39;')
  str = str.replace(/`/g, '&#96;')
  str = str.replace(/\//g, '&#x2F;')
  return str
}

var xss = require('xss')
var html = xss('<h1 id="title">XSS Demo</h1><script>alert("xss");</script>')
// -> <h1>XSS Demo</h1>&lt;script&gt;alert("xss");&lt;/script&gt;
console.log(html)

在前端规避 DOMXSS 隐患:

  • 对于 DOMXSS, 不使用 innerText, innerHTML , 使用 node.textContent, node.setAttribute 代替
  • Vue/React 中不使用 v-html/dangerouslySetInnerHTML 等api
  • DOM 中的内联事件监听器,如:location、onclick、onerror、onload、onmouseover 等,<a>:hrefeval()、setTimeout()、setInterval() 等,凡是能把字符串作为代码运行。如果不可信的数据拼接到字符串中传递给这些 API,很容易产生安全隐患,请务必避免或谨慎使用。
  • reactJSX已自带转义以预防XSS
  • httpOnly
  • 控制内容长度, 增加 XSS 的攻击难度

CSRF(cross-site request forgery)

跨站请求伪造, 诱导受害者进入恶意网站, 然后由恶意网站向被攻击网站发送跨站的请求.

利用受害者在被攻击网站已经获取的注册凭证(如: cookie中的 sessionId 等), 绕过后台的用户验证, 达到冒充用户身份对被攻击网站执行某项操作的目的.

最关键的一点在于 为何是跨站的 ? 恶意网站是无法直接取得受害者在被攻击网站已取得的存储在cookie中的凭证的, 只能通过跨站和利用浏览器默认自动携带cookie特点来伪造请求.

攻击流程如下:
1. 受害者登录 a.com , 并获得登录凭证(cookie)
2. 诱导受害者进入恶意网站 b.com
3. b.com 向 a.com 发送请求: a.com/act=xxx, 浏览器默认会自动携带 a.com 的cookie
4. a.com 收到请求后, 基于 cookie 对请求进行验证并确认是受害者的凭证, 误以为是受害者自己发出的请求
5. a.com 以受害者的名义执行了act=xxx
6. 攻击完成, 攻击者在受害者不知的情况下, 冒充受害者执行了自己定义的操作

如何发请求? 不一定得是 ajax, 可以简单的通过某些元素的属性来直接发起请求, 如: img 的 src 发起 get 请求.

Get型CSRF

Get 型 CSRF, 利用 HTML 中能够设置src/href等链接地址的标签可以发起一个GET请求, 如:
<link href"">
<img src="">
<meta http-equiv="refresh" content="0,url=">
<iframe src="">
<frame src="">
<script src="">
<a href="">
以及样式中的
@import ""
background:url("")

在受害者访问含有(访问页面即触发)<img src='http://bank.example/withdraw?account=xiaoming&amount=10000&for=hacker'/>的页面后,
浏览器会自动向http://bank.example/withdraw?account=xiaoming&amount=10000&for=hacker发出一次HTTP请求。
bank.example就会收到包含受害者登录信息的一次跨域请求。

链接型的 CSRF 也属于 Get 型 CSRF, 只不过触发条件需要用户点击链接
<a href="http://test.com/csrf/withdraw.php?amount=1000&for=hacker" taget="_blank">
  重磅消息!!
<a/>

Post型CSRF

Post 类型的 CSRF 通常是一个自动提交的表单(访问页面即触发)
<form action="http://bank.example/withdraw" method=POST>
    <input type="hidden" name="account" value="xiaoming" />
    <input type="hidden" name="amount" value="10000" />
    <input type="hidden" name="for" value="hacker" />
</form>
<script> document.forms[0].submit(); </script>

如何预防?

攻击特点:

  1. 通常发生在第三方域名, 即跨域;
  2. CSRF 攻击者只能冒用 Cookie 等信息但不能获取到 Cookie 信息 根据攻击特点, 做如下防御策略:
  • 阻止不明外域 , 基于 CSRF 跨域的特点
    • 同源检测
      • origin header: 指明当前请求来自哪个站点, 若 header 中携带了 origin, 直接使用Origin中的字段确认来源域名即可
      • referer header: 记录了当前 http 请求的来源地址, 也可以用于确定来源域名
    • Samesite Cookie , 限制跨域请求时不携带 cookie
      • Set-Cookie响应头新增Samesite属性,用来标明Cookie是否为“同站 Cookie”,同站Cookie只能作为第一方Cookie,不能作为第三方CookieSamesite 有以下两个属性值
        • Samesite=Strict: 严格模式,表明该 Cookie 在任何情况下都不可能作为第三方 Cookie
        • Samesite=Lax: 宽松模式, 大多数情况也是不发送第三方 Cookie,但导航到目标网址的 Get 请求除外
  • 提交时附加本域才能获取的信息, 基于攻击者无法获取 Cookie 的特点
    • CSRF token
    • 双重 Cookie 验证
  • JWT(Json-Web-Token)

CSRF token

CSRF 攻击者只是冒用Cookie 信息 , 是无法获取到用户信息的 , 如: Cookie, Header, Header 等.

故可以在请求中携带一个攻击者无法获取到的token , 这样服务器通过检验该 token 来区分来自用户的正常请求和来自攻击者的恶意请求 , 进而达到防范 CSRF 的作用.

CSRF token 防护过程 :

  • 用户使用账号和密码向服务器认证, 认证成功后, 服务端生成并返回一个token
  • 将生成的 token输出并到前端, 前端存储起来
  • 前端在发起的请求中以参数的形式携带 token
  • 服务器验证 token 是否正确

双重 Cookie

  • 在用户访问网站页面时,向请求域名注入一个Cookie,内容为随机字符串(如csrfcookie=v8g9e4ksfhw)。
  • 在前端向后端发起请求时,取出Cookie,并添加到URL的参数中(https://www.a.com/comment?csrfcookie=v8g9e4ksfhw
  • 后端接口验证Cookie中的字段与URL参数中的字段是否一致,不一致则拒绝。

补充

首先有必要了解为何需要在客户端存储用户的登录凭证?

由于 http 是无状态的, 在用户首次进行登录认证成功后, 将登录信息存储到客户端, 后续所有请求只需携带该登录信息, 即可避免每次请求前都需要发一个用户身份认证的请求

从 传统session认证 到 基于 token 认证

传统session认证 : 用户登录认证成功后, 在服务器session (session通常保存在内存) 中存储一份用户登录信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie, 以便在后续请求中识别用户身份.

缺点:

  • 随着用户数量的增多, 服务器开销增大
  • 由于保存在内存中, 用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用(即项目部署在服务器集群)上,相应的限制了负载均衡器的能力
  • 基于cookie来识别用户身份, 容易遭受CSRF攻击

token鉴权流程 :

不需要在服务端去保留用户的认证信息或者会话信息。这意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。

  • 用户使用用户名密码来请求服务器
  • 服务器进行验证用户的信息
  • 服务器通过验证发送给用户一个token
  • 客户端存储token,并在每次请求时附送上这个token
  • 服务端验证token值,并返回数据 token必须要在每次请求时传递给服务端,它应该保存在请求头(Authorization)里,另外,服务端要支持CORS(跨来源资源共享)策略,一般我们在服务端这么做就可以了Access-Control-Allow-Origin: *.

注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了

不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分.

参考文章: 什么是 JWT?

密码安全

加盐

对于密码存储来说,必然是不能明文存储在数据库中的,否则一旦数据库泄露,会对用户造成很大的损失。并且不建议只对密码单纯通过加密算法加密,因为存在 彩虹表 的关系。

通常需要对密码加盐,然后进行几次不同加密算法的加密。

// 加盐也就是给原密码添加字符串,增加原密码长度
sha256(sha1(md5(salt + password + salt)))

加盐并不能阻止别人盗取账号,只能确保即使数据库泄露,也不会暴露用户的真实密码。一旦攻击者得到了用户的账号,可以通过暴力破解的方式破解密码。对于这种情况,通常使用 验证码增加延时或者限制尝试次数的方式 。并且一旦用户输入了错误的密码,也不能直接提示用户输错密码,而应该提示账号或密码错误。

彩虹表

彩虹表( Table)是一种破解哈希算法的技术,是一款跨平台密码破解器,主要可以破解MD5、HASH等多种密码。它的性能非常让人震惊,在一台普通PC上辅以NVidia CUDA技术,对于NTLM算法可以达到最高每秒103,820,000,000次明文尝试(超过一千亿次),对于广泛使用的MD5也接近一千亿次。更神奇的是,彩虹表技术并非针对某种哈希算法的漏洞进行攻击,而是类似暴力破解,对于任何哈希算法都有效。