4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

beego AjaxでCSRF対策を独自実装する と思ったらしなくても良かった件

Last updated at Posted at 2021-08-10

はじめに

go言語のwebframework beegoにはCSRF対策が実装されています。

CSRFとは

https://www.trendmicro.com/ja_jp/security-intelligence/research-reports/threat-solution/csrf.html

説明は不要かと思いますが、CSRFとは他サイトを経由した
不正なリクエストによってサイトを攻撃する手法です。

CSRF対策の一つとしてbeegoでも採用されているのがトークンを使った対策です。
サーバに保存されたトークンとリクエストにあるトークンが一致しているかを
判定することによって不正なリクエストでないことを証明します。

公式サイトを読むと・・・
POSTの場合、configにパラメータを記載すればcookieに_xsrfをセットするよ
PUTとDELETEの場合、cookieに含めないのでcontrollerにXSRFFormHTML()を記載すれば_xsrfフィールドを全てのformに追加するよ
とありますね。

ですがAjaxリクエスト(POST)を利用する場合、常にエラーを返してしまい正常に動作しません。

公式サイトを見ても情報がないため、Ajaxリクエストの場合のCSRF対策は少し工夫してやる必要があります。

・・・とそんなふうに考えていた時期が俺にもありました。

しなくても良かった

結論から言います!
AjaxリクエストでもCSRF対策は正常に動作します!
追加でヘッダーに_xsrfを含めてやればよいです。

test.tpl
 {{.xsrfdata}}
test.js
let config = {
  headers: {
    "X-Xsrftoken": document.getElementsByName("_xsrf")[0].value
    //もしくはcookieからBASE64デコードした値をセット
  }
}

let data = {
}

axios.post(URL, data, config).then(...)

これだけでbeegoがよしなにしてくれます!
AjaxでCSRF対策をしたい方はここでページを閉じてもらってOKです。

筆者はひどく迷走しCSRF対策を独自実装しないといけないと思ってしまいました。
以下はその迷走の軌跡です。

迷走

↑の正解を見ると明白ですがヘッダーにトークンが含まれていないからですよね。
そこで筆者は勘違いをしてしまいました。

Ajaxリクエストだとヘッダーにトークンを含めてやる必要があるけど
ヘッダーのどこに含めるかはどこでもいいだろう。
AjaxリクエストだとbeegoのCSRF対策処理を通らないんだろうし、と。

なので

  • ヘッダーの適当な値にトークンを含めて送信
  • サーバに保存されたトークンとリクエストにあるトークンの一致確認

を勘違いして実装しちゃいました。

しなくても良かった実装箇所

  • ヘッダーの適当な値にトークンを含めて送信
test.js
let config = {
  headers: {
    "X-CSRF-Token": document.getElementsByName("_xsrf")[0].value
    //もしくはcookieからBASE64デコードした値をセット
  }
}

let data = {
}

axios.post(URL, data, config).then(...)
  • サーバに保存されたトークンとリクエストにあるトークンの一致確認
test.go
func (c *TestController) Prepare() {
	enableXSRF, _ := beego.AppConfig.Bool("EnableXSRF")
	if enableXSRF == true {
		if c.IsAjax() && c.Ctx.Request.Method == "POST" {
			//beegoの通常機能でajaxがエラーになるため一旦false
			//falseにした設定はこのリクエスト内だけ有効なので戻さなくてOK
			c.EnableXSRF = false
			valid := validation.Validation{}
			if c.Ctx.Request.Header["X-Csrf-Token"] != nil {
				token := c.Ctx.Request.Header["X-Csrf-Token"][0]
				if token != c.XSRFToken() {
					//トークン不一致エラー
					valid.SetError("CSRF", "トークン不一致です")
				}
			} else {
				//トークンがセットされてないエラー
				valid.SetError("CSRF", "トークンがセットされてないです")
			}
			if valid.HasErrors() {
				data := make(map[string]string)
				for _, err := range valid.Errors {
					data[err.Key] = err.Message
				}
				mapData := map[string]interface{}{
					"isValid":           false,
					"validationSummary": data,
				}
				c.Data["json"] = mapData
				c.Ctx.Output.SetStatus(http.StatusOK)
				c.ServeJSON()
				return
			}
		}
	}
}

こんな実装しなくても標準機能が対応していたんですねぇ・・・
うわー恥ずかしい!

惜しかった実装箇所

なぜAjaxリクエストだとエラーになると思い込んでしまったのか?

  • ヘッダーの適当な値にトークンを含めて送信

ヘッダーにトークンを含めて送信という考え方は正しかったんですが、
ヘッダーのどの値に含めるかが間違ってました。

test.js
let config = {
  headers: {
-    "X-CSRF-Token": document.getElementsByName("_xsrf")[0].value
+    "X-Xsrftoken": document.getElementsByName("_xsrf")[0].value
    //もしくはcookieからBASE64デコードした値をセット
  }
}

"X-Xsrftoken"にトークンをセットするのが正解なんです。
そんなのどこにも書いてないじゃん!と思いましたが
正しい値はbeegoのソースにちゃんと書いてあったんです・・・

context.go
// CheckXSRFCookie checks if the XSRF token in this request is valid or not.
// The token can be provided in the request header in the form "X-Xsrftoken" or "X-CsrfToken"
// or in form field value named as "_xsrf".
func (ctx *Context) CheckXSRFCookie() bool {
	token := ctx.Input.Query("_xsrf")
	if token == "" {
		token = ctx.Request.Header.Get("X-Xsrftoken")
	}
	if token == "" {
		token = ctx.Request.Header.Get("X-Csrftoken")
	}
	if token == "" {
		ctx.Abort(422, "422")
		return false
	}
	if ctx._xsrfToken != token {
		ctx.Abort(417, "417")
		return false
	}
	return true
}
  1. inputの"_xsrf"
  2. ヘッダーの"X-Xsrftoken"
  3. ヘッダーの"X-Csrftoken"

の順番でトークンが確認されることがちゃんと書いてあります。
実装する前にソースをちゃんと見ろって話しですね。

あ、ただしAjaxだと1.が認識されないので2.3.の方法でやるのが正しいです。

おわりに

AjaxリクエストのCSRF対策についての参考情報

beegoのgithub issueにヘッダについてのやり取りがありました。
https://github.com/beego/beego/issues/2426

中国語で言及されているサイトがありました。
https://www.kancloud.cn/hello123/beego/126124
https://cloud.tencent.com/developer/article/1067750

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?