CSRFトークンの追加

諏訪子 <suwako@076.moe>、2025-12-08 03:18
コミット 4085b77f6a02fd0c3bd77e8289d3e5b366e83f33 ・親 d810b71 ・ファイルを見る

5個のファイル、+61 -44

@@ -18,10 +18,16 @@
1818 exit();
1919 }
2020
21- $doLogin = count($_POST) > 0;
21+ $doLogin = $_SERVER['REQUEST_METHOD'] === 'POST';
2222 $error = '';
2323
2424 if ($doLogin) {
25+ if (!\verify_csrf_token($_POST['csrf_token'])) {
26+ header('Location: /');
27+ exit();
28+ }
29+ unset($_POST['csrf_token']);
30+
2531 $a = [];
2632 if (count($_POST) === 2) {
2733 $i = 0;
@@ -91,6 +97,12 @@
9197 $nyuE = '';
9298
9399 if ($doRegister) {
100+ if (!\verify_csrf_token($_POST['csrf_token'])) {
101+ header('Location: /');
102+ exit();
103+ }
104+ unset($_POST['csrf_token']);
105+
94106 $a = [];
95107 if (count($_POST) === 4) {
96108 $i = 0;
@@ -51,31 +51,19 @@
5151 }
5252
5353 $userData = $this->purgeOldTokens($userData);
54- $expire = 0;
55- $tokenExist = false;
54+ $token = bin2hex(random_bytes(64));
55+ $expire = time() + $this->tokenDuration;
56+ $newToken = [
57+ 'token' => $token,
58+ 'ip' => $ip,
59+ 'createDate' => time(),
60+ 'expDate' => $expire,
61+ 'lastDate' => time(),
62+ 'userAgent' => $_SERVER['HTTP_USER_AGENT'] ?? '不明'
63+ ];
5664
57- foreach ($userData->tokens as $t) {
58- if ($t->ip === $ip) {
59- $tokenExist = true;
60- break;
61- }
62- }
65+ $userData->tokens[] = $newToken;
6366
64- if (!$tokenExist) {
65- $token = bin2hex(random_bytes(64));
66- $expire = time() + $this->tokenDuration;
67- $newToken = [
68- 'token' => $token,
69- 'ip' => $ip,
70- 'createDate' => time(),
71- 'expDate' => $expire,
72- 'lastDate' => time(),
73- 'userAgent' => $_SERVER['HTTP_USER_AGENT'] ?? '不明'
74- ];
75-
76- $userData->tokens[] = $newToken;
77- }
78-
7967 $path = $this->dataDir.$this->id.'.'.$userData->username.'.json';
8068 $json = json_encode($userData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
8169
@@ -83,20 +71,15 @@
8371 return \Result::error('エラー:ユーザーデータの保存に失敗。');
8472 }
8573
86- if (!$tokenExist) {
87- $secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
88- $domain = $_SERVER['SERVER_NAME'];
89-
90- if (!setcookie('kerozen', $token, [
91- 'expires' => $expire,
92- 'path' => '/',
93- 'domain' => $domain,
94- 'secure' => $secure,
95- 'httponly' => true,
96- 'samesite' => 'Strict'
97- ])) {
98- return \Result::error('エラー:クッキーを設定に失敗。');
99- }
74+ if (!setcookie('kerozen', $token, [
75+ 'expires' => $expire,
76+ 'path' => '/',
77+ 'domain' => $_SERVER['SERVER_NAME'],
78+ 'secure' => (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'),
79+ 'httponly' => true,
80+ 'samesite' => 'Strict'
81+ ])) {
82+ return \Result::error('エラー:クッキーを設定に失敗。');
10083 }
10184
10285 return \Result::success('ログイン成功');
@@ -111,8 +94,6 @@
11194
11295 public function logout(?string $token = null): \Result {
11396 if (!AUTH_ENABLED) return \Result::Error('エラー:認証システムは無効です。');
114- $secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
115- $domain = $_SERVER['SERVER_NAME'];
11697 $userData = $this->getUserData();
11798
11899 if (!$token) {
@@ -124,8 +105,8 @@
124105 setcookie('kerozen', null, [
125106 'expires' => 0,
126107 'path' => '/',
127- 'domain' => $domain,
128- 'secure' => $secure,
108+ 'domain' => $_SERVER['SERVER_NAME'],
109+ 'secure' => (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'),
129110 'httponly' => true,
130111 'samesite' => 'Strict'
131112 ]);
変更 util.php
@@ -160,6 +160,24 @@
160160
161161 return $color.$suffix;
162162 }
163+
164+ function make_csrf_token(?bool $force = false): string {
165+ if (null !== getcookie('csrf_token') && !$force) return getcookie('csrf_token');
166+ $token = bin2hex(random_bytes(32));
167+ setcookie('csrf_token', $token, [
168+ 'expires' => time() + 300, // 5分
169+ 'path' => '/',
170+ 'domain' => $_SERVER['SERVER_NAME'],
171+ 'secure' => (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'),
172+ 'httponly' => true,
173+ 'samesite' => 'Strict'
174+ ]);
175+ return $token;
176+ }
177+
178+ function verify_csrf_token(string $token): bool {
179+ return hash_equals(getcookie('csrf_token'), $token);
180+ }
163181 }
164182
165183 function count_special_chars(string $str): int {
@@ -28,7 +28,10 @@
2828 </tr>
2929 <tr>
3030 <td></td>
31- <td><button>サインイン</button></td>
31+ <td>
32+ <input type="hidden" id="csrf_token" name="csrf_token" value="{{ make_csrf_token() }}" />
33+ <button>サインイン</button>
34+ </td>
3235 </tr>
3336 </tbody>
3437 </table>
@@ -38,7 +38,10 @@
3838 </tr>
3939 <tr>
4040 <td></td>
41- <td><button>登録</button></td>
41+ <td>
42+ <input type="hidden" id="csrf_token" name="csrf_token" value="{{ make_csrf_token() }}" />
43+ <button>登録</button>
44+ </td>
4245 </tr>
4346 </tbody>
4447 </table>