您现在的位置是:网站首页> 编程资料编程资料

go语言csrf库使用实现原理示例解析_Golang_

2023-05-26 412人已围观

简介 go语言csrf库使用实现原理示例解析_Golang_

引言

今天给大家推荐的是web应用安全防护方面的一个包:csrf。该包为Go web应用中常见的跨站请求伪造(CSRF)攻击提供预防功能。

csrf小档案

「csrf小档案」   
star837used by-
contributors25作者Gorilla
功能简介为Go web应用程序和服务提供跨站点请求伪造(csrf)预防功能。可作为gin、echo等主流框架的中间件使用。  
项目地址github.com/gorilla/csr…  
相关知识跨站请求伪造(CSRF)、contex.Contex、异或操作  

一、CSRF及其实现原理

CSRF是CROSS Site Request Forgy的缩写,即跨站请求伪造。我们看下他的攻击原理。如下图:

当用户访问一个网站的时候,第一次登录完成后,网站会将验证的相关信息保存在浏览器的cookie中。在对该网站的后续访问中,浏览器会自动携带该站点下的cookie信息,以便服务器校验认证信息。

因此,当服务器经过用户认证之后,服务器对后续的请求就只认cookie中的认证信息,不再区分请求的来源了。那么,攻击者就可以模拟一个正常的请求来做一些影响正常用户利益的事情(比如对于银行来说可以把用户的钱转账到攻击者账户中。或获取用户的敏感、重要的信息等)

相关知识:因为登录信息是基于session-cookie的。浏览器在访问网站时会自动发送该网站的cookie信息,网站只要能识别cookie中的信息,就会认为是认证已通过,而不会区分该请求的来源的。所以给攻击者创造了攻击的机会。

CSRF攻击示例

假设有一个银行网站A,下面的是一个转给账户5000元的请求,使用Get方法

GET https://abank.com/transfer.do?account=RandPerson&amount=$5000 HTTP/1.1 

然后,攻击者修改了该请求中的参数,将收款账户更改成了自己的,如下:

GET https://abank.com/transfer.do?account=SomeAttacker&amount=$5000 HTTP/1.1 

然后,攻击者将该请求地址放入到一个标签中:

最后,攻击者会以各种方式(放到自己的网站中、email、社交通讯工具等)引诱用户点击该链接。只要是用户点击了该链接,并且在之前已经登录了该网站,那么浏览器就会将带认证信息的cookie自动发送给该网站,网站认为这是一个正常的请求,由此,将给黑客转账5000元。造成合法用户的损失。

当然,如果是post表单形式,那么攻击者会将伪造的链接放到form表达中,并用js的方法让表单自动发送:

二、如何预防

常见的有3种方法:

  • 一种是在网站中增加对请求来源的验证,比如在请求头中增加REFFER信息。
  • 一种是在浏览器中启用SameSite策略。该策略是告诉浏览器,只有请求来源是同网站的才能发送cookie,跨站的请求不要发送cookie。但这种也有漏洞,就是依赖于浏览器是否支持这种策略。
  • 一种是使用Token信息。由网站自己决定token的生成策略以及对token的验证。

其中使用Token信息这种是三种方法中最安全的一种。接下来我们就看看今天要推荐的CSRF包是如何利用token进行预防的。

三、CSRF包的使用及实现原理

csrf包的安装

go get github.com/gorilla/csrf 

基本使用

该包主要包括三个功能:

  • 通过csrf.Protect函数生成一个csrf中间件或请求处理器,用于后续的生成及校验token的流程。
  • 通过csrf.Token函数,可以在响应中输出当前生成的token值。
  • 通过csrf.TemplateField函数,可以在html模版中输出一个hidden的input,用于在form表单中提交token。

该包的使用很简单。首先通过csrf.Protect函数生成一个中间件或请求处理器,然后在启动web server时对真实的请求处理器进行包装。

我们来看下该包和主流web框架结合使用的实例。

使用net/http包启动的服务

