質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.37%
セキュリティー

このタグは、コンピューターシステムの安全性やデータの機密性に関連したトピックの為に使われます。

Webサイト

一つのドメイン上に存在するWebページの集合体をWebサイトと呼びます。

CSRF

クロスサイトリクエストフォージェリ (Cross site request forgeries、CSRF)は、 外部Webページから、HTTPリクエストによって、 Webサイトの機能の一部が実行されてしまうWWWにおける攻撃手法です。

Q&A

解決済

3回答

2032閲覧

CSRF攻撃対策について

hupyaginu

総合スコア16

セキュリティー

このタグは、コンピューターシステムの安全性やデータの機密性に関連したトピックの為に使われます。

Webサイト

一つのドメイン上に存在するWebページの集合体をWebサイトと呼びます。

CSRF

クロスサイトリクエストフォージェリ (Cross site request forgeries、CSRF)は、 外部Webページから、HTTPリクエストによって、 Webサイトの機能の一部が実行されてしまうWWWにおける攻撃手法です。

0グッド

5クリップ

投稿2024/02/12 12:49

編集2024/02/12 13:00

前提

CSRF攻撃対策について調べています。

前提として以下を仮定します

  • a.com は攻撃者の管理下にあるWebサイト
  • b.com は攻撃対象の掲示板Webサイト

攻撃シナリオ

  1. 攻撃者が a.com に以下スクリプトを追加する

js

1const formData = new FormData(); 2formData.append("message", "hello"); 3 4await fetch('https://b.com/post', { 5 metho: 'post', 6 data: formData 7})

2 ユーザーがa.comにアクセスする
3. (1)のスクリプトが実行される
4. b.comにhelloが書き込まれる

質問


画像引用元: https://tech.basicinc.jp/articles/231

上記画像について、「攻撃者はトークンを知ることができない」と書いています。
しかし、攻撃シナリオの1を以下の様に変更した場合、トークンを知ることが可能になり、攻撃が成立するのではないでしょうか?

該当のソースコード

js

1// トークンを取得する 2const response = await fetch('https://b.com/') 3const html = await response.text() 4// 正規表現やDOMセレクタを利用してトークンを取得する 5const csrfToken = html.getCSRFToken() 6 7const formData = new FormData(); 8formData.append("message", "hello"); 9// CSRFトークンを追加する 10formData.append("csrfToken ", csrfToken); 11 12await fetch('https://b.com/post', { 13 metho: 'post', 14 data: formData 15})

試したこと

a.com を localhost:80、b.com を localhost:81 としてローカル環境で試してみました。
しかし、Google ChromeではCORSの問題が発生して実際に攻撃は成功しませんでした。
(こちらはまた別問題な気がするので、質問が複雑にならないように今は問題を保留します。)

本来であれば「単純リクエスト」に該当するので、GETでCSRFトークンが取得できてしまうと思います。
https://developer.mozilla.org/ja/docs/Web/HTTP/CORS#%E5%8D%98%E7%B4%94%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88

補足情報(FW/ツールのバージョンなど)

  • Google Chrome 121.0.6167.161

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

ベストアンサー

Google ChromeではCORSの問題が発生して実際に攻撃は成功しませんでした。
(こちらはまた別問題な気がするので、質問が複雑にならないように今は問題を保留します。)

いえ、それが本題です。CORSでクロスオリジンからの情報取得を禁止することで、無関係な箇所でのトークン取得を防止しています。

投稿2024/02/12 14:36

maisumakun

総合スコア145930

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hupyaginu

2024/02/12 16:57 編集

HTMLだけを取得するGETリクエストは単純リクエストの条件を満たしているので単純リクエストに該当すると考えています。 MDNには単純リクエストはCORSのプリフライトが発生しないと書かれていますが、MDNの内容は間違いで、単純リクエストがCORSで拒否されるので攻撃が成立しないという事でしょうか? それであれば、そもそも今の時代CSRF対策としてのCSRFトークン生成は不要で、CORSだけ適切に設定すれば良いという事でしょうか? 単純リクエストがCORSで拒否されるなら、以下記事の「CSRFが成功してしまう例」の(3)がそもそもプリフライトリクエストで失敗して、POSTリクエストは送信されないので、以下の記事もCSRFが成立しないので「誤り」という事でしょうか? (実際私も質問時の内容で再現できず失敗しているわけですが。) https://qiita.com/netebakari/items/41baa7e1d0b8d89f9d12 MDNは信頼できる情報だと考えていましたが、MDNも間違いということでしょうか? 時代の流れとしては以下のような感じでしょうか? 4の時代が来るまで全てのWebサイトがCSRF攻撃に対して脆弱だったのでしょうか? 1. CSRF攻撃という手法が何者かによって発見された 2. Webブラウザ側の対応ができていない(当時はCSRF攻撃対策トークン、CORSがなかった) 3. Webアプリケーション開発者がCSRFトークンを用意して対策した (しかし、実際は対策したつもりになっていて、実際はXHR(fetch)でトークンを取ることができていた時代がある?) 4. Webブラウザ側でCORSが実装された
maisumakun

2024/02/13 01:21 編集

