这是我参与「第四届青训营 」笔记创作活动的的第9天
攻击篇
Cross-site scripting(XSS): 跨站脚本攻击
XSS 攻击使攻击者能够将客户端脚本注入到其他用户查看的网页中。攻击者可能会利用跨站脚本漏洞绕过同源策略等访问控制。造成的后果可能是用户隐私泄露,也有可能使用户的机器被当做挖矿的机器。
XSS主要利用
- 作为开发者,盲目信任用户提交的内容。
- 作为前端工程师,直接把用户提交的字符串转化成了DOM。
XSS的一些特点
- 通常难以从UI上感知,因为攻击者实际上是在暗地执行恶意脚本
- 窃取用户信息(cookie/token)
- 绘制UI(例如弹窗),诱骗用户点击/填写表单
demo
submit接口:用户提交内容到服务端;服务端从用户提交的内容中读取content字段并存入数据库中。
render接口:渲染页面,服务端从数据库中读取content字段并返回给客户端即浏览器用于生成HTML。
public async submit(ctx) {
const { content, id } = ctx.request.body;
//没有对content 过滤
await db.save({
content,
id
});
}
public async render(ctx) {
const { content } = await db.query({
id: ctx.query.id
});
//没有对content 过滤
ctx.body = `<div>${content}</div>`;
}
两个接口,不论是读数据还是写数据,都没有对用户提交的内容进行过滤,攻击者可以直接提交恶意脚本:如下,直接提交一个script标签作为content字段到服务器端,当渲染HTML时,就会直接插入一个script标签,这样就完成了一次XSS攻击。
//提交时候
fetch("/submit",{
body: JSON.stringify({
id: "1",
content: `<script>alert("xss");</script>`
});
});
ctx.body = `
<div>
<script>alert("xss");</script>
</div>`;
分类
Stored XSS 存储型
恶意脚本会被存在数据库中。当用户访问页面时,就会去服务端读数据。然后写数据给浏览器。只要完成这个链路,用户就会被攻击。这种XSS类型危害是最大的,因为对所有用户都有效。
Reflected XSS 反射型
这种攻击不涉及数据库,而是直接从URL上进行攻击。
demo
URL:
host/path/?param=<script>alert('123')</script>
服务器代码:服务器会从用户请求的query中读取字段并将这个字段生成HTML片段。
public async render(ctx) {
const { param } = ctx.query;
ctx.status = 200;
ctx.body = `<div>${param}</div>`;
}
如果攻击者把恶意构造的一个script标签作为字段,当用户访问页面的时候,就会命中这种XSS攻击。
DOM-based XSS
这种攻击完全不需要服务器的参与,恶意攻击的发起和执行全在浏览器完成。
demo
URL同上;
浏览器:从URL的searchParam中读取指定的参数,然后创建一个HTML标签,把参数对应的值写到HTML标签的innerHTML属性中,再插入具体的body中。
const content = new URL(location.href).searchParams.get("param");
const div = document.createElement("div");
//恶意脚本注入
div.innerHTML = content;
document.body.append(div);
Mutation-based XSS
利用了浏览器渲染DOM的特性(独特优化)。不同浏览器,会有区别(按浏览器进行攻击)
demo
很多XSS过滤工具中会把这段title属性当做很正常的字符串:
<noscript><p title="</noscript><img src=x onerror=alert(1)>">
Chrome渲染结果:
因为src的属性不符合规范,所以会触发onerror事件。onerror回调一触发就完成了XSS攻击。
Cross-site request forgery(CSRF) 跨站伪造请求
特点
- 在用户不知情的前提下
- 利用用户权限(cookie)
- 构造指定 HTTP 请求,窃取或修改用户敏感信息
demo
假如用户收到了一封email,email中有一个链接,当用户点击后就访问了一个恶意页面,而在这个恶意页面中攻击者构造了一个HTTP请求,比如向银行发起一个转账请求,且这个请求是在另外的一个域名。假如银行的服务器接受到了这个请求,发现请求上有用户的cookie并且验证成功了,银行服务器会认为这是一个合法的请求,并执行转账接口,完成转账,返回结果。用户就这样平白无故的丢失了一笔财产。
Injection 注入
SQL Injection
在输入的字符串中注入SQL指令,在设计不良的程序当中忽略了字符检查,那么这些注入进去的恶意指令就会被数据库服务器误认为是正常的SQL指令而执行,因此遭到破坏或是入侵。
流程
demo
读取请求字段,直接以字符串的形式拼接SQL语句
public async renderForm(ctx) {
const { username,form_id } = ctx.query;
const result = await sql.query(`
SELECT a, b,c FROM table
WHERE username = ${username}
AND form_id = ${form_id}
`);
ctx. body = renderForm( result );
}
攻击者:
fetch("/api", {
method: "POST",
headers: {
"Content-Type": "applicaiton/json"
},
body: JSON.stringify({
username: "any; DROP TABLE table;",
})
})
服务器执行:
SELECT XXX FROM XXX; DROP TABLE table;
删库跑路成就达成✅
Injection 不止于 SQL
- CLI 命令行
- OS command 系统命令
- Server-Side Request Forgery(SSRF) 服务端伪造请求
- 严格来说,SSRF不是Injection,但是原理类似
demo
调用convert-cli进行视频转换,接受用户传入的options作为参数,没有进行任何过滤:
public async convertVideo(ctx) {
const { video, options } = ctx.request.body;
exec(`convert-cli ${video} -o ${options}`);
ctx.body = "ok";
}
攻击者:传入系统命令如rm语句:
fetch("/api", {
method:"POST",
body: JSON.stringify({
options: `' && rm -rf xx×`
})
});
当服务器端执行命令时,就会将服务器端的若干文件删除,可能导致服务器直接宕机:
const command = `convert-cli video - o && rm -rf xxx`
Denial of Service(DoS) 服务拒绝
攻击者通过某种方式(构造特定请求),导致服务器资源被显著消耗,来不及响应更多请求,导致请求挤压,进而雪崩效应。
ReDoS 基于正则表达式的DoS
攻击者利用正则表达式贪婪匹配的模式=>n次不行?n-1次再试试?从而产生回溯,造成服务器响应时间变长,接口吞吐量下降。
Distributed DoS(DDoS)
短时间内,来自大量僵尸设备的请求流量,导致服务器不能及时完成全部请求,导致请求堆积,进而雪崩效应,无法响应新请求。
攻击特点
- 直接访问 IP
- 任意 API,不会区分接口
- 消耗大量带宽(耗尽)
DDoS demo
SYN Flood 洪水攻击:
TCP 有三次握手,攻击者会构造大量 TCP 请求,给服务器发送大量的 SYN,服务器会按照规范,返回 ACK + SYN,但攻击者此时不会返回 ACK,导致三次握手没有完成,connection 不能被释放,很快达到最大连接数后,所有的新请求都不能被响应,这样就完成了一次洪水攻击。
基于传输层的攻击
中间人攻击
浏览器觉得自己在和服务器进行交互,服务器又觉得自己在给浏览器访问数据,实际上他们都是在和一个中间人进行沟通。中间人可以窃取信息,可以修改返回结果。中间人可以是一个恶意的浏览器,可以是一个便宜的路由器,甚至是一些运行商。
中间人可以进行攻击的原因:
- 明文传输
- 信息被篡改时服务器和浏览器不可知
- 服务器和浏览器未验证对方身份
防御篇
XSS 防御
永远不要信任用户提交的内容!
不要将用户提交的内容直接转换成DOM!
工具
前端
- 主流框架默认防御 XSS
- google-cLosure-library
服务端(Node)
- DOMPurify
如果用户需求必须动态生成DOM:
string -> DOM
如果要把string直接生成DOM,先对string进行转义。
上传svg
如果允许用户上传svg文件如图片,对svg文件进行扫描。
自定义跳转链接
如果允许用户自定义跳转链接,一定要做好过滤
自定义样式
如果允许用户自定义样式,一定要额外留意能设置URL如背景图片、字体文件的地方。
Content Security Policy(CSP) 内容安全策略
CSP可以让开发者自己规定哪些源(域名)是安全的、来自安全源的脚步可以执行,否则直接抛错。此外也可以直接拒绝 eval 或其他内联的script标签,直接抛错。
demo
服务器的响应头部设置方式:
//这个页面中,任何script标签,必须是同源才允许执行。
Content-Security-Policy: script-src 'self'
//在同源之外,还允许了另一个域名的script执行
Content-Security-Policy: script-src 'self' https://domain.com
除此之外的其他script标签都不可以执行。
浏览器 meta 标签设置方式:
<meta http-equiv="Content-Security-Policy" content="script-src self">
CSRF 的防御
限制请求来源
通过限制请求来源来限制攻击。如,服务器端的开发人员,可以针对接口请求,对 Origin 和请求的 Referer 进行校验,如果不是自己的域名就拒绝请求。
token防御
客户端向服务端发出一个页面的请求。
服务端进行校验,校验成功会生成 token, 把 token 发送给客户端并返回页面。
客户端自己保存 token, 再次请求就要在 Http 协议的请求头中带着 token 去访问服务端,和在服务端保存的 token 信息进行比对校验。
注: token往往需要和具体用户进行绑定;并且有过期时间。
SameSite Cookie 避免用户信息被携带
SameSite属性,它用来标明这个 Cookie是个“同站 Cookie”,同站Cookie只能作为第一方Cookie,不能作为第三方Cookie
依赖 Cookie 的第三方服务:
Set-Cookie: Samesite=None;Secure;
在任何站点都可以被携带。如果 SameSite=None,那么必须指定 Secure 属性,否则会无法写入。
Secure 属性表示当前 Cookie 只能在 HTTPS 的请求中写入和携带。
Injection 防止注入
找到项目中查询SQL的地方,使用 prepared statement 提前编译 SQL 语句。
其他方式
最小权限原则
所有的命令都不要通过 sudo 来执行,不要赋予 root 权限。
建立允许名单 + 过滤
建立白名单机制,只允许指定命令执行,拒绝 rm 等高危操作。
对 URL 类型参数进行协议、域名、ip 等限制
避免攻击者访问到内网资源造成内网瘫痪。
防御 DoS
- 完善代码 review 的工作,避免写出贪婪匹配的方式,特别是在接口处理相关操作的时候;
- 使用代码扫描工具,扫描代码仓库中存在的正则表达式并做好规整,对这些正则进行一些性能测试;
- 直接拒绝使用用户提供的正则。
DDoS 防御
流量治理:
在负载均衡这一层或者 API 网关层进行一些流量的识别,然后把能够识别出来的一些恶意攻击进行过滤,无论是转到其他服务也好,或者直接拒绝也好,都是不同的策略。
快速自动扩容:
当服务器检测到流量激增的时候,我们能够有一个方案,自动的执行快速扩容的操作,以便承载更多的流量。
非核心服务降级
所有的非核心的业务功能通通降级,这样的话,我们能够匀出更多的计算资源去应对激增的流量。
传输层——防御中间人
HTTPS 的一些特性
可靠性:加密(避免明文传输)
Https的过程,或者说TLS握手分为非对称加密和对称加密两个大过程。非对称加密过程,浏览器会先把自己支持的加密套件选项传给服务端,服务端接收这个可支持的套件选项之后,会选一个具体的套件,并且把服务器端的证书返回给浏览器侧,浏览器侧会先对证书进行校验,如果证书校验通过,那么双方会根据之前协商好的加密算法以及一些额外的随机数等,算出一个sessionKey,此时非对称加密的过程结束。之后进入对称加密,对称加密的过程,就是双方使用这个 sessionKey 对所有的信息进行对称加密,所有传输的信息都是加密后的信息,这也就是一次完整的 TLS 握手以及非对称加密和对称加密的过程。
完整性:MAC 验证(确保信息不会被篡改)
所有传输的信息除了有加密的信息之外,还会额外传递一个加密信息对应的hash,也就是哈希值。所有的接收方会对加密信息重新进行一次哈希计算,会和传递过来的哈希值进行对比。如果两者相等,说明没有被篡改;如果不相等,说明信息被篡改。这就是完整性的确保方案。
不可抵赖性:数字签名(确保双方身份可被信任)
存在一个CA,也就是 Certificate Authority 证书机构,会完成相关的一些签名的工作,比如说服务提供方,会把他的一些原信息以及一些公钥合并成一段信息,然后使用 CA 提供的一对私钥进行签名,生成真正的服务器端保存的证书。这个证书,就会传递给浏览器侧,浏览器侧,会使用 CA 颁发的公钥对这个证书进行验。如果校验通过,说明证书合法,也就是说证书的签发者身份是可信的。这就完成了一次数字签名校验流程。