: EasyBuggy - 30分でわかる!クロスサイトリクエストフォージェリ(CSRF)脆弱性


概要

EasyBuggyEasyBuggy Bootを利用して、クロスサイトリクエストフォージェリ(CSRF)脆弱性をシミュレートし、その修正を試みます。

環境構築はこちらの記事から。

注意事項

本記事では脆弱性をついた攻撃手法について解説しています。

しかし、実際に第三者が運用しているWebサービスなどに対して、勝手に脆弱性の検査をおこなうのは違法行為となる可能性が非常に高いです。無害な検査用文字列を送信しているだけのつもりであっても、意図しない破壊を招いたり、監視システムによって攻撃と勘違いされる可能性があります。

絶対にやめましょう。

参考事例: 脆弱性検査について

CSRF脆弱性の怖さ

古くは2005年にURLをクリックすると、勝手に「ぼくはまちちゃん!」というタイトルでmixiに日記が投稿されてしまうという現象が多発しました。これは、当時のmixiにCSRF脆弱性があり、それを攻撃されたものです。

また、2012年には「横浜CSRF事件」(パソコン遠隔操作事件)が世間を騒がせました。こちらも、横浜市(保土ケ谷区)の投稿フォームに存在していたCSRF脆弱性を攻撃されたことにより、被害を受けたコンピュータから犯罪予告の書き込みがおこなわれてしまったというものです。

現在では、多くのフレームワークがCSRF対策を備えています。このため、最も注意すべき脆弱性と対処法をまとめているOWASP Top 10 2017からは外れています。しかし、相変わらず対策が必要な攻撃手法ではあります。

脆弱性を確認する

通常操作時の動作を確認する

EasyBuggyを起動し、脆弱性 > CSRF (クロスサイトリクエストフォージェリ)を選択します。

管理者ログインページが表示された場合は、ユーザーIDにadmin、パスワードにpasswordを入力してログインします。
なお、この認証情報はEmbeddedADSクラスにて定義されているものです。

ログインに成功すると、パスワード変更画面が表示されます。

任意のパスワード文字列を入力し、送信ボタンを押します。
すると、パスワードは正常に変更されました。というメッセージが表示されます。これは通常の動作です。

HTTPステータス 404 – 見つかりません。 というエラー画面が表示される場合は、セットアップ時にパスを変更しているかどうかを再確認してください。

脆弱性をついた攻撃を試みる

CSRF脆弱性は、攻撃者が用意した罠ページに対して、ターゲットがアクセスすることで成立する脆弱性です。

1. (攻撃者が)罠ページを用意する

まず、下記のようなHTMLファイルを用意しておきます。
用意したHTMLファイルは、任意の箇所に保存しておきます。(ローカルでも構いません)

formタグのaction属性で指定しているURL内のポート番号は、環境に合わせて適宜修正してください。

<!DOCTYPE html>
<html>
<body>
  <form action="http://localhost:9080/admins/csrf" method="post">
  <input name="password" type="hidden" value="password2">
  <input type="submit" value="Don't push me!">
  </form>
</body>
</html>

2. (ターゲットが)管理者ログインページからログインする

ターゲットのつもりで、管理者ログインページからログインします。
CSRF脆弱性を成立させるためには、この「ターゲットがログイン状態である」という点が重要です。

3. (ターゲットが)罠ページにアクセスする

別タブを開き、先ほど(攻撃者が)作成した罠ページにアクセスします。

そして、ページに表示されている Don't push me! ボタンを押します。
(「押すなよ!」と言われると、押したくなるものですね)

その結果、ターゲットが利用しているパスワードが変更されてしまいます。
攻撃者は、変更されたパスワードを利用して、ターゲットになりすましてログインできるでしょう。

補足情報

今回は、攻撃の成立のためには、ターゲットがボタンを押すことが必要でした。
また、攻撃成立後は、正規サイトの「パスワードが変更された」旨のメッセージが表示されます。
このため、ターゲットは何か意図しないことが起きたことに気づきやすいです。

しかし、罠ページの作り方によっては、ターゲットに攻撃の成立を意識させないこともできてしまうでしょう。
(詳細な例はここでは紹介しません)

脆弱性を修正する

CSRF脆弱性を修正するには、すでに広く使われている対策ライブラリを利用するのがよいでしょう。
(プロジェクト内で修正方法が指定されている場合は、それに従ってください)

