1. nouka

    No comment

    nouka
Changes in body
Source | HTML | Preview
@@ -1,161 +1,168 @@
## CSRFとは
- Webアプリケーションの**「重要な処理(パスワードやメールアドレスの変更、決済、口座振込など)」**は、ユーザが意図したリクエストか確認が必要。
- 確認が抜けていると、罠サイトを閲覧しただけで**ユーザのブラウザから勝手に**「重要な処理」が実行させられてしまう。
- リクエスト強要とも。
----
以下のようなHTMLがあったとします。
```html
<!-- // 正規のサイト -->
<form action="http://hoge.com/password_change.php" method="post">
<input type="text" name="password">
<input type="submit" value="変更する">
</form>
```
----
それに対して以下のような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>
```
`password_change.php` で確認の処理が抜けていると、パスワードが勝手に変更させられてしまう。
----
## 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>
```
----
パスワードの変更受付処理でトークンを検証します。
```php
session_start();
$token = filter_input(INPUT_POST, 'token');
if (empty($_SESSION['token']) || $token !== $_SESSION['token']) {
die('エラー');
}
```
----
- 他にも「パスワードの再入力を求める」や「リファラをチェックする」といった対策がある。
- トークンの埋め込みが最も一般的。
----
## 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側はクロスオリジンの呼び出しを許可していないためレスポンスは参照できないが、リクエストが到達さえすれば攻撃は成立
+APIで確認の処理が漏れていると、パスワードが勝手に変更させられてしまいます
----
-## CSRF対策
+## CSRFの対策(Web APIの場合)
+----
ログイン(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']) {
+if (empty($token) || $token !== $_SESSION['token']) {
header('HTTP/1.1 403 Forbidden');
die('エラー');
}
...
```
----
-## CSRF対策
-- HTTP_X_CSRF_TOKENのように、カスタムヘッダを付与することでプリフライトリクエストが飛ぶ。
-- プリフライトリクエストを適切に処理すればCSRFも防ぐことができる。
-- トークンを検証する方法はCORSの実装に多少不備があっても大丈夫なので、最も安全
+- Web APIであっても基本的に対策方法は変わらない。
+- Web APIは不特定多数のサイトに公開することが想定されるケースも多いので、注意が必要。
+- CORSを間違って実装してしまうと、トークンも意味なくなってしまう事がある。
+
+----
+## 同一オリジンポリシー
+----
+- **JavaScript等のクライアントスクリプトで**サイトをまたがったアクセスを禁止する、セキュリティ上の制限。
+- 先の例でトークンを取得するAPIを実行しても、**レスポンスは第三者には読み取れない**。
+
+----
+- HTMLフォームやaリンクなどは同一オリジンポリシーは適用されない。
+- CSRFではリクエストがサーバに到達すれば攻撃が成功するので、レスポンスが見れなくても関係ない。
+
+----
+- 同一オリジンポリシーによる防御を働かせたい場合は、MIME Typeをapplication/jsonに限定するなどが必要。
+
+----
+## 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をレスポンスすることで、後続のリクエストを許可することができる。
+
+----
+- `Access-Control-Allow-Origin: *` で許可してしまうと、トークンによる対策も意味がなくなってしまう
----
## まとめ
- CSRFは攻撃者が罠サイトを作成し、ユーザーに罠サイトを閲覧させることで発動する。
- HTMLフォームの場合もWeb APIの場合もトークンを生成し検証する方法が最も安全。
-- Web APIでCSRF対策するにはCORS等の知識も必要
+- CORS対応のWeb APIを開発する場合は、何も考えず全てのオリジンを許可は絶対しないように