Web安全 —— 防御篇 | 青训营笔记

129 阅读14分钟

Web安全 —— 防御篇 | 青训营笔记

这是我参与「第四届青训营 」笔记创作活动的第4天

1.针对XSS攻击的防御方式

  • 永远不信任用户的提交内容
  • 不要将用户提交内容直接转换成 DOM(更多的应该当作字符串来对待)

1.1 防御 XSS —— 现成工具

  • 前端

    • 主流框架默认防御 XSS

      比如说React,Vue

    • 谷歌提供的google-closure-library

      提供了完善的防御XSS攻击的各组工具函数

  • 服务端(Node)

    • DOMPurify

      npm包,帮助我们完成字符串的转义,避免 XSS 攻击

1.2 XSS —— 必须动态生成 DOM

用户需求要求必须接受用户传过来的富文本,动态生成DOM。那么要怎么办呢?

实际上遇到这种情况目前也没有什么好的解决方案,只能是在一些需要注意的点上进行防范

⚠⚠ string -> DOM

如果我们要把string直接生成DOM,这里调用了一个 DOMParser 的 API

这个时候一定要注意,需要对string进行转义

⚠⚠ 上传svg

如果我们允许用户上传 svg 文件,比如说图片,我们也要对 svg 文件进行扫描,因为按照规范,svg 之中可以插入 script 标签,那么也就是说,如果 svg 图片被加载,script 标签会被执行,也就完成了一次 XSS 攻击。

⚠⚠ 自定义跳转链接

尽量的不要做让用户自定义跳转链接的行为。如果需要,也一定要做好过滤,因为如果允许他自定义跳转链接的话,是可以传递 js 代码的

⚠⚠ 自定义样式

这种方式其实非常巧妙,如果我们允许用户自定义样式的话,也有可能导致 XSS 攻击。所以我们要对这种形式额外的留意。

比如说这里有一个输入项,让用户去填写他的具体收入,然后假如有个攻击者传入一个自定义的样式,这段CSS代码,只有指定的标签被选中的时候,才会触发一个背景图的请求,如一个get请求。这也就是说,只有指定收入的用户选择,才会发起get请求,这个用户的收入情况,也就暴露给了恶意攻击者


2.Content Security Policy(CSP 内容安全策略)

  • 哪些源(域名)被认为是安全的
  • 来自安全源的脚本可以执行,否则直接抛错
  • 对 eval + inline script(内联的script标签) 直接拒绝报错

从域名这个角度指定哪些脚本可以使用,那么恶意脚本就难以完成攻击

可以在服务器进行设置,也可以在浏览器设置


3.针对 CSRF 跨站伪造请求的防御方式

3.1 CSRF —— Origin + Referer

由 CSRF 发送的请求来源是异常来源,如果请求来源是异常来源,那么就限制这种请求来源,从而限制这种攻击。

如果是服务器端的开发人员,那么就可以针对接口请求对 Origin 或者请求的 Referer 进行校验,如果 Origin 或者 Referer 是我的域名,那么我就放行这个请求,如果不是我的域名的话,我就拒绝这个请求,也就是说其他恶意页面向我发起的请求我就可以统统拒绝。但这里列举了一下,同源请求中,GET 和 HEAD 的请求方式,是不会发送 Origin 这个字段的。所以有时候 Referer 会被更广泛的去利用。

3.2 CSRF —— token

两个注意点:

  • 攻击者也有可能是合法的注册用户,可以用自己的 token 去模拟去试探这个 token 可不可以,所以token必须跟用户进行绑定,才能确保不会被其他的注册用户利用

  • 为什么需要过期时间?

    假如说 token 一直有效,那保不齐哪一天用户的 token 被窃取了,那他之前的所有请求,都有可能被窃取暴露,甚至说他之后的请求,也可以长时间供攻击者去利用。

3.3 CSRF —— iframe 攻击

绕过Origin限制,绕过同源限制的攻击,也就是 iframe 的攻击

