CSRF简介
CSRF跨站请求伪造是一种冒充受信任用户,向服务器发送非预期请求的攻击方式。
攻击原理
- 用户输入账号信息请求登录A网站,并且生成了登录信息在cookie,在cookie的合法配置下(域名/路由等)的http请求会自动带在请求头上。
- 攻击者构造一个B网站,令B网站能合法访问到A网站的cookie,并再次网站对A网站进行http请求,让浏览器自己把cookie带上。
- A网站接收到了B网站的请求,并且校验了浏览器自动带上的cookie信息,通过校验成功后给B网站返回一些权限信息
- 这个时候B网站就已经拿到了该用户的A网站的一些权限信息了。
当然以上是属于获取信息型,另外也可以对A网站的信息数据库进行修改,最主要的还是利用了A网站的cookie去通过鉴权。
防御手段
最常见的防御手段有两种:
- 不用cookie进行鉴权,像现在最常用的token,http使用自定义请求/响应头字段,浏览器存储在storage,并由开发者自己放在请求头上。服务的从请求头获取。模拟了cookie的部分行为,但取消了自动负载在http请求头上,另外一个就是取消了Set-Cookie。
- 验证Referer头字段。
talk is cheap,show me the code
构建一个正牌网站
要构造一个能被攻击的网站,需要满足两个条件:
- 使用cookie来存储鉴权信息。
- 不验证Referer头字段。
作为一个前端,我们就是用最熟悉的 nodejs + express 来完成我们的示例代码。
package.json
简单看看需要哪些依赖,就两个
{
"name": "server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"cookie-parser": "^1.4.6",
"express": "^4.18.2"
}
}
index.js
主要路由就四个,一个主页面,一个详情页,然后一个登录接口和一个需要鉴权的用户信息接口。
const express = require('express')
const fs = require('fs')
const path = require('path')
var cookieParser = require('cookie-parser')
const app = express()
app.use(cookieParser())
const port = 3000
const secret = '12345'
// 页面
app.get('/', (req, res) => {
let htmlStr = fs.readFileSync(path.resolve(__dirname, './static/index.html'));
htmlStr = htmlStr.toString();
res.send(htmlStr);
})
app.get('/detail', (req, res) => {
let htmlStr = fs.readFileSync(path.resolve(__dirname, './static/detail.html'));
htmlStr = htmlStr.toString();
res.send(htmlStr);
})
// 登录
app.get('/login', (req, res) => {
res.cookie('token', secret, {
// expires: new Date(Date.now() + 10 * 1000),
});
res.send({ success: true });
})
// 列表
app.get('/userinfo', (req, res) => {
if (req.cookies.token === secret) {
res.send({
success: true,
data: {
username: 'abc',
password: '123',
},
});
} else {
res.status(401);
res.send({
success: false,
});
}
})
app.listen(port, () => {
console.log(`Example app listening on http://localhost:${port}`);
})
两个页面
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button>login</button>
<button>get userinfo</button>
<a href="/detail" target="_blank">我要买这个</a>
<script>
let buttons = document.querySelectorAll('button');
buttons[0].addEventListener('click', e => {
fetch('/login').then(_ => _.json()).then(res => console.log(res));
})
buttons[1].addEventListener('click', e => {
fetch('/userinfo').then(_ => _.json()).then(res => console.log(res));
})
</script>
</body>
</html>
detail.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
details
</body>
</html>
简单部署
为了更相似于真实场景,还有一些后续的操作,这里我用了wsl去运行这个 A网站 了。
记住我们这里的正牌站点的ip是172.19.174.136
构建一个冒牌网站
冒牌网站我们想清楚需求: 如果是网站的index.html,我们不需要做任何操作,用户能在这里正常的登录。但对于detail.html,我们希望能在此入手,能获取到cookie登录信息
虚假的detail.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
details
<button>美女荷官,在线发牌</button>
<script>
let button = document.querySelector('button');
button.addEventListener('click', () => {
fetch('/userinfo').then(_ => _.json()).then(res => console.log(res));
})
</script>
</body>
</html>
可以看到我们的的冒牌网站,只是多了一个button,并且多了一些非常诱惑人的信息,引诱用户进行点击。
冒牌网站如何获取到正牌网站的cookie?
我们可以知道同一个域名下cookie可以共享,那么如果我们用nginx进行代理,让网站的其他页面直接转发源站,但detail页就访问我们自己的冒牌站点,nginx配置如下:
server {
listen 80;
server_name localhost;
location /detail {
proxy_pass 这里替换冒牌网站的地址;
}
location / {
proxy_pass 这里替换正牌网站的地址;
}
}
配置完之后,我们分别访问正牌站点和冒牌站点的detail页看看
正牌站点
可以看到我们的正牌站点,是没有广告的,并且cookie的域名是http://172.19.174.136:3000
冒牌站点
而我们的冒牌网站则是有广告,并且cookie域名与正牌站点的域名是不一样的
但是我们这里只是测试,所以域名用了localhost,但也可以修改的,比如host文件,nginx代理,openssl,云服务的话就更麻烦了,但总的来说这里是可以改的,并且应该改成让用户相信我们这个是正规网站。
然后有了这个站点之后,就可以群发骚扰短信了,后面就等一个大怨种上当就行啦 哈哈哈。
假装自己是大怨种
收到了一个诈骗信息
点进了冒牌网站领福利了
点击发财按钮
然后我们就到了下面这个界面,并且经受不住诱惑,点击了发财按钮
然后
然后,你的信息就已经泄露了,我们去看看控制台 -> 网络
自动带上了cookie鉴权信息
成功通过了正牌网站的鉴权,获取了用户信息
真实开发问题分享
经验总结:开发中利用csrf解决的问题 - 掘金 (juejin.cn)
总结
我们这里只是一个场景,有可能获取了信息之后,还会用来做其他事情,或者是修改用户信息,或者是登录你的账户给自己买了一支花西子等等等。
另外我们的演示过程也不是很完整,正牌网站和冒牌网站的cookie不是共享的(域名不一样),冒牌网站在正常页面没登陆时,详情页也没有cookie去通过鉴权等这些场景都没有演示。希望各位看完之后自己去实现一个试一试吧,代码几乎都已经贴出来了,其中用nginx代理,已经整个过程思路才是关键。
另外有像burpsuite之类的软件可以直接检测出存在csrf漏洞的接口,并一键生成相应的冒牌站点。
最后
如果这篇文章有什么错误非常欢迎评论区进行指正。
如果觉得帮到你的话求一个点赞关注!!!