1. nouka

    No comment

    nouka
Changes in body
Source | HTML | Preview
@@ -1,163 +1,161 @@
## CSRFとは
+- Webアプリケーションの**「重要な処理(パスワードやメールアドレスの変更、決済、口座振込など)」**は、ユーザが意図したリクエストか確認が必要。
+- 確認が抜けていると、罠サイトを閲覧しただけで**ユーザのブラウザから勝手に**「重要な処理」が実行させられてしまう。
+- リクエスト強要とも。
+
+----
以下のような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が送信されるので変更できる。
-- リクエストがサーバに届いた時点で攻撃が成立する。
-
+`password_change.php` で確認の処理が抜けていると、パスワードが勝手に変更させられてしまう。
----
-## CSRF対策
-
+## 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等の知識も必要。