攻击者会构造一个页面,页面上有一个button标签,button标签下面会盖着一个 iframe ,也就是我们的合法页面。

那么用户点击这个button标签的时候,由于button标签设置的 CSS 属性,导致点击行为被穿越到下方的 iframe 之中,iframe 可能因为受到了点击,而发起一个 http 请求,而因为 iframe 发起的请求不是跨域请求,是同源请求,那么对应的攻击也就完成了

那么面对这种 iframe 攻击,怎么防御?

其实有一个 HTTP 响应头部可以被利用到,即 X-Frame-Options:DENY/SAMEORIGIN。如果我们可以在服务器进行编码的话,就可以对所有的页面设置 X-Frame-Options 这个响应头部。

那么它有两个值可以选,一种是DENY,一种是SAMEORIGIN。

  • DENY 其实也很好理解,即当前这个页面不能被作为 iframe 进行加载
  • SAMEORIGIN 顾名思义,必须是同源的页面才可以加载我这个 iframe

通过这种方式,就能规避攻击者利用 iframe 这种攻击方式

3.4 CSRF anti-pattern(CSRF 反模式)

有一些开发为了偷懒,有时候将 get 的请求实现为既能 get 又能 post,也就是说既能获取数据,又能修改数据。

假如有一个getAndUpdate接口,这个接口会读 update 和 id 字段,如果读到了 update 有内容,那么它就执行更新操作,如果没有内容,那么就返回 get 操作。这样的话,如果会被 CSRF 攻击到,那不仅是用户隐私可能被泄露,甚至用户的隐私都可能被窜改,攻击者一石二鸟。

写代码的时候一定要避免这种偷懒行为,一定还是要根据功能把各个接口按照职责划分开,应该是 get 就用 get ,应该是 post 就用 post。

3.5 避免用户信息被携带:SameSite Cookie

另外一种防御 CSRF 策略,也就是前一段时间比较火的 SameSite Cookie 属性

如果 CSRF 利用的是用户权限,而用户权限有在我们的 cookie 之中,那么的话,如果我们的请求不带上这个 cookie ,是不是就没有这种跨站伪造请求的方式,换句话说,我页面的 cookie 只能为我所用,其他页面向我发出请求都不能带上我的 cookie。这也是从根源上解决了CSRF攻击的方式。

具体 SameSite Cookie 的含义:假如我有一个页面,页面对应的域名是A,那么此时所有 domain 属性是 A 的 Cookie ,都称之为第一方 Cookie ,也就是 SameSite Cookie 。所有 domain 属性不是 A 的 Cookie ,全部归为第三方 Cookie ,也就是非 SameSite Cookie 。那当我们向域名 A 发出请求的时候,所有第一方的 Cookie 都可以成功的被带上,所有第三方的 Cookie 都不会带给域名 A 的服务器,这也是在讲浏览器的默认行为。

补充:domain 属性,可以访问该 Cookie 的域名。如果设置为 ".google.com" ,则所有以 ".google.com" 结尾的域名都可以访问该 Cookie 。注意第一个字符必须为 "." 。

domain表示的是cookie所在的域,默认为请求的地址,如网址为www.study.com/study,那么domain默认为www.study.com。而跨域访问,如域A为t1.study.com,域B为t2.study.com,那么在域A生产一个令域A和域B都能访问的cookie就要将该cookie的domain设置为.study.com;如果要在域A生产一个令域A不能访问而域B能访问的cookie就要将该cookie的domain设置为t2.study.com。注意:一般在域名前是需要加一个"."的,如"domain=.study.com"。

所以 SameSite Cookie 限制的是:① Cookie domain ② 页面域名

即 Cookie 的 domain 属性和当前域名是不是能够匹配

那依赖 Cookie 的第三方服务怎么办?

如果我是一个第三方服务,我的服务依赖于 Cookie 怎么办,像刚刚所说的,第三方 Cookie 不会被带上,那我的服务就不能正常工作。

