セキュリティ:アカウント認証のセキュリティホールの修正

守矢諏訪子 <suwako@076.moe>、2026-11-10 15:52
コミット efb632fa2b08d47ad1a89be5acae4f69dd8fa626 ・親 3a8fe88 ・ファイルを見る

1個のファイル、+28 -18

@@ -175,14 +175,21 @@
175175 public function logout(?string $token = null): Result {
176176 if (!AUTH_ENABLED) return Result::Error('エラー:認証システムは無効です。');
177177 $userData = $this->getUserData();
178+ $token = $token ?? $this->token;
178179
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+ }
183190 }
184191
185- setcookie('kerozen', null, [
192+ setcookie('kerozen', '', [
186193 'expires' => 0,
187194 'path' => '/',
188195 'domain' => $_SERVER['SERVER_NAME'],
@@ -316,28 +323,31 @@
316323 private function getUserId(?string $username = null, ?string $token = null): int {
317324 if (!AUTH_ENABLED) return 0;
318325 if (!$username && !$token) return 0;
326+ if (!$username && !preg_match('/^[0-9a-f]{128}$/', $token)) return 0;
327+
319328 $file = scandir($this->dataDir);
320- $matches = [];
321- $id = 0;
329+ $now = time();
322330
323331 foreach ($file as $f) {
324- if ($f === '.kara' || $f === '.' || $f === '..') continue;
332+ if (!preg_match('/^(\d+)\.[A-Za-z0-9]+\.json$/', $f, $m)) continue;
325333
326334 $path = "{$this->dataDir}{$f}";
327335 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;
335342 }
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+ }
336348 }
337- if (!isset($matches[0])) return 0;
338- $id = (int)preg_replace('/^(.*?)\.(.*?)$/', "$1", str_replace($this->dataDir, '', $matches[0]));
339349
340- return $id;
350+ return 0;
341351 }
342352
343353 private function getUserData(): ?\stdClass {