EasyBuggyの場合

今回は、Apache Tomcatで利用できるCSRF Prevention Filterを利用して対策してみます。
CSRF Prevention Filterでは、攻撃者には推測不可能な乱数(nonce)を埋め込むことで、正規のリクエストか否かを識別します。

1. web.xmlの編集

src/main/webapp/WEB-INF/web.xml を開き、<web-app> 要素内に下記を追記します。

この記述により、CSRF脆弱性からの保護対象とするURLを設定します。

web.xml
<filter>
  <filter-name>CsrfFilter</filter-name>
  <filter-class>org.apache.catalina.filters.CsrfPreventionFilter</filter-class>
  <init-param>
    <param-name>entryPoints</param-name>
    <param-value>/admins/csrf</param-value>
  </init-param>
</filter>
<filter-mapping>
    <filter-name>CsrfFilter</filter-name>
    <url-pattern>/admins/csrf</url-pattern>
</filter-mapping>

2. formタグの書き換え

CSRF Prevention Filterでは、formの送信先を HttpServletResponse#encodeURL(String) で処理することで、nonceを付与します。

EasyBuggyでは、formタグの出力処理はCSRFServletクラスに実装されています。
formタグの出力処理を下記のように書き換えましょう。

bodyHtml.append("<form action=\"" + res.encodeURL("/admins/csrf") + "\" method=\"post\">");

3. 動作確認

正しく修正できていれば、罠ページのボタンをクリックすると、今度は下記のようなエラー画面に遷移するはずです。

また、通常操作時には、パスワード変更完了画面のURLの末尾に ?org.apache.catalina.filters.CSRF_NONCE={ランダムな文字列} という文字列が付与されています。この文字列がサーバー側で保持しているものと一致しているかを確認して、正規のリクエストかどうかを判定しているわけですね。

EasyBuggy Bootの場合

EasyBuggy BootはSpring Bootベースのアプリケーションです。
このため、同じSpring FrameworkであるSpring Securityを利用して修正してみましょう。

1. pom.xmlの編集

pom.xmlの<dependencies>要素内に下記を追記しましょう。

pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

追記が終わったら、プロジェクトをリフレッシュしてください。

Referenced Librariesに下記のようなライブラリが追加されているはずです。

  • spring-boot-starter-security-x.x.x.RELEASE.jar
  • spring-securty-config-x.x.x.RELEASE.jar
  • spring-securty-core-x.x.x.RELEASE.jar
  • spring-securty-web-x.x.x.RELEASE.jar

うまくライブラリがReferenced Librariesに反映されていない場合は、mvn eclipse:eclipseコマンドの実行を試してみてください。

2. 設定クラスの作成

そのままではアプリケーションのすべてのページにBasic認証がかかってしまいます。このため、下記のクラスを作成して解除します。

SecurityConfiguration.java
package org.t246osslab.easybuggy4sb;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // デフォルトでアプリケーション全体に有効となるBasic認証を解除
        http.authorizeRequests().antMatchers("/").permitAll();
    }
}

3. Thymeleafのテンプレートファイルの編集

EasyBuggyと同様に、<form>タグにnonceを埋め込みます。

EasyBuggy Bootでは、プレゼンテーション層(ユーザー・インターフェイス層)にThymeleafを利用しています。
このため、Thymeleafのテンプレートファイルであるsrc/main/resouces/templates/csrf.htmlを編集します。

ファイル内の<form>タグに、th:action属性を追加します。

csrf.html
<form th:if="${complete == null}" th:action="@{/admins/csrf}" action="/admins/csrf" method="post">

4. 動作確認

正しく修正できていれば、罠ページのボタンをクリックすると、今度は下記のようなエラー画面に遷移するはずです。

また、通常操作時には、新しいパスワードの入力画面にて、<form>タグ内に下記のHTMLタグが追加されています。
<input type="hidden" name="_csrf" value="{ランダムな文字列}">

これがサーバー側で保持しているものと一致しているかを確認して、正規のリクエストかどうかを判定しています。

最後に

これらのCSRF対策を、各処理に埋め込んでいく必要があります。機能が増えれば増えるほど対応が必要な箇所が増えていくため、大変ですね。

このため、最近ではSameSite属性を利用することによる対策が手軽だと思います。詳細は下記の記事からどうぞ。

関連資料