清风徐来
Michael's Blog
Demo 学 Echo Part13 跨站点请求伪造(CSRF)

请输入图片描述

跨站点请求伪造(CSRF)攻击以及如何预测它们

此类攻击的示例:尝试通过Web浏览器以外的媒体登录,例如使用CURL等

通常的防御方法是使用csrf令牌。在有表单的每个页面上,都会生成csrf令牌。提交表单时,会在请求中插入CSRF,然后后端检查发送的CSRF是否有效

csrf令牌本身是每次表单页面出现时生成的随机字符串。通常在每个POST请求中,令牌都作为标头,数据表单或查询字符串插入

package main

import (
	"fmt"
	"html/template"
	"net/http"

	"github.com/labstack/echo"
	"github.com/labstack/echo/middleware"
)

type M map[string]interface{}

func main() {
	tmpl := template.Must(template.ParseGlob("./*.html"))

	e := echo.New()

	const CSRF_TOKEN_HEADER = "X-Csrf-Token"
	const CSRF_KEY = "csrf_token"

	e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{
		TokenLookup: "header:" + CSRF_TOKEN_HEADER,
		ContextKey:  CSRF_KEY,
	}))

	e.GET("/index", func(c echo.Context) error {
		data := make(M)
		data[CSRF_KEY] = c.Get(CSRF_KEY)
		return tmpl.ExecuteTemplate(c.Response(), "view.html", data)
	})

	//sayhello处理程序中没有csrf标记检查,因为它已被中间件隐式处理
	e.POST("/sayhello", func(c echo.Context) error {
		data := make(M)
		if err := c.Bind(&data); err != nil {
			return err
		}

		message := fmt.Sprintf("hello %s", data["name"])
		return c.JSON(http.StatusOK, message)
	})

	e.Logger.Fatal(e.Start(":9000"))
}

view.html

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<body>
    <form id="form" action="/sayhello" method="POST">
        <div>
            <label>Name</label>
            <input type="text" name="name" placeholder="Type your name here">
        </div>
        <div>
            <label>Gender</label>
            <select name="gender">
                <option value="">Select one</option>
                <option value="male">Male</option>
                <option value="female">Female</option>
            </select>
        </div>
        <div>
            <input type="hidden" name="csrf_token" value="{{ .csrf_token }}">
            <button type="submit">Submit</button>
        </div>
    </form>

    <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>

    <script type="text/javascript">
        $(function () {
            $('form').on('submit', function (e) {
                e.preventDefault()
        
                var self = $(this)
        
                var formData = {
                    name: self.find('[name="name"]').val(),
                    gender: self.find('[name="gender"]').val(),
                }
        
                var url = self.attr('action')
                var method = self.attr('method')
                var payload = JSON.stringify(formData)
        
                $.ajax({
                    url: url,
                    type: method,
                    contentType: 'application/json',
                    data: payload,
                    beforeSend: function(req) {
                        var csrfToken = self.find('[name=csrf_token]').val()
                        req.setRequestHeader("X-Csrf-Token", csrfToken)
                    },
                }).then(function (res) {
                    alert(res)
                }).catch(function (err) {
                    alert('ERROR: ' + err.responseText)
                    console.log('err', err)
                })
            })
        })
    </script>
</body>
</html>

浏览器访问,提交数据,一切正常: csrf.png 如果把JavaScript中的以下去掉,会验证不过的

beforeSend: function(req) {
                        var csrfToken = self.find('[name=csrf_token]').val()
                        req.setRequestHeader("X-Csrf-Token", csrfToken)
                    },

尝试直接CURL访问,验证肯定不过

curl -X POST http://localhost:9000/sayhello \
     -H 'Content-Type: application/json' \
     -d '{"name":"noval","gender":"male"}'
{"message":"invalid csrf token"}

curl -X POST http://localhost:9000/sayhello \
     -d 'name=Joe' \
     -d 'gender=male'
{"message":"invalid csrf token"}

最后修改于 2019-08-17