7月24日、hhh.hnが「My security camera shipped a GitHub admin token in its login page」と題した記事を公開した。韓国の監視カメラメーカーHanwha Visionのファームウェアを解析したところ、数百のリポジトリにadmin権限を持つGitHubトークンが含まれていた——その経緯と根本原因を詳述した記事だ。なお著者のhhh.hnはファームウェアのリバースエンジニアリングを趣味で行っているセキュリティリサーチャーで、同ドメインで複数の調査記事を公開している。
IoT機器のサプライチェーンセキュリティが問われ続けるなか、今回の事例が示す根本原因はシンプルかつ身近だ——ViteのビルドでCIの環境変数をprocess.envごとフロントエンドバンドルに書き出してしまうというミスが、管理者権限トークンの漏洩につながった。Viteに限らず、類似の設定ミスはWebpack・esbuildなど他のバンドラーでも起きうる。自社のCI設定に同様のリスクがないか、確認しながら読んでほしい。
ファームウェア解析の経緯
著者がHanwha Visionに注目したきっかけは、AXISカメラのセキュリティ管理に関する議論の中で名前が挙がったことだ。Hanwha VisionはもともとSamsung Techwinとして設立された映像監視企業で、Hanwha Groupの子会社である。
公開されているファームウェアを入手し、binwalkで解析すると、内部に暗号化されたfwimage.tgzが見つかった。Matt Brownの先行研究を参考にすることで、パスフレーズがHTW + モデル番号(例: HTWXNP-9300RW)という形式であることが判明し、外側のアーカイブは展開できた。
しかし内側にはさらに別の暗号化されたfwimage.tgzがあり、同じ手法は通用しなかった。著者はfwupgraderバイナリを取り出し、Ghidra(NSAが開発・公開している無償のリバースエンジニアリングツール)で解析を試みようとした。ところがここで、いかにも現代的な展開になる。
「でも今は2025年なので、Claude Codeに投げて夕食を作りに行き、パートナーと時間を過ごした。しばらくして戻ってきたら、説明と綺麗なrootfsが出来上がっていた」
Hanwhaはファームウェアに難読化を施していた。AESキーはバイナリ内の静的キーテーブルとXOR演算されており、実行時に復元される仕組みだ。IVは平文のまま格納されており、opensslのCLIコマンドも断片がXOR難読化された状態で埋め込まれていた。復元されたコマンドは以下のとおりだ。
openssl enc -md sha256 -aes-256-cbc -d \
-K <KEY> -iv <IV> -in <INPUT> -out <OUTPUT>
著者はモデルライン共通のキーとIVをそのまま公開している。
KEY = dfa049bb922e63e2decc764af5628068e5b7a2662e479a615b14643e567579b0
IV = 53f926801b81454a4f889c9a390db6e6
「truffles」——rootfsの中に眠っていたもの
rootfsを展開した後、著者がまず実行したのはtrufflehogだ。シークレット(APIキーや認証情報)がソースコードやバイナリに混入していないかを検出するオープンソースツールで、CI/CDパイプラインへの組み込みも広く行われている。結果は即座に出た。GitHubトークンが約30ファイルに重複して存在しており、そのトークンはHanwhaのGitHub organizationにある数百のリポジトリへのadmin権限を持っていた。
なぜ30ファイルにも散在していたのか。原因はビルドプロセスにあった。カメラのUIはViteでビルドされており、ある変数にprocess.envの全内容がビルド時に展開されていた。CIジョブで使われた環境変数がそのままJavaScriptバンドルに書き込まれ、ファームウェアの各所に複製されていたわけだ。
var W = {
DATAPORT: "9090",
GIT_LFS_SKIP_SMUDGE: "1",
npm_command: "run-script",
KUBERNETES_SERVICE_PORT_HTTPS: "443",
GITHUB_NPM_TOKEN: "<snip>:ghp_…REDACTED…",
npm_config_userconfig: "/home/docker/.npmrc",
// etc
Viteではimport.meta.envやdefineを使って必要な変数だけを明示的に渡すことが公式ドキュメントで推奨されているが、process.envを丸ごと展開する実装は開発初期のプロトタイプや、Node.js向けのコードをそのままフロントエンドビルドに流用した際などに混入しやすい。著者は実機を所持していないため確認はできないが、「カメラの管理UIにアクセスしたユーザーには、このトークンがネットワーク越しに送信されていた可能性がある」と指摘している。
米国防総省のIPアドレスという謎
環境変数の中には、もう一つ目を引く内容があった。米国防総省(DoD)に割り当てられたIPアドレスが複数含まれていたのだ。
SWARM_MASTER_NFS_ADDRESS: 55.101.212.23OTEL_ELASTIC_URL: http://55.101.212.21:5601/...CIMIP: 55.101.211.213
ここで、Hanwha Groupの企業構造が関係してくる。同グループの傘下にはK9 Thunder自走砲やSGR-A1自律警戒ロボットを手がける防衛部門が存在し、米国法人Hanwha Defense USAも設立されている。著者は「これは推測だ」と明示したうえで、「親会社の共有CI基盤が航空宇宙・防衛部門のIPアドレスを含んでいた可能性がある」という仮説を示している。
500本のファームウェアを一括検証
一例にとどまらないか確認するため、著者はHanwhaのサイトをスクレイピングして約500本のファームウェアをダウンロードした(カメラ数は約600台だが、全モデルにファームウェアが公開されているわけではない)。同じ手法で62%を展開でき、GitHubトークンが含まれていたのは3本のみ——すべて同一トークンだった。
開示対応は迅速だった
著者はHanwhaの公開セキュリティ窓口に、トークンの場所を示した簡潔なメールを送った。12時間以内に返信があり、トークンは即座に無効化された。著者は「入れるべきではなかったのは確かだが、これほど迅速な対応は経験したことがない」と評価している。
根本原因は単純だ。CIの環境変数をprocess.envごとフロントエンドバンドルに書き出してしまうというありふれた設定ミスが、管理者権限トークンの漏洩につながった。Viteを使っているプロジェクトであれば、公式ドキュメントの環境変数ガイドを改めて確認する価値がある。また、trufflehogをCIに組み込んでおくことで、同種のミスを出荷前に検出できる。
詳細はMy security camera shipped a GitHub admin token in its login pageを参照していただきたい。