セキュリティ:アカウント認証のセキュリティホールの修正
守矢諏訪子 <suwako@076.moe>、2026-11-10 15:52
コミット efb632fa2b08d47ad1a89be5acae4f69dd8fa626
・親 3a8fe88
・ファイルを見る
1個のファイル、+28 -18
| @@ -175,14 +175,21 @@ | ||
| 175 | 175 | public function logout(?string $token = null): Result { |
| 176 | 176 | if (!AUTH_ENABLED) return Result::Error('エラー:認証システムは無効です。'); |
| 177 | 177 | $userData = $this->getUserData(); |
| 178 | + $token = $token ?? $this->token; | |
| 178 | 179 | |
| 179 | - if (!$token) { | |
| 180 | - $userData->tokens = array_filter($userData->tokens, function($t): bool { | |
| 181 | - return ($t->expires ?? 0) > time(); | |
| 182 | - }); | |
| 180 | + if ($userData instanceof \stdClass && $token) { | |
| 181 | + $userData->tokens = array_values(array_filter($userData->tokens ?? [], function($t) use ($token): bool { | |
| 182 | + return isset($t->token) && !hash_equals((string)$t->token, $token) && ($t->expDate ?? 0) > time(); | |
| 183 | + })); | |
| 184 | + | |
| 185 | + $path = $this->dataDir.$this->id.'.'.$userData->username.'.json'; | |
| 186 | + $json = json_encode($userData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); | |
| 187 | + if (file_put_contents($path, $json) === false) { | |
| 188 | + logger(\LogType::Auth, '【logout】JSONファイルを変更出来なかった。chownをご確認下さい。パス:'.$path); | |
| 189 | + } | |
| 183 | 190 | } |
| 184 | 191 | |
| 185 | - setcookie('kerozen', null, [ | |
| 192 | + setcookie('kerozen', '', [ | |
| 186 | 193 | 'expires' => 0, |
| 187 | 194 | 'path' => '/', |
| 188 | 195 | 'domain' => $_SERVER['SERVER_NAME'], |
| @@ -316,28 +323,31 @@ | ||
| 316 | 323 | private function getUserId(?string $username = null, ?string $token = null): int { |
| 317 | 324 | if (!AUTH_ENABLED) return 0; |
| 318 | 325 | if (!$username && !$token) return 0; |
| 326 | + if (!$username && !preg_match('/^[0-9a-f]{128}$/', $token)) return 0; | |
| 327 | + | |
| 319 | 328 | $file = scandir($this->dataDir); |
| 320 | - $matches = []; | |
| 321 | - $id = 0; | |
| 329 | + $now = time(); | |
| 322 | 330 | |
| 323 | 331 | foreach ($file as $f) { |
| 324 | - if ($f === '.kara' || $f === '.' || $f === '..') continue; | |
| 332 | + if (!preg_match('/^(\d+)\.[A-Za-z0-9]+\.json$/', $f, $m)) continue; | |
| 325 | 333 | |
| 326 | 334 | $path = "{$this->dataDir}{$f}"; |
| 327 | 335 | if (!is_file($path)) continue; |
| 328 | - $content = file_get_contents($path); | |
| 329 | - if ($token && str_contains($content, $token)) { | |
| 330 | - $matches[] = $path; | |
| 331 | - break; | |
| 332 | - } else if (!$token && str_contains($content, '"username": "'.$username.'",')) { | |
| 333 | - $matches[] = $path; | |
| 334 | - break; | |
| 336 | + $data = json_decode(file_get_contents($path)); | |
| 337 | + if (!$data instanceof \stdClass) continue; | |
| 338 | + | |
| 339 | + if ($username) { | |
| 340 | + if (isset($data->username) && $data->username === $username) return (int)$m[1]; | |
| 341 | + continue; | |
| 335 | 342 | } |
| 343 | + | |
| 344 | + foreach (($data->tokens ?? []) as $t) { | |
| 345 | + if (!isset($t->token, $t->expDate)) continue; | |
| 346 | + if ($t->expDate > $now && hash_equals((string)$t->token, $token)) return (int)$m[1]; | |
| 347 | + } | |
| 336 | 348 | } |
| 337 | - if (!isset($matches[0])) return 0; | |
| 338 | - $id = (int)preg_replace('/^(.*?)\.(.*?)$/', "$1", str_replace($this->dataDir, '', $matches[0])); | |
| 339 | 349 | |
| 340 | - return $id; | |
| 350 | + return 0; | |
| 341 | 351 | } |
| 342 | 352 | |
| 343 | 353 | private function getUserData(): ?\stdClass { |