从两个角度来学习web安全
攻击篇
Cross-Site Scripting(XSS)
跨站脚本攻击,在页面中注入恶意脚本(如script标签),当用户访问页面时,恶意脚本会被执行,完成攻击。
XSS攻击的原理:
- 开发者盲目信任用户的提交内容
- 开发者直接将用户提交的string转换为DOM
XSS的特点:
- 难以从UI上感知
- 会窃取用户信息(cookie/token)
- 绘制UI,诱导用户点击/填写表单
XSS攻击示例
服务端接口:
// 从用户提交的内容中提取字段存储到数据库中
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>'
}
攻击者可以直接提交恶意脚本:
// 提交
fetch("/submit", {
body: JSON.stringfy({
id: "1",
content: `<script>alert("xss")</script>`
});
});
// 渲染
ctx.body = `
<div>
<script>alert("xss")</script>
</div>`;
XSS的分类
Stored XSS
存储型XSS攻击:
- 恶意脚本被存储在数据库中
- 访问页面时读数据导致被攻击
- 危害最大,对全部用户可见
Reflected XSS
反射型XSS攻击:
- 不涉及数据库
- 从URL上进行攻击
反射型XSS攻击示例: URL中包含query参数,服务端从URL中读取字段并生成HTML片段。 攻击者可以通过修改query参数,生成恶意的HTML标签,从而在用户访问页面时命中XSS攻击
URL:
host/path/?param=<script>alert("xss")</script>
服务端代码:
public async render(ctx) {
const { param } = ctx.query;
ctx.status = 200;
ctx.body = `<div>${param}</div>`;
}
DOM-based XSS
基于DOM的XSS攻击:
- 不需要服务器的参与
- 恶意攻击的发起及执行,均在浏览器完成
URL:
host/path/?param=<script>alert("xss")</script>
浏览器中代码:
// 获取param参数
const content = new URL(location.href).searchParams.get("param");
const div = document.creatElement("div");
// 注入恶意脚本
div.innerHTML = content;
document.body.append(div);
基于DOM的XSS攻击与反射型XSS攻击的区别在于完成注入脚本的地方:
Mutation-based XSS
基于区别的XSS攻击:
- 利用了浏览器渲染DOM的特性(独特优化)
- 不同的浏览器存在区别(按浏览器进行攻击)
基于区别的XSS攻击示例:
noscript标签:
<noscript><p title="</noscript><img src=x onerror=alert(1)>">
Chrome浏览器渲染HTML片段:
<div>
<noscript><p title="</noscript> // </noscript>会关闭noscript标签
<img src="x" onerror="alert(1)"> // img被视为标签,其src属性不合法,进而触发onerror事件回调,完成XSS攻击
"">"
</div>
Cross-Site request forgery(CSRF)
跨站伪造请求:
- 在用户不知情的前提下
- 利用用户权限(cookie)
- 构造指定HTTP请求,窃取或修改用户敏感信息
CSRF攻击示例:
过程中:
- 用户没有访问银行页面
- 银行页面中的特定接口被请求
- 利用用户cookie,请求执行成功
CSRF的分类
GET请求
示例:
<!-- 点击链接时触发 -->
<a href="http://bank.com/transfer?to=hacker&amount=100">您中奖了</a>
<!-- 加载img时触发 -->
<img style="display:none;" src="http://bank.com/transfer?to=hacker&amount=100"/>
beyond GET请求
示例:
<!-- 为表单设置POST方法 -->
<form action="https://bank/transfer_tons_of_money" method="POST">
<!-- 为input标签设置hidden属性 -->
<input name="amount" value="1000000" type="hidden"/>
<input name="to" value="hacker" type="hidden"/>
</form>
Injection
注入攻击,最常见的注入攻击:SQL Injection
SQL注入攻击流程:
- 在HTTP请求上恶意注入SQL参数
- 请求到达服务器端,服务器端从请求上读取参数得到SQL语句并运行
- SQL运行结果可能是攻击者获取其他数据、修改数据、删除数据等
SQL注入示例:
服务器代码:
public async renderForm(ctx) {
// 1. 读取请求字段
const { username, form_id } = ctx.query;
// 2. 直接以字符串的形式拼接SQL语句
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
})
})
其他类型的Injection
- CLI,命令行
- OS Command,系统命令
- Server-Side Request Forgery(SSRF),服务端伪造请求
- 严格而言,SSRF不是注入攻击,但原理类似
系统命令Injection示例:
Injection执行删除
服务端代码:
// 用于转换视频,接受options参数
public asnc converVideo(ctx) {
const { video, options } = ctx.request.body;
exec(`convert-cil ${video} -o ${options}`);
ctx.body = "ok";
}
攻击者代码:
fetch("/api", {
method: "POST",
body: JSON.stringfy({
options: `' && rm -rf xxx` // 传入rm系统命令
})
});
const command = `convert-cil video -o && rm -rf xxx`
Injection读取+修改
当可以为攻击者执行任何语句时,攻击者就可以读或修改服务器上的任何文件,这是十分危险的。 常见的重要文件有:
- /etc/passwd
- /etc/shadow
- ~/.ssh
- /etc/apache2/httpd.conf
- etc/nginx/nginx.conf
- 通过修改Nginx配置可以将流量转发到真实第三方
- 第三方可能会承受不了新流量而服务挂掉
SSRF示例
服务端伪造请求:
- 请求用户自定义的callback URL
- web server通常有内网访问权限
public async webhook(ctx) {
// callback可能是内网url,访问callback会暴露内网信息
ctx.body = await fetch(ctx.query.callback);
}
Denial of Service(DoS)
攻击者通过某种方式(构造特定请求),导致服务器资源被显著消耗,来不及响应更多请求,导致请求挤压,进而雪崩效应。
ReDoS:基于正则表达式的DoS
Distributed DoS(DDoS)
短时间内,来自大量僵尸设备的请求流量,服务器不能及时完成全部请求,导致请求堆积,进而雪崩效应,无法响应新请求。
DDoS攻击特点:
- 直接访问IP
- 任意API
- 消耗大量带宽(耗尽)
DDoS攻击示例:洪水攻击
攻击者发送大量TCP请求,而不返回ACK,导致三次握手无法完成,连接无法释放,从而达到最大连接数
基于传输层的攻击方式:中间人攻击
中间人攻击的原理:
- 明文传输
- 信息篡改不可知
- 对方身份未验证
防御篇
XSS的防御
针对XSS攻击的防御策略:
- 不要相信用户提交的内容
- 不要将用户提交的内容转换为DOM
对于必须要动态生成DOM的需求:
- string生成DOM时要对string进行转译
- 对SVG文件要进行扫描,SVG中可以包含
<script></script>标签 - 对于用户自定义跳转行为要做好过滤,自定义跳转可以传递JS代码
- 对于自定义样式的地方,要注意可以设置URL的位置
Content Security Policy(CSP)
同源策略(Same-origin Policy, SoP):同源是指协议、域名、端口都相同。HTTP通常对于同源的请求可行,对跨域的请求要看服务器的配置。
内容安全策略:
- 定义一些认为是安全的源
- 来自安全源的脚本可以执行,否则直接抛出错误
- 对于eval和内联的script标签直接报错
CSP示例: 服务器的响应头部
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">
CSRF的防御
跨站伪造请求,可以针对异常来源来防御,限制请求来源,从而限制伪造请求。
请求头部校验
可以对请求头部进行校验:
- Origin
- 同源请求中,GET和HEAD不发送Origin字段
- Referer(常用)
其他判断请求来源于合法来源的方式:
token防御机制:
其中:
- token要进行用户绑定,因为攻击者也可以使注册用户,拥有自己的token
- token要有过期时间(前向保密),防止用户token泄露带来危害
CSRF的iframe攻击
攻击者构建页面,在其中使用iframe指向合法页面,并在iframe上覆盖button并设置穿透属性,从而通过iframe实现同源请求:
防御方式:为页面设置X-Frame-Options: DENY/SAMEORIGIN响应头部,拒绝或只允许同源页面通过iframe访问页面。
SameSite Cookie:避免用户信息被携带
只对自己的cookie有响应
CSRF防御中间件
CSRF的攻击方式及防御方式过多,不能case by case的进行防御,应该使用中间件,生成各种CSRF的防御策略。
Injection的防御
针对Injection的防御思路:
- 找到代码中查询SQL的位置
- 使用prepared statement,将SQL语句进行提前编译
其他非SQL注入攻击的防御措施:
- 最小权限原则,所有的命令不要通过
sudo来执行,不给予root权限 - 建立允许名单 + 过滤,只允许执行指定命令,拒绝
rm等高危操作 - 对URL类型参数进行协议、域名、IP等限制,避免访问内网
DOS的防御
Regex Dos防御:
- 避免使用贪婪匹配
- 使用代码扫描工具,查找存在的正则表达式,并进行性能基础测试
- 拒绝使用用户提供的正则表达式
DDoS的防御:
- 流量治理
- 负载均衡,过滤策略
- API网关,过滤策略
- 前置CDN,抗量策略
- 快速自动扩容,抗量策略
- 非核心服务降级,抗量策略
传输层的防御
防御中间人:使用HTTPS。 HTTPS的特点:
- 可靠性:加密,禁止明文传输
- 完整性:MAC验证,确保信息未被篡改
- 不可抵赖性:数字签名,验证双方身份