package main import ( "fmt" "github.com/gorilla/csrf" "net/http" ) func main() { muxServer := http.NewServeMux() muxServer.HandleFunc("/", IndexHandler) CSRF := csrf.Protect([]byte("32-byte-long-auth-key")) http.ListenAndServe(":8000", CSRF(muxServer)) } func IndexHandler(w http.ResponseWriter, r *http.Request) { // 获取token值 token := csrf.Token(r) // 将token写入到header中 w.Header().Set("X-CSRF-Token", token) fmt.Fprintln(w, "hello world.Go") } 

echo框架下使用csrf包

package main import ( "github.com/gorilla/csrf" "net/http" "github.com/labstack/echo" ) func main() { e := echo.New() e.POST("/", func(c echo.Context) error { return c.String(http.StatusOK, "Hello, World!") }) // 使用自定义的CSRF中间件 e.Use(CSRFMiddle()) e.Logger.Fatal(e.Start(":8080")) } // 自定义CSRF中间件 func CSRFMiddle() echo.MiddlewareFunc { csrfMiddleware := csrf.Protect([]byte("32-byte-long-auth-key")) // 这里使用echo的WrapMiddleware函数将csrfMiddleware转换成echo的中间件返回值 return echo.WrapMiddleware(csrfMiddleware) } 

gin框架下使用csrf包

import ( "fmt" "github.com/gin-gonic/gin" "github.com/gorilla/csrf" adapter "github.com/gwatts/gin-adapter" ) // 定义中间件 func CSRFMiddle() gin.HandlerFunc { csrfMiddleware := csrf.Protect([]byte("32-byte-long-auth-key")) // 这里使用adpater包将csrfMiddleware转换成gin的中间件返回值 return adapter.Wrap(csrfMiddleware) } func main() { r := gin.New() // 在路由中使用中间件 r.Use(CSRFMiddle()) // 定义路由 r.POST("/", IndexHandler) // 启动http服务 r.Run(":8080") } func IndexHandler(ctx *gin.Context) { ctx.String(200, "hello world") } 

beego框架下使用csrf包

package main import ( "github.com/beego/beego" "github.com/gorilla/csrf" ) func main() { beego.Router("/", &MainController{}) beego.RunWithMiddleWares(":8080", CSRFMiddle()) } type MainController struct { beego.Controller } func (this *MainController) Get() { this.Ctx.Output.Body([]byte("Hello World")) } func CSRFMiddle() beego.MiddleWare { csrfMiddleware := csrf.Protect([]byte("32-byte-long-auth-key")) // 这里使用adpater包将csrfMiddleware转换成gin的中间件返回值 return csrfMiddleware } 

实际上,要通过token预防CSRF主要做以下3件事情:每次生成一个唯一的token、将token写入到cookie同时下发给客户端、校验token。接下来我们就来看看csrf包是如何实现如上步骤的。

实现原理

csrf结构体

该包的实现是基于csrf这样一个结构体:

type csrf struct { h http.Handler sc *securecookie.SecureCookie st store opts options } 

该结构体同时实现了一个ServeHTTP方法:

func (cs *csrf) ServeHTTP(w http.ResponseWriter, r *http.Request) 

在Go中,我们知道ServeHTTP是在内建包net/http中定义的一个请求处理器的接口:

type Handler interface { ServeHTTP(ResponseWriter, *Request) } 

凡是实现了该接口的结构体就能作为请求的处理器。在go的所有web框架中,处理器本质上也都是基于该接口实现的。

好了,现在我们来分析下csrf这个结构体的成员:

  • 「h」:是一个http.Handler,作为实际处理请求的处理器。该h的来源是经Protect函数返回值包装后的,即开始示例中CSRF(muxServer)中的muxServer。又因为csrf也是一个请求处理器,请求就会先执行csrf的ServeHTTP方法的逻辑,如果通过了,再执行h的ServeHTTP逻辑。
  • 「sc」:类型是*securecookie.SecureCookie,第三方包,该包的作用是对cookie的值进行加密/解密。在调用csrf.Protect方法时,传递的第一个32字节长的参数就是用于该包进行对称加密用的秘钥。下一篇文章我们会详细介绍该包是如何实现对cookie内容进行/加解密的。
  • 「st」:类型是store,是csrf包中定义的一个接口类型。该属性的作用是将token存储在什么地方。默认是使用cookieStore类型。即将token存储在cookie中。
  • 「opts」:Options属性,用于设置csrf的选项的。比如token存储在cookie中的名字,token在表单中的名字等。

这里大家可能有这样一个疑问:csrf攻击就是基于cookie来进行攻击的,为什么还要把token存储在cookie中呢?在一次请求中,会有两个地方存储token:一个是cookie中,一个是请求体中(query中,header中,或form中),当服务端收到请求时,会同时取出这两个地方的token,进而进行比较。所以如果攻击者伪造了一个请求,服务器能接收到cookie中的token,但不能接收到请求体中的token,所以伪造的攻击还是无效的。

csrf包的工作流程

在开始的“使用net/http包启动的服务”示例中,我们先调用了Protect方法,然后又用返回值对muxServer进行了包装。大家是不是有点云里雾里,为什么要这么调用呢?接下来咱们就来分析下Protect这个函数以及csrf包的工作流程。

在使用csrf的时候,首先要调用的就是Protect函数。Protect的定义如下:

func Protect(authKey []byte, opts ...Option) func(http.Handler) http.Handler 

该函数接收一个秘钥和一个选项切片参数。返回值是一个函数类型:func(http.Handler) http.Handler。实际的执行逻辑是在返回的函数中。如下:

CSRF := csrf.Protect([]byte("32-byte-long-auth-key")) http.ListenAndServe(":8000", CSRF(muxServer)) // Protect源码 func Protect(authKey []byte, opts ...Option) func(http.Handler) http.Handler { return func(h http.Handler) http.Handler { cs := parseOptions(h, opts...) // Set the defaults if no options have been specified if cs.opts.ErrorHandler == nil { cs.opts.ErrorHandler = http.HandlerFunc(unauthorizedHandler) } if cs.opts.MaxAge < 0 { // Default of 12 hours cs.opts.MaxAge = defaultAge } if cs.opts.FieldName == "" { cs.opts.FieldName = fieldName } if cs.opts.CookieName == "" { cs.opts.CookieName = cookieName } if cs.opts.RequestHeader == "" { cs.opts.RequestHeader = headerName } // Create an authenticated securecookie instance. if cs.sc == nil { cs.sc = securecookie.New(authKey, nil) // Use JSON serialization (faster than one-off gob encoding) cs.sc.SetSerializer(securecookie.JSONEncoder{}) // Set the Ma
                
                

-六神源码网