举一个例子:假如说我们构造一个页面,有一个某站播放器的 SDK ,但因为它的 Cookie 是作为第三方 Cookie,向我页面请求的时候,用户状态登录带不上,导致用户发不了弹幕,用户体验非常糟糕,这怎么办呢。

实际上 SameSite Cookie 已经想到了如何应对这种方式。也就是我们在服务器端进行 Set-Cookie 操作的时候,可以把 SameSite 这个属性标志为 none,也就是说不对 SameSite 进行任何限制,但同时我们需要标明这个 Cookie 是 Secure 的,以确保安全。

SameSite vs CORS(跨站资源共享)

SameSite 和 CORS 听上去可能有些重复,或者说有一些迷惑

  • SameSite 针对的是 Cookie 的发送,CORS 是对资源读写或者 HTTP 请求进行一些限制
  • SameSite 对比的是 Cookie 的 domain 属性和当前域名是否一致,CORS 对比的是资源的域名和当前的域名是否一致。
  • 说的通俗一点 SameSite 场景化就是 “我跟你说个事儿,出这屋我可就不认了”,也就是限制条件是这个屋,即当前页面。
  • 而 CORS 更接近于白名单的机制,我允许你访问你才能访问,我不允许,你既然是跨站的,就不允许访问我的资源。

3.6 防御 CSRF 的正确姿势

防御 CSRF 的方法非常的多,那我们肯定不能 case by case 的去解。

防御的正确姿势:从 node 这一层角度上来讲,我们应该做一个中间件,这个中间件就专门去生成各种防御 CSRF 的策略,就在一处,保证我们整个 Web APP 都能完成对 CSRF 的防御。


4.针对Injection注入攻击的防御方式

  • 找到项目中查询 SQL 的地方

  • 使用 prepared statement

    即将 SQL 语句进行提前的编译,导致 injection 注入这种攻击不能完成

Injection beyond SQL

除了SQL外,针对其他注入攻击,列举一些其他的防御措施:

  • 最小权限原则:所有的命令都不要通过 sudo 来执行,不要给 root 权限
  • 建立白名单机制,只允许指定命令执行,rm这种高危操作,是要坚决拒绝的
  • 针对 SSRF 攻击方式,对 URL 类型参数进行协议、域名、ip等限制,避免攻击者能够访问我们的内网资源,造成我们的内网系统瘫痪

5.针对 Dos 服务拒绝的防御方式

Regex Dos(基于正则表达式的Dos)

  • 最重要的是,要完善我们代码 Review 的工作,避免写出贪婪匹配的方式,特别是接口处理相关操作的时候
  • 可以利用一些代码扫描工具,去扫描整个代码仓库中存在的正则表达式,并把它们做规整,并对这些正则进行性能的基础测试
  • 更加根本的做法,直接拒绝使用用户提供的正则表达式。因为如果用户能够自定义正则表达式,也就是说,我们把基于正则的 Dos 攻击入口暴露在外,非常危险。

5.1 针对 DDoS

列举几个比较关键的防御思路:

  • 流量治理:我们可以在负载均衡这一层或者 API 网关层进行一些流量的识别,然后把能够识别出是恶意攻击的进行过滤,可以转到其他服务或者直接拒绝。

    另外一种是前置 CDN ,所有流量都要先过CDN,再到我们具体的服务,这实际上是第二种思路 —— 抗量

  • 抗量还有两种策略:

    • 第一种是快速自动扩容,也就是当我们的服务检测到流量激增的时候,我们有一个方案,可以自动执行快速扩容的操作,以承载更多的流量
    • 另外一种是非核心服务降级。所有非核心业务功能统统降级,也就是关闭,这样可以匀出更多的计算资源去应对激增的流量

5.2 针对传输层的防御方式 —— 防御中间人

