会社のクラウドに空いていた「誰でもコードを実行できる入口」を4つ閉じた。仕様書に無い4本目を見つけた話
前回の最後に「次は収益導線の前提が初日で崩れた話を書く」と予告したけど、それは次に回す。間に1本、割り込みの話が入った。
先週、本業で使っている研修システムの中身をClaude Codeと一緒に調べていたら、送りつけたプログラムをそのまま実行して結果を返してくる入口が、認証なしで外に開いたまま動いているのが見つかった。1本ではなく4本。翌日には4本とも認証を必須にして、外から叩いて全部弾かれるところまで確認した。今はもう閉じている。
今回はその2日間の話を書く。技術的に難しかった部分はほとんど無くて、詰まったのは全部それ以外だった。
見つかり方は、まったくの副産物だった
うちの会社(本業のHiyoku合同会社のほう)は、エンジニア研修用の学習システムを持っている。数年前に作ったもので、俺は非エンジニアなので中身の作りは知らない。作った後の棚卸しをしてこなかったのは、これから書くとおり全部こっちの話。今それを一から作り直していて、その過程で「昔の採点処理はどうやって動いてるんだっけ」をClaude Codeに調べさせていた。
出てきた答えが想定外だった。採点は、今のシステムの中で動いているのではなく、別のクラウド上にある独立したサービス4本で動いていた。受講者が書いたプログラムを、そこに投げて、向こうで実行して、結果を返す。学習システムとしてはまっとうな作りだと思う。
先に範囲だけ書いておくと、開いていたのは**「送られたプログラムを実行して結果を返す」入口だけ**で、受講者の学習記録はまったく別の場所にある。そこに手が届く入口ではなかった。
問題は、そのサービスに認証が付いていなかったこと。つまり、URLさえ知っていれば、うちの受講者でなくても、そこに好きなプログラムを送りつけて実行させられる。しかも料金は使った分だけの従量課金で、支払っているのはうちだ。
深夜にこれを読んで、手が止まった。
最初の壁は、技術ではなく「入れない」だった
閉じ方自体は簡単だ。管理画面を開いて、「誰でも呼べる」設定を外せばいい。5分で終わる。
そう思って翌朝ログインしたら、自分の会社のシステムなのに、そのプロジェクトが開けなかった。
Claude Codeに指示して一つずつ確認していった結果はこうだった。俺のアカウントは会社ドメインの管理者権限を持っている。持っているのに、そのプロジェクトは見えない。理由は単純で、プロジェクトが会社の組織の外に置かれていたから。個人のアカウントで立ち上げて、後から会社の管理下に移す——この後半の手順を、会社として誰にも踏ませていなかった。会社の管理者権限は、組織の外には届かない。
支払いを調べると、もっと据わりが悪かった。会社の口座から毎月引き落としが立っている。去年の秋は月6,000円台、今年に入ってからは1万円前後。1年分並べても止まっている月は無い。お金は会社から出続けているのに、その請求先に会社の誰も到達できない状態が、たぶん1年以上続いていた。
ここで一度、俺の中の「今週中に応急処置をする」という計画が丸ごと崩れた。予算の上限を付けるのも、サービスを止めるのも、全部プロジェクトに入れることが前提だったからだ。
結局これは技術では解決しなかった。連絡を取って、権限を渡してもらった。会社としてちゃんと一度頼む、という当たり前の手順を踏むのに半日かかった。ここは省略しようがなかったと思う。
人間のアカウントを諦めて、機械用のアカウントに切り替えた
権限をもらって、自分のアカウントに管理者を付けた。付けたのに、しばらく経っても「権限がありません」が消えない。
10分ほど原因を探ったところで、俺はClaude Codeに方針の切り替えを指示した。人間のアカウントの権限で戦うのをやめて、機械用のアカウント(サービスアカウント)の鍵を作り、それでローカルから全部操作するという形にした。人間のアカウントは、招待の受諾だとか、組織のポリシーだとか、画面の裏側で効いている条件が多すぎて、原因の切り分けに時間を持っていかれる。機械用のアカウントは、鍵を持っているかどうかだけで決まるので迷子にならない。
これが正解だった。以降の作業は全部ローカルのコマンドで通り、俺は元の持ち主のアカウントに二度と入らずに済んでいる。ちなみに最初の「権限が効かない」の正体は、管理者への招待を受け取ったまま承諾していなかっただけだった。バグでもポリシーでもなく、ただの受諾待ち。半日後に承諾して、そこも解決した。
引き継ぎ資料に書いていない4本目が動いていた
ここがこの記事で一番書きたかったところ。
閉じるべきサービスの一覧は、以前Claude Codeにまとめさせた仕様書に書いてあった。3本は独立したサービス、残り1本は「今のシステムの中を経由して呼んでいる」と書いてある。その通りなら、閉じるのは3本でいい。
俺はそこで念のため、書いてある一覧を使わずに、そのクラウドの全地域を機械的に洗って「今生きているサービス」を全部並べるようClaude Codeに指示した。出てきたのは4本。仕様書が「中を経由している」と書いていた4本目は、実際には独立したサービスとして単体で生きていて、そのまま外から叩ける状態だった。
仕様書を信じて作業していたら、3本閉じて満足して、1本開けたまま「対応完了」と書いていた。これが今回一番の収穫だと思っている。**設計書に書いてあるのは「作ったときにそうするつもりだった形」であって、今動いているものの一覧ではない。**閉じたかどうかを確かめるときは、資料ではなく現物を数えるしかない。
閉じた直後に叩いたら、普通に動いた
4本の設定を外して、すぐに外からURLを叩いた。**200が返ってきた。**動いている。
一瞬「設定が効いていないのか」と思ったけど、少し待ってもう一度叩いたら403、つまり拒否になった。この手の権限変更は、反映が行き渡るまで最大2分ほどかかる。変更した数秒後の結果で合否を判定してはいけないという、地味だけど今後ずっと効く教訓だった。うちは自動化を作るときに「本当に閉じたか」を機械に確認させることが多いので、そこの待ち時間を入れておかないと、閉じていないものを閉じたと報告する仕組みが出来上がる。
最終的に、4本すべてで外部からの実行が拒否されることを確認した。学習システム本体の側からも同じ処理を呼んでみて、そちら経由の抜け道が無いことも確かめた。
悪用されていたかは、正直「たぶん」までしか言えない
一番気になるのはそこだと思う。俺が確認できたのは支払いの推移だけで、1年分を並べても不自然な跳ね上がりは無かった。誰かが大量にプログラムを走らせていれば従量課金なので金額に出る。出ていない。
ただそれは「たぶん無かった」であって、証明ではない。それに、仮に何も起きていなかったとしても、見つかったきっかけが別件の調べ物の副産物だったという事実のほうが問題として大きい。誰も点検していなかったから、点検で見つからず、たまたま隣を掘っていて見つかった。
ついでに、依存を1個減らした
せっかく入れたので、閉じる以外のこともまとめて済ませた。
12月に採点機能を復活させる予定があって、そのために模範解答とテストケースが要る。これも同じクラウドの中にあって、今後アクセスできなくなると詰む材料だった。全部ローカルに吸い出して手元に置いた。合わせて、月の予算に対する警告メールが自分に届くようにもした(上限で自動停止させる機能ではないので、あくまで気づくための仕掛け)。
これで、この件は「元の持ち主に連絡しないと何もできない」状態ではなくなった。閉じたことより、こっちのほうが実は効いていると思う。
何を学んだか
会社の設備の棚卸しって、普通はパソコンとかソフトの契約を数える話だと思っていた。でも実際に危なかったのは、会社の口座からお金が出続けているのに、持ち主が社内の誰でもないものだった。しかもそれは、誰かがサボって出来たものではなくて、当時ちゃんと仕事をした結果がそのまま残っていただけだった。悪意はどこにも無い。
非エンジニアの経営者としては、中身の作りを理解するのは無理でも、「どのサービスに、誰の名前で、いくら払っているか」の一覧を持つことはできる。むしろそれは俺の仕事だった。今回はそこをサボっていたぶんの請求が来たんだと思っている。
次回
次こそ予告どおり、このブログの収益導線に手を付けたら初日で前提が崩れた話を書く。