Absence of Anti-CSRF Tokens

概要

  • 脆弱性の名前: Absence of Anti-CSRF Tokens (CSRF対策トークンの欠如)
  • 問題の要点: WebアプリケーションがCSRF(Cross-Site Request Forgery)攻撃に対する対策を講じていないため、攻撃者がユーザーの意図しない操作を強制的に実行させられる脆弱性。
  • よくある発生シーン: フォーム送信、APIエンドポイント、状態を変更するリクエストを処理するWebアプリケーション

背景

CSRF攻撃は、ユーザーが認証済みの状態で悪意のあるWebサイトを閲覧した際に、そのWebサイトからユーザーの意図しないリクエストをWebアプリケーションに送信することで発生します。
CSRF対策トークンは、リクエストが正規のユーザーによって送信されたものであることを検証するために使用されます。このトークンが存在しない場合、攻撃者はユーザーになりすまして操作を実行できる可能性があります。
近年、Webアプリケーションの複雑化に伴い、CSRF対策の重要性が高まっています。

セキュリティ上のリスク

  • 攻撃者がユーザーになりすまして、アカウント情報の変更、パスワードの変更、商品の購入などの操作を実行する。
  • 企業や組織の評判低下や顧客からの信頼失墜。

対処方法の具体例

PHP

誤った設定例

CSRF対策トークンを使用せずにフォームを送信する例:

<?php
// 誤った例: CSRF対策なし
?>
<form action="/profile/update" method="post">
    <input type="text" name="username" value="<?php echo $username; ?>">
    <input type="submit" value="Update">
</form>

正しい設定例

CSRF対策トークンを生成し、フォームに含めて検証する例:

<?php
// 正しい例: CSRF対策
session_start();
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
$csrf_token = $_SESSION['csrf_token'];
?>
<form action="/profile/update" method="post">
    <input type="hidden" name="csrf_token" value="<?php echo $csrf_token; ?>">
    <input type="text" name="username" value="<?php echo $username; ?>">
    <input type="submit" value="Update">
</form>
<?php
// リクエストの検証
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $csrf_token) {
        die('CSRF token validation failed');
    }
    // ...
}
?>

Python (Flask)

誤った設定例

CSRF対策なしでフォームを処理する例:

# 誤った例
from flask import Flask, request, render_template

app = Flask(__name__)

@app.route('/profile/update', methods=['POST'])
def update_profile():
    username = request.form['username']
    # ...
    return 'Profile updated'

正しい設定例

Flask-WTFを使用してCSRF対策を実装する例:

# 正しい例
from flask import Flask, request, render_template
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
from flask_wtf.csrf import CSRFProtect

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your_secret_key'  # 秘密鍵を設定
csrf = CSRFProtect(app)

class UpdateProfileForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired()])
    submit = SubmitField('Update')

@app.route('/profile/update', methods=['GET', 'POST'])
def update_profile():
    form = UpdateProfileForm()
    if form.validate_on_submit():
        username = form.username.data
        # ...
        return 'Profile updated'
    return render_template('update_profile.html', form=form)

Java (Spring)

誤った設定例

CSRF対策なしでフォームを処理する例:

// 誤った例
@PostMapping("/profile/update")
public String updateProfile(@RequestParam("username") String username) {
    // ...
    return "profileUpdated";
}

正しい設定例

Spring Securityを使用してCSRF対策を実装する例:

// 正しい例
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().and() // CSRF対策を有効化
            .authorizeRequests()
                .antMatchers("/profile/update").authenticated()
                .anyRequest().permitAll()
            .and()
            .formLogin();
    }
}

JavaScript (フロントエンド)

誤った設定例

APIリクエストにCSRFトークンを含めない例:

// 誤った例
fetch('/api/profile/update', {
    method: 'POST',
    body: JSON.stringify({ username: 'new_username' }),
    headers: { 'Content-Type': 'application/json' }
});

正しい設定例

CookieからCSRFトークンを取得し、APIリクエストのヘッダーに含める例:

// 正しい例
function getCookie(name) {
    const value = `; ${document.cookie}`;
    const parts = value.split(`; ${name}=`);
    if (parts.length === 2) return parts.pop().split(';').shift();
}

const csrfToken = getCookie('csrftoken'); // Djangoの例

fetch('/api/profile/update', {
    method: 'POST',
    body: JSON.stringify({ username: 'new_username' }),
    headers: {
        'Content-Type': 'application/json',
        'X-CSRFToken': csrfToken
    }
});

Nginx

Nginx自体はCSRF対策を提供するものではありませんが、リバースプロキシとして使用する場合、バックエンドアプリケーションが生成したCSRFトークンを適切に処理する必要があります。

AWS

AWS WAFを使用して、CSRF攻撃を防御することができます。WAFのルールを設定し、リクエストにCSRFトークンが含まれているかどうかを検証します。

検出方法

OWASP ZAPでの出力例

  • Alert 名: Absence of Anti-CSRF Tokens
  • リスク: Middle
  • URL: CSRF対策トークンが欠如しているURL
  • パラメータ: なし
  • 詳細: 脆弱性が存在する場所と、CSRF攻撃が可能であるという情報

手動再現例

  1. Webアプリケーションのフォームがあるページにアクセスします。
  2. 開発者ツールを開き、フォームのHTMLソースを確認します。
  3. CSRF対策トークン(csrf_tokenなどの名前のhidden input)が存在しない場合、CSRF対策が講じられていない可能性があります。
  4. Burp Suiteなどのツールを使用して、リクエストをキャプチャし、CSRFトークンなしでリクエストを再送信します。
  5. リクエストが成功した場合、CSRF攻撃が可能であると判断できます。

まとめ

  • CVSS 基本値: 4.7 (Middle)
  • 運用チームや開発者が意識すべきポイント:
    • すべてのフォームとAPIエンドポイントでCSRF対策を実装する。
    • CSRFトークンを適切に生成、保存、検証する。
    • フレームワークが提供するCSRF対策機能を活用する。
    • 定期的にペネトレーションテストを実施し、脆弱性を特定する。
  • 再発防止:
    • 開発プロセス全体でセキュリティを考慮する(Security by Design)。
    • コードレビューを実施し、CSRF対策の欠如を早期に発見する。
    • 自動脆弱性診断ツールを導入し、定期的にスキャンを行う。
    • 開発者向けのセキュリティトレーニングを実施する。

補足資料・参考URL

以上の対策と検出方法を活用して、CSRF攻撃のリスクを低減してください。