浅谈 CSRF 攻击(附实例,go 语言服务器)

5 篇文章 0 订阅

先提一点,做实例的时候遇到的 cookie 带不上的原因,是谷歌提出的 SameSite Cookies 机制导致的,之前都不知道 cookie 还有这么个属性。
在这里插入图片描述

趣事

2011年12月21日,国内最大的开发者社区 CSDN 被黑客在互联网上公布了600万注册用户的数据。更糟糕的是,CSDN 在数据库中明文保存了用户的密码。当然了,这次事故的原理,肯定不是 CSRF

原理

CSRFCross-Site Request Forgery 的缩写,跨站请求伪造。本篇阐述的是基于浏览器 cookie 机制伪造请求的方式。
我们有三个角色:

  1. 用户 U
  2. 被攻击的服务器 A
  3. 攻击者 B
    CSRF图示

需要满足以下几个条件:

  1. 用户登录过网站 A,并且保持登录状态(cookie未清空),同时访问了网站 B
  2. 网站 A 对于自己开放的修改信息的接口,只做了 cookie 的校验(现在应该是很少有网站会这么做了)。
  3. 网站 B 提前了解了网站 A 的相应接口。

过程中遇到的一些问题

cookie

有关 cookie 的介绍,网上资源很多,这里就不详细介绍了,只说以下 SameSite 属性。
cookieSameSite 的出现,也是由于安全原因考虑,各大浏览器厂商也重视了该问题,same-site cookies 是基于 ChromeMozilla 开发者花了三年多时间制定的 IETF 标准。它是在原有的 Cookie 中,新添加了一个 SameSite 属性,它标识着在非同源的请求中,是否可以带上 Cookie ,它可以设置为3个值,分别为:

  • Strict
    Strict 是最严格的,它完全禁止在跨站情况下,发送 Cookie。只有在自己的网站内部发送请求,才会带上 Cookie。不过这个规则过于严格,会影响用户的体验。
  • Lax
    默认的规则。Lax 的规则稍稍放宽了些,大部分跨站的请求也不会带上Cookie,但是一些导航的Get请求会带上Cookie,如下:
  • None
    None 就是关闭 SameSite 属性,所有的情况下都发送 Cookie。不过 SameSite 设置 None,还要同时设置 CookieSecure 属性,否则是不生效的(下图为手动设置的 SameSite None,红色警告了)。
    在这里插入图片描述

Cors 跨域

期间也遇到了跨域相关的问题,可以在服务器端进行相应的配置,具体原理自行搜取。
本服务使用的是 gogin 框架,可以直接使用第三方跨域库 github.com/rs/cors/wrapper/gin ,如下:

import (
	cors "github.com/rs/cors/wrapper/gin"
	"github.com/gin-gonic/gin"
)
func ServerA() {
	router := gin.Default()
	router.Use(cors.Default())
	router.Run(":9090")
}

源码实现

相应的源码在 Gitee/GoTest 下的 CSRF 文件夹内,感兴趣的可以下载源码测试。

案例:用户登录 Server A(部署在:172.16.60.43:9090),这个时候收到了一封邮件(是兄弟就来砍我…),点击打开了诈骗网站 B(部署在本地)。
主要就是验证:我们从 B 网站访问 A 的时候,会不会带上 cookie 值,简化的代码如下:

Server A

package main

import (
	"commonTest/utils"
	"fmt"
	"github.com/gin-gonic/gin"
	"os"
	"path"

	//cors "github.com/rs/cors/wrapper/gin"
	"net/http"
	"strings"
)

// 需要做 cookie 验证
func main() {
	router := gin.Default()
	//router.Use(cors.Default())
	router.Use(customCors())

	getwd, err := os.Getwd()
	utils.CheckErr(err)
	htmlFile := path.Join(getwd, "CSRF", "indexA.html")
	fmt.Println("htmlFile: ", htmlFile)
	router.LoadHTMLFiles(htmlFile)
	router.GET("/", func(c *gin.Context) {
		c.HTML(http.StatusOK, "indexA.html", nil)
	})

	router.GET("/login", func(c *gin.Context) {
		fmt.Println(c.ClientIP())
		c.SetCookie("test", "zzyy", 24*60*60, "", "", false, false)
		c.String(http.StatusOK, "OK")
	})

	router.GET("/change", func(c *gin.Context) {
		// 只需要验证收到 cookie 信息即可
		cookie, err := c.Cookie("test")
		if err != nil {
			fmt.Println("接收cookie出错了: ", err.Error())
			c.String(http.StatusBadRequest, "就你还想来攻击我")
		} else {
			fmt.Println("成功接收到cookie: ", cookie)
			c.String(http.StatusOK, "修改成功")
		}
	})

	err = router.Run(":9090")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println("exit")
}

