Help us understand the problem. What is going on with this article?

CSRFに関して

More than 1 year has passed since last update.

CSRFに関して

by nouka
1 / 15

CSRFとは

  • Webアプリケーションの「重要な処理(パスワードやメールアドレスの変更、決済、口座振込など)」は、ユーザが意図したリクエストか確認が必要。
  • 確認が抜けていると、罠サイトを閲覧しただけでユーザのブラウザから勝手に「重要な処理」が実行させられてしまう。
  • リクエスト強要とも。

以下のようなHTMLがあったとします。

<!-- // 正規のサイト -->
<form action="http://hoge.com/password_change.php" method="post">
  <input type="text" name="password">
  <input type="submit" value="変更する">
</form>

それに対して以下のようなHTMLを用意します。

<!-- // 罠サイト -->
<body onload="document.forms[0].submit()">
  <form action="http://hoge.com/password_change.php" method="post">
    <input type="hidden" name="password" value="cracked">
  </form>
</body>

password_change.php で確認の処理が抜けていると、パスワードが勝手に変更させられてしまう。


CSRFの対策


inputのhiddenにトークンを埋め込みます。

<?php
session_start();
if (empty($_SESSION['token'])) {
  $token = bin2hex(openssl_random_pseudo_bytes(24));
  $_SESSION['token'] = $token;
} else {
  $token = $_SESSION['token'];
}
?>
<form action="http://hoge.com/password_change.php" method="post">
  <input type="hidden" name="token" value="<?php echo htmlspecialchars($token, ENT_COMPAT, 'UTF-8'); ?>">
  <input type="text" name="password">
  <input type="submit" value="変更する">
</form>

パスワードの変更受付処理でトークンを検証します。

session_start();
$token = filter_input(INPUT_POST, 'token');
if (empty($_SESSION['token']) || $token !== $_SESSION['token']) {
  die('エラー');
}

  • 他にも「パスワードの再入力を求める」や「リファラをチェックする」といった対策がある。
  • トークンの埋め込みが最も一般的。

CSRF(Web APIの場合)


こんなAPIがあったとします。

session_start();
if (empty($_SESSION['uid'])) {
  header('HTTP/1.1 403 Forbidden');
  die('ログインしてください');
}
$req = json_decode(file_get_contents('php://input'));
// DBにpasswordをセットする疑似コード
$this->db->set('password', ['uid' => $_SESSION['uid'], 'password' => $req->password]);
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['result' => 'OK']);

それに対して以下のようなJavaScriptを実行させます。

var xhr = new XMLHttpRequest();
xhr.open("POST", "http://api.hoge.com/passward");
xhr.withCredentials = true;
xhr.send('{"password": "cracked"}');

APIで確認の処理が漏れていると、パスワードが勝手に変更させられてしまいます。


CSRFの対策(Web APIの場合)


ログイン(uidを取得)処理でトークンも生成。

if ($_SESSION['token']) {
  $token = bin2hex(openssl_random_pseudo_bytes(24));
  $_SESSION['token'] = $token;
}
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['uid' => $_SESSION['uid'], 'token' => $_SESSION['token']]);

APIでトークンを検証する。

session_start();
if (empty($_SESSION['uid'])) {
  header('HTTP/1.1 403 Forbidden');
  die('ログインしてください');
}
$token = $_SERVER['HTTP_X_CSRF_TOKEN'];
if (empty($token) || $token !== $_SESSION['token']) {
  header('HTTP/1.1 403 Forbidden');
  die('エラー');
}
...

まとめ

  • CSRFは攻撃者が罠サイトを作成し、ユーザーに罠サイトを閲覧させることで発動する。
  • HTMLフォームの場合もWeb APIの場合もトークンを生成し検証する方法が最も安全。
nouka
WEBデザイナーからエンジニアに転向し、現在はリードエンジニア的な立場で働いています。 好きな本:「コードコンプリート」「リーダブルコード」「レガシーコード改善ガイド」「JavaScript: The Good Parts」「メンタルブロックバスター」
https://www.wantedly.com/users/18366823
ozvision
購買プラットフォーム「ハピタス」を開発・運営するベンチャー企業
https://www.oz-vision.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした