一番単純な CSRF
まずは、一番単純な CSRF を示します。 なお、今後はローカルマシンでサンプルアプリが起動していることを前提として、実際に挙動を確認するリンクなどを示します。
1. GET を使ったエンドポイントの CSRF 脆弱性
ログイン認証がなく、GET を使ったエンドポイントを用いて、最も簡単な CSRF を示します。
1.1. シナリオと概要説明
以下、理解を助けるために、現実に当てはめたシナリオを提示します。
<<<<<<<<<<<<<<
あなたは、掲示板サイトを作ることにしました。 機能としては以下の通りです。
- ユーザーはログイン認証が不要
- ユーザーの識別は IP アドレスで行う
- 投稿内容は、URL のクエリパラメタとしてクライアントからサーバーに送信される
- 投稿された内容は、すべて同じ掲示板で表示され、だれでも閲覧できる
CSRF の脆弱性を抜きにしても色々と問題がありますが、あなたは経験不足から問題点に気が付きませんでした。
<<<<<<<<<<<<<<
その結果として実装されたのが、以下のエンドポイントです。 なお、本質でない機能は簡略化しています。たとえば、投稿された内容は保存などを行っていません。 https://github.com/sasakiy84/csrf-demo/blob/main/originHandler/1-board.ts
// /1-get-board に来た GET リクエストを処理する
router.get("/1-get-board", (req, res) => {
// リクエスト送信者の IP アドレスを取得する
const ip = req.ip;
// URL クエリパラメタに text という名前で埋め込まれている値を取得する
const postedText = req.query.text;
if (!postedText) {
res.send("query: text is required");
return;
}
// 掲示板に投稿する代わりに、サーバー側のコンソールに表示する
console.log(`user: ${ip} ::: ${postedText}`);
res.send(`<p>全体掲示板:今日の投稿<br />user: ${ip} ::: ${postedText}</p>`);
});
簡単に処理の内容を説明すると、reqest の情報が入っているreq
という変数から IP アドレスと text という URL のクエリパラメタを取り出して、その内容を表示させています。
クエリパラメタについて概要を説明しておきます。
URL は、いくつかの部分に分けることができ、クエリパラメタは path の後の部分をさします。具体的には、?
で始まり、以降key1=value1
の組み合わせが&
で繋がっていきます。
たとえば、http://example.com/path/to/html.html?key1=value1&key2=value2
のような感じです。
検索クエリなどをクライアントがサーバーに渡すときに用いられることが多いです。
1.2. アプリの正常系
開発者は、クライアントから以下のような URL のリクエストを受け取ります。
http://localhost:3000/1-get-board?text=%E3%81%AF%E3%81%98%E3%82%81%E3%81%A6%E3%81%AE%E6%8A%95%E7%A8%BF
ここでのクエリパラメタは、text=%E3%81%AF%E3%81%98%E3%82%81%E3%81%A6%E3%81%AE%E6%8A%95%E7%A8%BF
の部分です。
ちなみに、%E3
などの意味不明な文字列は、日本語が変換されたものです。ブラウザの検索窓などにコピペすると、日本語が復元されると思います。
このリンクにアクセスすると、処理が実行され、今回であればはじめての投稿
という投稿内容がコンソール画面とクライアントに表示されます。
1.3. CSRF の脆弱性
では、このアプリにどのような CSRF 脆弱性があるのでしょうか。あるシナリオを想定してみましょう
<<<<<<<<<<<<<<<<
あなたがとあるブログを見ていると、以下のようなコメントがありました。
この記事について、より詳細に書かれた内容が >> ここ << にあります
あなたが興味をもってこのリンクを踏むと、以下の URL にとばされます
すると、なんと匿名掲示板サイトに以下の内容が投稿されました
私は呼声市役所を爆破します
この投稿は警察に通報され、IP アドレスをもとに投稿者が捜索されました。このとき、投稿者の IP アドレスはあなたのものだったので、あなたは威力業務妨害の罪に問われる可能性が出てきました。 <<<<<<<<<<<<<<<<<<
上記のようなシナリオが想定されます。
ここで、再び CWE の定義を確認してみましょう。
英語版
The web application does not, or can not, sufficiently verify whether a well-formed, valid, consistent request was intentionally provided by the user who submitted the request. https://jvndb.jvn.jp/ja/cwe/CWE-352.html
日本語版
本脆弱性が存在する Web アプリケーションは、フォーマットに沿った、妥当で一貫性のあるリクエストが、送信したユーザの意図通りに渡されたものかを十分に検証しない、あるいは検証が不可能です。 https://cwe.mitre.org/data/definitions/352.html
今回の爆破予告を投稿したリクエストは、ユーザーが意図していないリクエストです。ユーザーはリンクをクリックした時点で、爆破予告を投稿しようという意図は一切なかったはずです。 にもかかわらず、サーバー側ではこのリクエストを正規のものとみなして、掲示板に投稿するという処理を実行してしまいました。このように、ユーザーが意図していないリクエストにより、サーバー側の処理が実行されてしまうのが CSRF 脆弱性です。
演習として、クエリパラメタを調整して、不適切投稿をユーザーにさせてみてください。