Web开发安全 —— 攻击篇 | 青训营笔记

38 阅读5分钟

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在浏览器直接执行

2.png

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

  1. 首先有一个HTTP请求,SQL参数附在请求上,服务器解析请求并运行SQL
  2. 恶意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.png

我们发现会不断地发生回溯,直至不可再分

那么攻击者就可以利用这一点进行攻击,加入我们服务端有一个贪婪式字符串,攻击者写一个容易发生回溯行为的字符串,那么就会不断占用服务器,造成接口吞吐量下降,用户的响应时间延长。

3. Distributed DoS(DDoS)

最常见,短时间内,来自大量僵尸设备的请求流量,服务器不能及时完成全部请求,导致请求堆积,进而雪崩效应,无法响应新请求。

简单来说,不搞复杂的,量大就完事了

不会限制在域名访问,直接访问IP,不区分借口,主要目的是用来消耗带宽

  • 耗时的同步操作
  • 数据库写入
  • SQL join
  • 文件备份
  • 循环执行逻辑

典型的洪水攻击

发送大量的TCP请求,TCP的三次握手,但是攻击者不会返回第三次ACK,导致三次握手未完成,连接数不能被释放,一段时间即会到达最大连接数,拒绝新请求

4.png

四、传输层攻击——中间人攻击

浏览器和服务器之间接入第三方中间人,浏览器认为自己和服务器通信,服务器认为自己和浏览器通信,但其实它们的通信都被中间人所监视查看。

5.png

为什么中间人这种方式可能发生呢?

  • 所有传输都是明文传输
  • 传输信息被篡改服务器浏览器不可知
  • 服务器浏览器未对对方身份进行验证

五、参考

  • 字节录播课 - 《Web 开发安全 - 攻击篇》