HTTPS 的一些特性

  • 可靠性就是 HTTP 运用了加密,避免了明文传输,
  • 完整性是 MAC 校验规则,确保信息没有被篡改
  • 不可抵赖性是数字签名,确保了双方的身份是可被信任的

数字签名

  • 数字签名会有一个签名执行者,签名执行者会有一对私钥和公钥,私钥是自己藏好,公钥是各方都可见,公开的。
  • 数字签名的过程:签名执行者使用他的私钥对一些指定内容进行数学计算,生成一个对应的签名。凡是具备对应公钥的人,都可以用公钥对生成的签名,进行一次校验,如果确实是用对应的私钥生成的签名,那么校验就会通过。如果不是的话,则不通过。
  • 具体细节查阅网上

HTTPS 或 TLS 的大致流程(简略版本)

  • HTTPS 或 TLS 的过程分为对称加密非对称加密两个大过程
  • 先进行非对称加密过程,浏览器会先把自己支持的加密套件选项传给服务端,服务端接受这个可支持的加密套件选项后,会选一个具体的套件,并且把服务器端的证书,返回给浏览器。浏览器先将证书进行校验,如果证书校验通过,那么双方会根据之前协商好的加密算法以及一些额外的随机数等,算出一个 sessionKey,此时非对称加密过程结束。
  • 之后进入对称加密过程,双方使用 sessionKey 作为 secure,对所有的信息进行对称加密,所有的传输信息都是加密后的信息,这就是一次完整的TLS过程。

HTTPS —— 完整性

如何确保一个信息的完整性?

所有传输的信息,除了是有加密的信息之外,还会额外传递一个加密信息对应的 hash 哈希值,所有的接收方会对加密的信息重新进行一次哈希计算,会和传递过来的哈希值进行对比。如果两者相等,说明没有被篡改,不等则说明信息被篡改。

HTTPS —— 不可抵赖性:数字签名

  • HTTPS 在非对称加密过程中会传递证书,存在一个 CA 证书机构,证书机构会完成一些签名的操作。

    比如说:服务的提供方会把它的一些元信息以及一些公钥合并成一对信息,然后使用 CA 提供的一对私钥进行签名,生成真正的服务器端保存的证书,这个证书就会传递给浏览器层。浏览器就会用 CA 颁发的公钥,对这个证书进行校验。如果校验通过,说明这个证书合法,也就是证书签发者的身份是可信的,这也就是完成了一次数字签名校验的流程。

  • 那浏览器是如何有 CA 的公钥的呢?

    实际上每一个主流的浏览器都会内置大量 CA 签发证书,这些证书里面就会包含 CA 各个的公钥

  • 成也证书,败也证书。

    当签名算法不够健壮时,证书是可以被破解的,也就是可以被造假的,也就导致了中间人又有可乘之机,但好在签名算法不断地迭代,以及 CA 方不断地更新迭代,现在的签名算法已经非常的健壮,以及非常难以通过单一的一个攻击者暴力破解,现在可以非常放心的使用证书。

HTTP Strict-Transport-Security(HSTS)

  • HTTP Strict-Transport-Security(HSTS) 即如何将 HTTP 请求升级为 HTTPS
  • 简单流程:首先浏览器向服务器端发送一个 Https 的请求,服务器端接受到这个请求后会返回 Strict-Transport-Security 这个头部,并且传递一个值 max-age=3600 ,即在这个时间范围内,之后如果浏览器发出的是 http 请求,那么统统升级为 Https 协议,也就确保了之后,不会再因为用户手动输入 http,而导致潜在中间人攻击。当然这种方式也有一个缺点就是,一定要先有一次 https 请求

Subresource Integrity(SRI)

  • 前端工程开发会把很多的静态资源,比如 js,css,... 文件,放到 CDN ,也就是一个静态资源托管的地方,如果 CDN 被攻击者攻陷,导致我们所有的资源被劫持,被篡改,那么怎么办呢?

  • 通过 SRI 解决。SRI 的功能实际上就是对比哈希值 hash