> 単純リクエストがCORSで拒否されるので攻撃が成立しないという事でしょうか? 違います。クロスオリジンの単純リクエストの場合、リクエストを飛ばすことはできますが、(サーバサイドでAccess-Control-Allow-Originにより許可しない限り)結果をJavaScript から取得できません。 なお、単純リクエストを飛ばすだけなら<img>や<script>のsrcに書き込む、というような手法もありえます。
maisumakun

2024/02/13 01:13

> 時代の流れとしては以下のような感じでしょうか? これも違います。最初にJavaScriptで行えるようになったXHRでは、そもそもクロスオリジンでの通信機能がなく、それを「セキュリティ上の問題がないように」実現するための仕組みとしてCORSが作られました。
hupyaginu

2024/02/14 13:25 編集

自分でimgやscriptからhtmlが取れるか色々試しましたがやはり無理でした! 他にも、教えていただいたことを確認するように自分で試すことでCSRF攻撃について理解を深めることができました! ありがとうございます!
maisumakun

2024/02/14 23:50

> そもそも今の時代CSRF対策としてのCSRFトークン生成は不要で、CORSだけ適切に設定すれば良いという事でしょうか? これも違います。トークンがなかった場合、JavaScriptなしで単にフォームから送信するだけでもCSRFは成立します。
otn

2024/02/15 14:34

なんかこう、JavaScriptを使わないとCSRF攻撃できないと思うって、時代の変化ですかね。 xhrrequestが出来たのより CSRF攻撃のほうが古いはず。
guest

0

CORSが発生しない ことと プリフライトリクエストが発生しない ことを混同されているように感じます。

GET のような単純リクエストは安全であるはずなので、ブラウザはクロスオリジンであるかどうかを確認せずに(つまりプリフライトリクエストを実行せずに)いきなりサーバーに要求を出します。ただし、返ってきた結果に、適切な Access-Control-Allow-Origin ヘッダー が設定されていなかった場合、ブラウザはスクリプトに結果を読み取ることを許可しません。そのため、攻撃者はトークンを読み取ることはできません。

単純じゃないメソッドの場合、そもそもサーバーがリクエストを処理すること自体を防ぎたい可能性があるので、ブラウザはいきなり正規の要求を投げる代わりに、まずはプリフライトリクエストを投げて Access-Control-Allow-Origin ヘッダー を確認します。ここで適切な Access-Control-Allow-Origin ヘッダー が返ってきた際に初めて正規の要求を出します。

おそらく MDN の記述に間違いはなく、結果を読み取られなければいいだけの単純リクエストと、実行そのものを阻止したい POST などのリクエストで手順が違うということですね。

投稿2024/02/12 17:15

R.Mizukami

総合スコア1086

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

maisumakun

2024/02/13 01:20

自分の回答にも補足しましたが、単純リクエストを「投げるだけ」なら、<img src>や<script src>を使う手段が昔から存在します。
guest

0

基本的にはすでにサーバーBにログイン中で、セッション情報がクッキーに格納されている前提、サーバーBではサーバーA(クロスオリジン)からのリクエストがなぜかピンポイントで許可されてて、クッキーも送れる前提の話かと思います。

以下は

で動かす想定の実験コードです。

sh

1npm init -y 2npm i express 3cat >server_a.js <<EOF 4const express = require('express') 5const app = express() 6app.get('/', (req, res) => { 7 res.send(\` 8<script> 9(async ()=>{ 10 try { 11 const formData = new FormData() 12 const res = await fetch('http://localhost:3001/buy', { 13 method: 'get', 14 credentials: 'include', 15 data: formData 16 }) 17 console.log(await res.json()) 18 } 19 catch(e) { 20 console.log(e) 21 } 22})() 23</script> 24\`) 25}) 26app.listen(3000, ()=>{}) 27EOF 28cat >server_b.js <<EOF 29const express = require('express') 30const app = express() 31app.use((req, res, next)=>{ 32 res.header('Access-Control-Allow-Origin', 'http://localhost:3000') 33 res.header('Access-Control-Allow-Credentials', 'true') 34 res.cookie('session', 'something_id') 35 next() 36}); 37app.get('/', (req, res) => { 38 res.send('商品 ¥500<form action="/buy"><input name="csrf" type="hidden" value="something_token"><input type="submit" value="購入する"></form>'); 39}) 40app.get('/buy', (req, res) => { 41 res.json({message: (req.query.csrf == 'something_token') ? 'お買い上げありがとうございました' : '攻撃してませんか?'}); 42}) 43app.listen(3001, ()=>{}) 44EOF 45node server_a.js& 46node server_b.js& 47wait

ブラウザで http://localhost:3001/ を開いて購入ボタンを押すと、購入できた風のJSONが返ってきます(フォームsubmitでJSON返るのも気持ち悪いですが)。
次に http://localhost:3000/ を開くと行儀悪くいきなりfetchしに行ってくれるので、そこでCSRFトークンみたいな何かで弾かれてるのが分かると思います。

最後に一言忘れてました。既にサーバーBで取得されたトークンはクッキーにないので、サーバーAからは取れないですよね?

あと、Access-Control-Allow-Origin, Access-Control-Allow-Credentialsとても危険なので原則分からないのにいじらない方がいいです。

投稿2024/02/12 18:29

編集2024/02/12 18:49
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.37%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問