var allowedMethods = []string{
	"POST",
	"GET",
	"OPTIONS",
	"PUT",
	"PATCH",
	"DELETE",
}

func customCors() gin.HandlerFunc {
	return func(c *gin.Context) {
		origin := c.Request.Header.Get("Origin")
		fmt.Println("origin: ", origin)
		c.Header("Access-Control-Allow-Origin", origin)
		c.Header("Access-Control-Allow-Credentials", "true")

		if c.Request.Method == "OPTIONS" {
			c.Writer.Header().Set(
				"Access-Control-Allow-Methods",
				strings.Join(allowedMethods, ", "),
			)
			c.Writer.Header().Set(
				"Access-Control-Allow-Headers",
				c.Request.Header.Get("Access-Control-Request-Headers"),
			)
		}
	}
}

indexA.html

<html>
<body>
	<h1>登录</h1>
	<form action="/login" method="get">
		<input type="text" name="account"/>
		<input type="password" name="password"/>
		<input type="submit" value="登录"/>
	</form>
</body>
</html>

提供了两个接口:

  • /login 用于登录,设置 cookie
  • /change 用于测试是否收到了 cookie

访问 http://172.16.60.43:9090/ 跳转到登录页面
登录
登录成功,可以看到 cookie 已经赋值
在这里插入图片描述

Server B

package CSRF

import (
	"commonTest/utils"
	"fmt"
	"github.com/gin-gonic/gin"
	"net/http"
	"os"
	"path"
)

go:embed indexB.html
//var htmlB embed.FS

func ServerB() {
	router := gin.Default()
	//fInfo, err := fs.Stat(htmlB, "indexB.html")
	//utils.CheckErr(err)
	//fmt.Println("fInfo: ", fInfo.Name())

	//matches, err := fs.Glob(htmlB, "*.html")
	//utils.CheckErr(err)
	//fmt.Println("matches: ", matches)
	//router.LoadHTMLFiles("indexB.html")

	// 获取文件所在位置
	getwd, err := os.Getwd()
	utils.CheckErr(err)

	htmlFile := path.Join(getwd, "CSRF", "indexB.html")
	fmt.Println("htmlFile: ", htmlFile)

	router.LoadHTMLFiles(htmlFile)
	router.GET("/", func(c *gin.Context) {
		c.HTML(http.StatusOK, "indexB.html", nil)
	})
	router.Run(":9092")
}

indexB.html

<html>
<body>
<div>
	<form action="http://172.16.60.43:9090/change" method="get">
		<input type="hidden" name="account"/>
		<input type="hidden" name="password" value="34324"/>
		<input type="submit" value="是兄弟就来砍我,一刀99999暴击"/>
	</form>
</div>
</body>
</html>

隐藏了用户名密码的输入框,引诱点击提交按钮。
入侵网站
经测试,是能够带上 cookie 请求相应的数据的。
带cookie

总结

CSRF 的原理,了解了浏览器的 cookie 机制,基本上理解起来问题不大,就是利用了服务器的基于 cookie 的鉴权机制。
web 安全,是个很大的领域,这个例子也让自己过了把当黑客的瘾,找了几个网站看了下,都没成功,看样子大家对 CSRF 攻击都做了功课了。当然这是最基础的 web 攻防,感兴趣的站友们可以自行深入了解。

参考

CSRF的原理与防御 | 你想不想来一次CSRF攻击?
Web安全之CSRF实例解析
OWASP Cheat Sheet Series Cross-Site Request Forgery Prevention
前端网络安全必修 1 同源策略和CSRF

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值