前端安全

167 阅读8分钟

XSS

什么是XSS?

juejin.cn/post/684490…

Cross-Site Scripting (跨站脚本攻击)简称XSS,是一种代码注入攻击。攻击者通过在目标网站上注入恶意脚本,使之在用户的浏览器上运行。利用这些恶意脚本,攻击者可获取用户的敏感信息如Cookie、SessionID等,进而危害数据安全。

在部分情况下,由于输入的限制,注入的恶意脚本比较短。但可以通过引入外部的脚本,并有浏览器执行,来完成比较复杂的攻击策略。

XSS的分类

  • 存储型XSS
  1. 攻击者将恶意代码提交到目标网站的数据库中。
  2. 用户打开目标网站时,网站服务器会将恶意代码从数据库取出,拼接在HTML中返回给浏览器。
  3. 用户浏览器接收代响应后解析执行,混在其中的恶意代码被执行。
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

这种攻击常见于带有用户保存数据的网站功能,如论坛发帖、商品评论、用户私信等。

  • 反射型XSS
  1. 攻击者构造出特殊的URL,其中包含恶意代码。
  2. 用户打开带有恶意代码的URL时,网站服务端将恶意代码从URL中取出,拼接在HTML中返回给浏览器。
  3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

反射型XSS和存储型XSS的区别是:存储型XSS的恶意代码存在数据库中,反射型XSS的恶意代码存在URL中。

反射型URL漏洞常见于通过URL传递参数的网站,如网站搜索、跳转等。

由于需要用户主动打开恶意的URL才能生效,攻击者往往会结合多种手段诱导用户点击。

POST的内容也可以触发反射型XSS,只不过其触发条件比较苛刻(需要构造表单提交页面,并诱导用户点击),所以非常少见。

  • DOM型XSS
  1. 攻击者构造出特殊的URL,其中包含恶意代码。
  2. 用户打开带有恶意代码的URL。
  3. 用户浏览器接收到响应后解析执行,前端JavaScript取出URL中的恶意代码并执行。
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

DOM型XSS跟前两种XSS的区别:DOM型XSS攻击中,取出和执行恶意代码由浏览器端完成,属于前端JavaScript自身的漏洞,而其他两种XSS都属于服务端的安全漏洞。

XSS攻击的预防

  • 纯前端渲染

  • 过滤转义脚本

  • CSP (Content Security Policy) 严格的CSP在XSS的防范中可以起到以下的作用:

    禁止加载外域代码,防止复杂的攻击逻辑。
    禁止外域提交,网站被攻击后,用户的数据不会泄露到外域。
    禁止内联脚本执行(规则较严格,目前发现github使用)。
    禁止未授权的脚本执行(新特性,Google Map移动版在使用)。
    合理使用上报可以及时发现XSS,利于尽快修复问题。
    
  • 输入内容长度控制

    对于不受信任的输入,都应该限定在一个合理的长度。虽然无法完全防止XSS发生,但可以增加XSS攻击的难度。

  • HTTP-only Cookie:禁止JavaScript读取某些敏感Cookie,攻击者完成XSS注入后也无法窃取此Cookie。

  • 验证码:防止脚本冒充用户提交危险操作。

CSRF

什么是CSRF?

juejin.cn/post/684490…

CSRF(Cross-site request forgery)跨站请求伪造:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对攻击的网站执行某项操作的目的。

一个典型的CSRF攻击有着如下的流程:

受害者登录a.com,并保留了登录凭证(Cookie)。
攻击者引诱受害者访问了b.comb.coma.com服务端发送了一个请求:a.com/acr=xxx 。
a.com服务端结束到请求后,对请求进行验证,并确认是受害者的凭证,误以为是受害者自己发送的请求。
a.com以受害者的名义执行了act=xxx。
攻击完成,攻击者在受害者不知情的情况下,冒充受害者,让a.com执行了自己定义的操作。

