セキュリティ:キャッシュ
守矢諏訪子 <suwako@076.moe>、2026-11-10 17:14
コミット 6287571d0d719969124961801b55a1f9804e14f5
・親 62fe9d8
・ファイルを見る
3個のファイル、+36 -6
| @@ -263,7 +263,7 @@ | ||
| 263 | 263 | try { |
| 264 | 264 | $handle = ''; |
| 265 | 265 | if (isset($params['handle'])) $handle = $params['handle']; |
| 266 | - if ($handle === '') { | |
| 266 | + if (!$this->isValidHandle($handle)) { | |
| 267 | 267 | header('Location: /openprovider/listcustomers'); |
| 268 | 268 | exit(); |
| 269 | 269 | } |
| @@ -287,7 +287,7 @@ | ||
| 287 | 287 | try { |
| 288 | 288 | $handle = ''; |
| 289 | 289 | if (isset($params['handle'])) $handle = $params['handle']; |
| 290 | - if ($handle === '') { | |
| 290 | + if (!$this->isValidHandle($handle)) { | |
| 291 | 291 | header('Location: /openprovider/listcustomers'); |
| 292 | 292 | exit(); |
| 293 | 293 | } |
| @@ -342,7 +342,7 @@ | ||
| 342 | 342 | try { |
| 343 | 343 | $tld = ''; |
| 344 | 344 | if (isset($params['tld'])) $tld = $params['tld']; |
| 345 | - if ($tld === '') { | |
| 345 | + if (!preg_match('/^[A-Za-z0-9-]{1,63}$/', $tld)) { | |
| 346 | 346 | header('Location: /'); |
| 347 | 347 | exit(); |
| 348 | 348 | } |
| @@ -447,7 +447,7 @@ | ||
| 447 | 447 | try { |
| 448 | 448 | $domain = ''; |
| 449 | 449 | if (isset($params['domain'])) $domain = $params['domain']; |
| 450 | - if ($domain === '') { | |
| 450 | + if (!preg_match('/^(?=.{1,253}$)(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z0-9-]{2,63}$/', $domain)) { | |
| 451 | 451 | header('Location: /'); |
| 452 | 452 | exit(); |
| 453 | 453 | } |
| @@ -465,5 +465,18 @@ | ||
| 465 | 465 | } catch (\Exception $e) { |
| 466 | 466 | throw new \Exception($e->getMessage()); |
| 467 | 467 | } |
| 468 | + } | |
| 469 | + | |
| 470 | + ////////// | |
| 471 | + | |
| 472 | + /** | |
| 473 | + * 顧客ハンドルの形式を確認する(例:AB123456-XX) | |
| 474 | + * URLの値はAPIのパスとキャッシュのファイル名にそのまま入るので、先に確認する | |
| 475 | + * | |
| 476 | + * @param string $handle 顧客ハンドル | |
| 477 | + * @return bool 正しい形式ならtrue | |
| 478 | + */ | |
| 479 | + private function isValidHandle(string $handle): bool { | |
| 480 | + return preg_match('/^[A-Za-z0-9-]{1,64}$/', $handle) === 1; | |
| 468 | 481 | } |
| 469 | 482 | } |
| @@ -58,6 +58,7 @@ | ||
| 58 | 58 | * @return Result |
| 59 | 59 | */ |
| 60 | 60 | public function set(string $type, array $data): Result { |
| 61 | + if (!$this->isSafeName($type)) return Result::Error('エラー:不正なキャッシュ名。'); | |
| 61 | 62 | if (!file_exists($this->dataDir)) { |
| 62 | 63 | if (!mkdir($this->dataDir, 0755)) |
| 63 | 64 | return Result::Error('エラー:ユーザーのアイコンディレクトリの作成に失敗。'); |
| @@ -77,6 +78,7 @@ | ||
| 77 | 78 | * @return array |
| 78 | 79 | */ |
| 79 | 80 | public function get(string $type): array { |
| 81 | + if (!$this->isSafeName($type)) return []; | |
| 80 | 82 | if (!file_exists("{$this->dataDir}{$type}.json")) return []; |
| 81 | 83 | |
| 82 | 84 | $content = file_get_contents("{$this->dataDir}{$type}.json"); |
| @@ -95,6 +97,7 @@ | ||
| 95 | 97 | * @return Result |
| 96 | 98 | */ |
| 97 | 99 | public function murder(string $name): Result { |
| 100 | + if (!$this->isSafeName($name)) return Result::Error('エラー:不正なキャッシュ名。'); | |
| 98 | 101 | if (!file_exists("{$this->dataDir}{$name}.json")) return Result::Error('キャッシュファイルが存在しない。'); |
| 99 | 102 | if (!unlink("{$this->dataDir}{$name}.json")) return Result::Error('キャッシュファイルの削除に失敗。'); |
| 100 | 103 | return Result::Success(); |
| @@ -114,5 +117,17 @@ | ||
| 114 | 117 | && (isset($cache[$dataPoint]) |
| 115 | 118 | && $cache[$dataPoint] === $data) |
| 116 | 119 | && (isset($cache['lifespan']) && time() < ($cache['lifespan'] + $this->dataDuration))); |
| 120 | + } | |
| 121 | + | |
| 122 | + /////// | |
| 123 | + | |
| 124 | + /** | |
| 125 | + * キャッシュ名はファイル名になるので、英数字・「.」「_」「-」だけを許可する(「..」は不可) | |
| 126 | + * | |
| 127 | + * @param string $name キャッシュ名 | |
| 128 | + * @return bool 安全ならtrue | |
| 129 | + */ | |
| 130 | + private function isSafeName(string $name): bool { | |
| 131 | + return preg_match('/^[A-Za-z0-9._-]{1,200}$/', $name) === 1 && !str_contains($name, '..'); | |
| 117 | 132 | } |
| 118 | 133 | } |
| @@ -78,9 +78,11 @@ | ||
| 78 | 78 | if (!OPENPROVIDER_ENABLED) return Result::Error('エラー:OpenProviderは無効です。'); |
| 79 | 79 | $cacheName = 'login'; |
| 80 | 80 | $cache = $this->cache->get($cacheName); |
| 81 | - if (time() < ($this->lastAuth + $this->tokenDuration)) { | |
| 81 | + | |
| 82 | + // 以前は$lastAuthが0のまま比較していたので、キャッシュのトークンが使われず毎回パスワードを送っていた | |
| 83 | + $this->lastAuth = (int)($cache['last_auth'] ?? 0); | |
| 84 | + if (isset($cache['data']['token']) && time() < ($this->lastAuth + $this->tokenDuration)) { | |
| 82 | 85 | $this->token = $cache['data']['token']; |
| 83 | - $this->lastAuth = $cache['last_auth']; | |
| 84 | 86 | return Result::Success('', $cache); |
| 85 | 87 | } |
| 86 | 88 |