Web开发安全
使用的依赖(npm package, 甚至是NodeJS)可能成为最薄弱的一环
npm install除了带来了黑洞, 还可以带来漏洞
攻击篇
Cross-Site Scripting(跨站脚本攻击)-XSS
主要利用
特点
- 通常难以从 UI 上感知(暗地执行脚本)
- 窃取用户信息(cookie/token)
- 绘制 UI(例如弹窗),诱骗用户点击/填写表单
例子
XSS demo
//写数据
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>`;
}
攻击者可以直接提交恶意脚本
分类
Stored XSS(存储型XSS)
-
恶意脚本被存在数据库中
-
访问网页=>读数据=>被攻击
-
危害最大,对全部用户可见
Reflected XSS(反射型XSS)
-
不涉及数据库
-
从URL上攻击
恶意修改要匹配的字段 点击即命中
DOM-based XSS(DOM型XSS)
-
不需要服务器的参与
-
恶意攻击的发起+执行 全在浏览器完成
Reflected VS DOM-based
-
完成注入脚本的地方不同
Mutation-based XSS(mXSS)
是一种特殊类型的跨站脚本攻击,它利用了浏览器在解析和渲染HTML时的行为变化。在这种情况下,即使输入数据在服务器端被清洗和验证为安全,但在客户端渲染时,浏览器可能会以意想不到的方式改变HTML结构,从而产生安全漏洞
-
利用了浏览器渲染DOM的特性(独特优化)
-
不同浏览器会有区别(按浏览器进行区别攻击)
-
最难防御
CroCross-site request forgery(跨站伪造请求)-CSRF
特点
-
在用户不知情的前提下
-
利用用户权限(cookie)
-
构造指定 HTTP 请求,窃取或修改用户敏感信息
iframe攻击
同源请求
点击button会先点击到iframe
CSRF-GET请求
一点击就会完成攻击
<a href="https://bank.com/transfer?to=hacker&amount=100">点我抽奖</a>
图片一加载就会完成攻击
<img style="display:none;" src="https://bank.com/transfer?to=hacker&amount=100"/>
SQL Injection(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); }
Injection不止于SQL
CLI
public async convertVideo(ctx) {
const { video, options } = ctx.request.body;
exec(`convert-cli ${video} -o ${options}`);
ctx.body = "ok";
}
OS command
Server-Side Request Forgery(服务端伪造请求)-SSRF
严格而言,SSRF不是injection,但是原理类似
-
请求[用户自定义]的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); }访问 callback === 暴露内网信息
Denial of Service(拒绝服务攻击)-DoS
通过某种方式(构造特定请求),导致服务器资源被显著消耗,来不及响应更多请求,导致请求挤压,进而雪崩效应。
正则表达式-贪婪模式
重复匹配时「?」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"
ReDoS: 基于正则表达式的DoS
贪婪:n次不行?n-1次再试试?-- 回溯
响应时间+
接口吞吐量-
Distributed DoS(DDoS)
短时间内,来自大量僵尸设备的请求流量,服务器不能及时完成全部请求,导致请求堆积,进而雪崩效应,无法响应新请求。
DoS特点
- 耗时的同步操作
- 数据库写入
- SQL join
- 文件备份
- 循环执行逻辑
例子:洪水攻击
基于传输层的攻击
中间人攻击
防御篇
XSS的防御
-
永远不信任用户的提交内
-
不要将用户提交内容直接转换成DOM
防御XSS的现成工具
前端
- 主流框架默认防御XSS
- google-closure-library
服务端(Node)
- DOMPurify(npm包)
【用户需求】必须动态生成DOM
string->DOM
new DOMParser();
上传svg
<svg>
<script>alert("xss");</script>
</svg>
尽量不要让用户进行自定义跳转操作 Bolb动态生成script
const blob = new Blob(
[script],
{ type: "text/javascript"},
);
const url= new URL.create0bjectURL(blob);
const script = document.createElement("script");
script.src = url;
自定义样式
Same-origin Policy(同源策略)
源(origin)由协议(如HTTP或HTTPS)、域名(或IP地址)和端口号组成。只有当这三个都相同时,才被认为是“同源
1/2协议不同 2/3域名不同
HTTP请求:同源 OK 跨域 NO(要看请求的类型以及服务器配置)
Content Security Policy(内容安全策略)-CSP
- 允许开发者定义哪些源(域名)被认为是安全的
- 来自安全源的脚本可以执行,否则直接抛错
- 对 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">
CSRF的防御
-
if 伪造请求===异常来源
then 限制请求来源 → 限制伪造请求
-
请求头部
-
Origin
- 同源请求中,GET+ HEAD 不发送
-
Referer
-
token防御机制
除了Origin + Referrer,其他判断【请求来自于合法来源】的方式: 先有页面,后有请求
if(请求来自合法页面)
then(服务器接收过页面请求)
then(服务器可以标识)
- 用户绑定:攻击者也可以是注册用户===可以获取自己的token
- 设置过期时间
iframe的防御
设置X-Frame-Options响应头部:DENY/SAMEORIGIN
CSRF anti-pattern(CSRF防御反模式)
GET !== GET + POST
//将更新+获取逻辑放到同一个GET接口 应该把各个接口按照职责划分开
public async getAndUpdate(ctx) {
const { update, id } = ctx.query;
if (update) {
await this.update(update);
}
ctx.body = await this.get(id);
}
SameSite Cookie(避免用户信息被携带
依赖于Cookie的第三方服务怎么办?
eg.内嵌一个某站播放器,识别不了用户登录态,发不了弹幕
Set-Cookie: SameSite=None; Secure;
SameSite vs CORS
-
SameSite
- Cookie 发送
- domain vs 页面域名
- “我跟你说个事儿,出这屋我可就不认了"
-
CORS
- 资源读写(HTTP 请求)
- 资源域名 vs 页面域
- 白名单
防御CSRF的正确姿势
Injection的防御
SQL
- 找到项目中查询 SQL 的地方
- 使用 prepared statement
PREPARE q FROM 'SELECT user FROM users WHERE gender = ?';
SET @gender = 'female';
EXECUTE q USING @gender;
DEALLOCATE PREPARE q;
除SQL外其他的注入攻击
-
最小权限原则
- 所有的命令都不要通过sudo来执行,不要给root权限
-
建立允许名单 +过滤
- 只允许指定命令执行,禁止rm操作
-
对 URL 类型参数进行协议、域名、ip 等限制,避免攻击者访问内网资源
DoS的防御
Regex DoS
- 完善代码Review的工作(避免写出贪婪匹配的方式/(ab*)+/)
- 代码扫描 +正则性能测试
- 拒绝使用用户提供的使用正则
DDoS
-
流量治理
- 负载均衡 过滤
- API 网关 过滤
- CDN 抗量
-
快速自动扩容 抗量
-
非核心服务降级 抗量
传输层的防御
防御中间人
HTTPS的特性
-
可靠性
- 加密 避免了明文传输
-
完整性
- MAC验证规则 确保信息不被篡改
-
不可抵赖性
- 数字签名 确保了双方身份是可被信任的
HTTPS的大致流程
-
证书
-
当签名算法不够健壮时:
HTTP Strict-Transport-Security(将HTTP 主动升级到 HTTPS)-HSTS
- 需要先有一次HTTPS请求
Subresource Integrity(SRI)
-
CDN:静态资源托管的地方
-
静态资源被劫持篡改怎么办-SRI
-
例子
-
标签hash(原始内容hash)
<script src="https://example/app.js" integrity="sha384-{some-hash-value}" crossorigin="anonymous"></script> -
实际内容hash
const remoteHash = hash(content); if (tagHash !== remoteHash) { throw new Error("wrong hash"); }
-
补充:Feature Policy(特性策略)/Permission Policy(权限策略)
是Web开发中的两个相关概念,它们用于控制和限制网页可以访问的浏览器API和特性。
Feature Policy
Feature Policy允许开发者声明哪些源(页面)(origins)可以使用特定的API和浏览器特性。这通过在HTTP头部中设置Feature-Policy字段来实现。
例如,不希望页面使用全屏API,可以设置如下策略:
Feature-Policy: fullscreen 'none';
这告诉浏览器,不允许任何源使用全屏API。
可以通过指定不同的值来允许某些源使用特定的特性:
'self':仅允许当前源使用特性。'none':不允许任何源使用特性。- 指定的域名,如
example.com:允许指定的域名使用特性。 '*':允许所有源使用特性。
Permission Policy
Permission Policy(有时称为Permissions Policy)是Feature Policy的一个子集,它专注于控制Web页面请求权限的能力。这些权限包括访问地理位置、摄像头、麦克风等。Permission Policy也通过HTTP头部实现,但使用Permissions-Policy字段。
例如,不希望页面访问用户的麦克风,可以设置如下策略:
Permissions-Policy: microphone=();
或者,允许当前源访问麦克风,但不允许其他源访问:
Permissions-Policy: microphone=(self);
共同点和区别
- 共同点:Feature Policy和Permission Policy都提供了一种机制,允许开发者限制页面对某些API和特性的访问。
- 区别:Feature Policy更广泛,包括了浏览器特性和API的控制,而Permission Policy专注于权限控制。
为什么使用这些策略?
如果不小心被攻击了,那我们至少还可以限制这个页面不能调用一些敏感的东西,比如用户的照相机