一番単純な 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 にとばされます

http://localhost:3000/1-get-board?text=%E7%A7%81%E3%81%AF%E5%91%BC%E5%A3%B0%E5%B8%82%E5%BD%B9%E6%89%80%E3%82%92%E7%88%86%E7%A0%B4%E3%81%97%E3%81%BE%E3%81%99

すると、なんと匿名掲示板サイトに以下の内容が投稿されました

私は呼声市役所を爆破します

この投稿は警察に通報され、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 脆弱性です。

演習として、クエリパラメタを調整して、不適切投稿をユーザーにさせてみてください。

results matching ""

    No results matching ""