1. nouka

    Posted

    nouka
Changes in title
+CSRFに関して
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,163 @@
+## CSRFとは
+以下のようなHTMLがあったとします。
+
+```html
+<!-- // 正規のサイト -->
+<form action="http://hoge.com/password_change.php" method="post">
+ <input type="text" name="password">
+ <input type="submit" value="変更する">
+</form>
+```
+----
+## CSRFとは
+それに対して以下のようなHTMLを用意します。
+
+```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>
+```
+----
+## CSRFとは
+- ユーザーが罠サイトを閲覧しただけで、パスワードが変更されてしまう。
+- ログインが必要だったとしても、CookieとしてセッションIDが送信されるので変更できる。
+- リクエストがサーバに届いた時点で攻撃が成立する。
+
+----
+## CSRF対策
+inputのhiddenにトークンを埋め込みます。
+
+```php
+<?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>
+```
+----
+## CSRF対策
+パスワードの変更受付処理でトークンを検証します。
+
+```php
+session_start();
+$token = filter_input(INPUT_POST, 'token');
+if (empty($_SESSION['token']) || $token !== $_SESSION['token']) {
+ die('エラー');
+}
+```
+
+----
+## CSRF対策
+- 他にも、パスワードの再入力を求める、や、リファラをチェックする、といった対策がある。
+- トークンの埋め込みが最も一般的。
+
+----
+## CSRF(Web APIの場合)
+- 基本的には普通のCSRFと変わらない。
+- MIMEタイプやCORSを意識する必要がある。
+
+----
+## CORS
+- 同一オリジンポリシーの制限を超えて、サイト間でデータをやり取りするための仕様。
+- シンプルなリクエストの場合は、相手側の許可はいらない。
+
+----
+## シンプルなリクエスト
+- HTTPメソッドがGET, POST, HEADのいずれか。
+- HTTPヘッダにAccept, Accept-Language, Content-Language, Content-Type以外のフィールドが含まれない。
+- Content-Typeの値はapplication/x-www-form-urlencoded, multipart/form-data, text/plainのいずれか。
+
+----
+## プリフライトリクエスト
+- シンプルなリクエストの条件を満たさない場合に、ブラウザが実際のリクエストの前に送信するリクエスト。
+- Access-Control-Request-Method, Access-Conrol-Request-Headers, Originヘッダをリクエストする。
+
+----
+## プリフライトリクエスト
+- リクエストに対してAccess-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Originをレスポンスすることで、後続のリクエストを許可することができる。
+
+----
+## CSRF(Web APIの場合)
+こんなAPIがあったとします。
+
+```php
+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']);
+```
+
+----
+## CSRF(Web APIの場合)
+それに対して以下のようなJavaScriptを実行させます。
+
+```js
+var xhr = new XMLHttpRequest();
+xhr.open("POST", "http://api.hoge.com/passward");
+xhr.withCredentials = true;
+xhr.send('{"password": "cracked"}');
+```
+
+----
+## CSRF(Web APIの場合)
+- withCredentials=trueなので$_SESSIONが使えてしまう。
+- API側はクロスオリジンの呼び出しを許可していないためレスポンスは参照できないが、リクエストが到達さえすれば攻撃は成立。
+
+----
+## CSRF対策
+ログイン(uidを取得)処理でトークンも生成。
+
+```php
+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']]);
+```
+----
+## CSRF対策
+APIでトークンを検証する。
+
+```php
+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対策
+- HTTP_X_CSRF_TOKENのように、カスタムヘッダを付与することでプリフライトリクエストが飛ぶ。
+- プリフライトリクエストを適切に処理すればCSRFも防ぐことができる。
+- トークンを検証する方法はCORSの実装に多少不備があっても大丈夫なので、最も安全。
+
+----
+## まとめ
+- CSRFは攻撃者が罠サイトを作成し、ユーザーに罠サイトを閲覧させることで発動する。
+- HTMLフォームの場合もWeb APIの場合もトークンを生成し検証する方法が最も安全。
+- Web APIでCSRF対策するにはCORS等の知識も必要。