CSRF攻击的预防

  • 使用Origin请求头确定来源域名 但Origin在以下两种情况下并不存在:

    1. IE11同源策略
    2. 302重定向
  • 使用Referer请求头来确定来源域名 但攻击者可以影藏,甚至修改自己请求的Referer。

  • CSRF Token

    我们可以要求所有的用户请求都携带一个CSRF攻击者无法获取到的Token。服务器通过效验请求是否携带正确的Token,来把正常的请求和攻击的请求区分。

    如何实现CSRF Token?

  1. 将CSRF Token输出到页面中

    首先,用户打开页面的时候,服务器需要给这个用户生成一个Token,该Token通过加密算法对数据进行加密,一般Token都包含随机字符串和时间戳额组合,显然在提交的时Token不能再放在Cookie中了,否则又会被攻击者冒用。因此,为了安全起见Token最好还是存在服务器的Session中,之后在每次页面加载时,使用js遍历整个DOM树,对于DOM中所有的a和form标签后加入Token。这样可以解决大部分的请求,但是对于在页面加载之后动态生成的HTML代码,这种方法就没有作用,还需要程序员在编码时手动添加token。

  2. 页面提交的请求携带这个Token 对于GET请求,Token将附在请求地址之后,这样URL就变成http://url?csrftoken=tokenvalue。而对于POST请求来说,要在form的最后加上:<input type="hidden" name="csrftoken" value="tokenvalue" />这样,就把Token以参数的形式加入请求了。

  3. 服务器验证Token是否正确 当用户从客户端得到了Token,再次提交给服务器的时候,服务器需要判断Token的有效性,验证过程是先解密Token,对比加密字符串以及时间戳,如果加密字符串一致且时间未过期,那么这个Token就是有效的。

  • 分布式校验 在大型的网站中,使用Session存储CSRF Token会带来很大的压力,访问单台服务器session是同一个。但是现在的大型网站中,我们的服务器不止一台,可能是几十台甚至是几百台之多,甚至多个机房都可能不在同一个省份,用户发起的HTTP请求通常要经过像Ngnix之类的负载均衡器之后,在路由到具体的服务器上。由于Session默认存储在单机服务器内存中,因此在分布式环境下同一个用户发送的多次HTTP请求可能先后落在不同的服务器上,导致后面发起的HTTP请求无法拿到之前的HTTP请求存储在服务器中的Session数据,从而使得session机制在分布式环境下失效,因此在分布式集群中CSRF Token需要存储在Redis之类的公共存储空间。

由于使用Session存储,读取和验证CSRF Token会引起较大的复杂性和性能问题,目前很多网站采用Encrypted Token Pattern方式。这种方法的Token是一个计算出来的结果,而非随机生成的字符串,这样在校验时无需再去读取存储的Token,只用再次计算一次即可。

这种Token的值通常是使用UserId,时间戳和随机数,通过加密的方法生成,这样既可以保证分布式服务的Token一致,又能保证Token不容易被破解。

在Token解密成功之后,服务器可以访问解析值,Token中包含的UserID和时间戳将会被拿来验证有效性,将UserID与当前登录的UserID解析对比,并将时间戳和当前时间解析比较。

验证码和密码也可以起到CSRF Token的作用,而且更安全。 所有很多银行等网站会要求已经登录的用户在转账时再次输入密码。

  • 双重Cookie验证

  • SameSite Cookie属性 setCookie响应头新增SameSite属性,它用来表明这个Cookie是一个为“网站Cookie”,同站Cookie只能作为第一方Cookie,不能作为第三方Cookie。

SameSite有两个属性值,分别为Strict和Lax。

如果SameSiteCookie被设置为Strict,浏览器在任何跨域请求中都不会携带Cookie,新标签重新打开也不携带,所以CSRF攻击基本没有机会。

SameSite兼容性不好。

点击劫持

juejin.cn/post/684490…