Web开发安全 —— 攻击篇
这是我参与第五届青训营伴学笔记创作活动的第 5 天,记录了 Web 攻击的一些常见手段。
一、Cross-Site Scripting(XSS)(跨站脚本攻击)
不良用户在网页中注入恶意脚本,危害用户安全
1. 产生原因:
- 开发者盲目信任用户的提交内容
- 前端工程师直接把用户提交的字符串转化成了DOM(string -> DOM)
- document.write
- element.innerHTML = anyString;
- SSR(user_data) // 伪代码
2. XSS的一些特点
- 通常难以从UI上感知(暗地执行脚本)
- 窃取用户信息(cookie/token)
- 绘制UI(例如弹窗),诱骗用户点击/填写表单
例:
public async submit(ctx) {
const { content, id } = ctx.request.body;
// 没有对 content 过滤,直接存储用户数据
await db.sava({
content,
id
});
}
public async render(ctx) {
const { content } = await db.query({
id: ctx.query.id
});
// 没有对 content 过滤,直接转化为DOM,极易发生攻击
ctx.body = `<div>${content}<div>`
}
没有对用户提交的数据进行过滤,可以直接提交恶意脚本:
// 提交时
fetch("/submit", {
body: JSON.stringfy({
id: "1",
// 注入脚本
content: `<script>alert("xss");</script>`
});
});
// 导致结果
ctx.body = `
<div>
<script>alert("xss");</script>// 被添加执行恶意脚本
</div>`;
3. Stored XSS(存储型XSS攻击)
- 恶意脚本被存在数据库中
- 访问页面 -> 读数据 === 被攻击
- 危害最大,对全部用户可见
例:网站某内容具有恶意脚本,所有查看后的用户都会泄露个人信息
4. Reflected XSS(反射性XSS攻击)
- 不涉及数据库
- 从URL上攻击
例:假如我们的URL格式为:host/path/?param=<script>alert('123')</script>,js代码如下
public async render(ctx) {
const { param } = ctx.query;
ctx.status = 200;
ctx.body = `<div>${param}</div>`; // 注入了我们的param=<script>alert('123')</script>
}
5. DOM-based XSS(基于DOM的XSS攻击)
- 不需要服务器的参与
- 恶意攻击的发起 + 执行,全在浏览器完成
还是上面的例子,假如我们的URL格式为:host/path/?param=<script>alert('123')</script>,js代码如下
// content获取到了param=<script>alert('123')</script>
const content = new URL(location.href).searchParams.get("param");
const div = document.createElement("div");
// 恶意脚本注入
div.innerHTML = content;
document.body.append(div);
6. Reflected vs DOM-based
完成注入脚本的地方:
- Reflected在服务端
- DOM-based在浏览器直接执行
7. Mutation-based XSS
- 利用了浏览器渲染DOM的特性(独特优化)
- 不同浏览器,会有区别(按浏览器进行区别攻击)
- 最难防御
例:
// 过滤工具看过后,发现是平平无奇的title属性
<noscript><p title="</noscript><img src=x onerror=alert(1)>">
// 但是渲染后,会变成下面这样
// onerror会自动触发,因为图片src错误,发生了脚本攻击
<div>
<noscript><p title="</noscript>
<img src="x" onerror=alert(1)">
"">"
</div>
二、Cross-site request forgery(CSRF)(跨站伪造请求)
- 在用户不知情的前提下
- 利用用户权限(cookie)
- 构造指定 HTTP 请求,窃取或修改用户敏感信息
例:当用户点击了恶意链接访问恶意页面,自动用该用户的信息去发送请求,比如向银行发送请求,由于能够通过盗取cookie伪装成当前用户,银行认为请求合法并执行请求,那么你的钱就可能不翼而飞了。
1. GET请求
<a href="get链接">点我抽奖</a>
<img style="display:none" src="get链接" />
2. POST请求
用表单构造跨站伪造请求
<form action="post链接" method="POST">
<input name="account" value="100000000" type="hidden">
<input name="to" value="hacker" type="hidden">
</form>
三、Injection(注入攻击)
1. SQL Injection
- 首先有一个HTTP请求,SQL参数附在请求上,服务器解析请求并运行SQL
- 恶意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": "application/json"
},
body: JSON.stringfy({
username: "any; DROP TABLE table;",
})
})
// 那么,恭喜你,你的服务器处理后SQL变成了这个样子
SELECT XXX FROM XXX; DROP TABLE table;
// 达成删库跑路成就
2. Injection 不止于 SQL
- CLI(命令行)
- OS command(系统命令)
- Server-Side Request Forgery(SSRF),服务端伪造请求
- 严格而言,SSRF 不是 injection,但是原理类似
容易删除掉重要文件产生严重后果!:
// 服务端
public async convertVideo(ctx) {
const { video, options } = ctx.request.body;
exec(`convert-cli ${video} -o ${options}`);
ctx.body = "ok";
}
// 请求
fetch("/api", {
method: "POST",
body: JSON.stringfy({
options: `' && rm -rf xxx`
})
});
// 转化后,会删除服务端若干信息,可能宕机
const command = `convert-cli video - o && rm -rf xxx`
还会把重要文件暴露,供攻击者读取、修改
- /etc/passwd
- /etc/shadow
- ~/.ssh
- /etc/apache2/httpd.conf
- /etc/nginx/nginx.conf
如果nginx代理被修改了:
location / {
proxy_pass https://hacker.com;
}
那么会产生重大影响,因为流量的位置已经不受原本方控制了,可以设想以下场景
- 流量转发到真实第三方
- 第三方扛不住新增流量
- 第三方服务挂掉
- 竞争对手被无情消灭
SSRF例子:
- 请求【用户自定义】的callback URL
- web server通常有内网访问权限
public async webhook(ctx) {
// callback 可能是内网url
// e.g http://secret.com/get_employ_payrolls
ctx.body = await fetch(ctx.query.callback);
}
四、Denial of Service(Dos)
通过某种方式(构造特定请求),导致服务器资源被显著消耗,来不及响应更多请求,导致请求挤压,进而雪崩效应。
1. 正则表达式——贪婪模式
重复匹配时one? vs no?:满足“一个”即可 vs 尽量多
const greedyRegExp = /a+/; // 有多少匹配多少
const nonGreedyRegExp = /a+?/ // 有一个就行
const str = "aaaaaa";
console.log(str.match(greedyRegExp)[0]); // "aaaaaa"
console.log(str.match(nonGreedyRegExp)[0]); // "a"
2. ReDos:基于正则表达式的Dos
贪婪搜索:n次匹配不上,那么n - 1次再试试 -> 回溯
例:匹配ab,后面加上一个a让正则表达式失效,那么会一直尝试减少ab的数量来查看能否匹配
我们发现会不断地发生回溯,直至不可再分
那么攻击者就可以利用这一点进行攻击,加入我们服务端有一个贪婪式字符串,攻击者写一个容易发生回溯行为的字符串,那么就会不断占用服务器,造成接口吞吐量下降,用户的响应时间延长。
3. Distributed DoS(DDoS)
最常见,短时间内,来自大量僵尸设备的请求流量,服务器不能及时完成全部请求,导致请求堆积,进而雪崩效应,无法响应新请求。
简单来说,不搞复杂的,量大就完事了
不会限制在域名访问,直接访问IP,不区分借口,主要目的是用来消耗带宽
- 耗时的同步操作
- 数据库写入
- SQL join
- 文件备份
- 循环执行逻辑
典型的洪水攻击:
发送大量的TCP请求,TCP的三次握手,但是攻击者不会返回第三次ACK,导致三次握手未完成,连接数不能被释放,一段时间即会到达最大连接数,拒绝新请求
四、传输层攻击——中间人攻击
浏览器和服务器之间接入第三方中间人,浏览器认为自己和服务器通信,服务器认为自己和浏览器通信,但其实它们的通信都被中间人所监视查看。
为什么中间人这种方式可能发生呢?
- 所有传输都是明文传输
- 传输信息被篡改服务器浏览器不可知
- 服务器浏览器未对对方身份进行验证
五、参考
- 字节录播课 - 《Web 开发安全 - 攻击篇》