はじめに
※本メールは、タイムズカー会員様のうち、本人確認書類情報の漏えいが確認された方へお送りしております。
メール史上トップクラスに恐ろしい冒頭文の通り、はい、個人情報いかれました。
しかも免許証画像、いかれました(アウトー!)。
ということで今回は、偶然にも被害者となってしまった今回の騒動について、外側から見える実装みたいなものがあったので、あくまでもしがない一個人の、矮小かつ繊細な原因考察を行っていこうと思います。
あくまでもここはQiitaなので、セキュアプログラミング的な要素も加えつつやっていきます。
目次
1. 公表された660万件と、まだ分からない原因
2026年9月28日、パーク24はタイムズカーWebシステムへの不正アクセスにより、約660万件のアカウント情報が第三者に取得されたと発表しました。対象はタイムズカーとタイムズビジネスサービスの会員に加え、退会済みの利用者や、タイムズカーへの入会が完了していない申込者にも及びます。
漏えいした情報には、氏名、住所、生年月日、電話番号、メールアドレス、運転免許情報、本人確認書類情報、パスワード、連携サービスIDなどが挙げられており、項目は対象者によって異なるみたいです。
私の場合は冒頭のメールで「免許証画像」、「現住所確認書類画像」とあったので、流出した情報に応じてそれぞれメール本文が異なるんですかね。
下が、簡単なタイムラインです。
| 公表日 | 確認された内容 |
|---|---|
| 9月25日の第1報 | 同日9時07分に不正アクセスを検知。個人情報漏えいの可能性を公表 |
| 9月28日の第2報 | 約660万件のアカウント情報の取得を確認。9月26日7時25分までに経路と攻撃元との通信を遮断したと説明 |
| 9月29日の第3報 | 本人確認書類が漏えいしたアカウントは約160万件と確認。同日から対象会員への個別メール案内を開始 |
9月29日の第3報では、本人確認書類が漏えいしたアカウント数は約160万件と公表されています。漏えいが確認された情報には、運転免許証画像や現住所確認書類画像のほか、学生プランの学生証画像、家族プランの家族確認書類画像も挙げられています。同日から対象の会員へのメール案内を開始し、個々の漏えいした詳細は2週間程度を目処に改めて案内するとのことです。
以降は、公開HTMLと公式の技術資料を出発点にした考察をやっていきます。想定する構成や不備は不確定であり、確実にタイムズの実装上の欠陥である、というものではないのでご了承ください。
2. 公開HTMLに残っていたSeasarの痕跡
2-1. 確認できたのは、Teeda関連のJavaScript
9月29日に取得したタイムズカーのトップページのHTMLには、次のJavaScriptへの参照がありました。src属性を抜粋しています。
<script src="/view/teedaExtension/org/seasar/teeda/ajax/js/ajax.js?20201217"></script>
参照先のファイルも取得でき、内部にはKumu.AjaxとexecuteTeedaAjaxという識別子がありました。少なくとも、トップページがTeeda関連のJavaScriptを参照し、サイトがそのファイルを配信していたことは確認できます。
Teedaは、SeasarプロジェクトのJava向けウェブアプリケーションフレームワークです。一応公式ドキュメントでは、画面を扱うTeeda Core、HTMLテンプレートを扱うTeeda Extension、非同期通信を扱うTeeda Ajaxがあるみたいです。とはいえTeeda Ajaxは他のモジュールから独立して利用できるので、このJavaScriptだけでサーバー全体の採用技術を逆転裁判みたく突きつけることはできません(くらえ!)。
2-2. サポート終了から考える保守の責任
Seasarプロジェクトは、例外として列挙した製品を除き、2016年9月26日にメンテナンスとサポートを終了すると告知しています。もちろん、TeedaやSeasar2もSeasarプロジェクトに含まれてます。
公式サポートが終了しても一応コードは動き続けますが、新しい公式パッチは無くなります。皆さんご経験があるかもしれませんが、継続利用を続ける場合は、脆弱性情報を確認して独自修正、加えて依存ライブラリの更新、など運用保守作業がより肝要になります。まぁ大抵は移行を考えるとは思います。
私が一エンジニアとして気になるのは、フレームワークの新旧より、個人情報を扱う処理を継続して点検・修正できる体制があったのか、ですかね。世にはレガシーシステムを運用している現場は依然山のようにありますから。
一利用者としてお願いしたいのは、古いフレームワークは新しいものに移行してください、ということです。
適切な修正があてられておらず、脆弱性を突かれた、という説もここから提唱できます。
3. どのような実装なら個人情報を取得されるのか
会員サイトの一般的な構成として、ブラウザーからの要求をウェブアプリが受け取り、会員情報や本人確認画像の保管先へ問い合わせる実装を想定してみます。画像がデータベース内にあるか、別の保管先にあるかも未公表なので、ここでは役割だけを考えます。
以下はセキュアプログラミングの考え方として超基本的なものも混ざっていますが、原因として考えられなくもないので一応おいておきます。
3-1. 非同期通信の処理にも、毎回の認可が必要
Teeda AjaxはJavaScript側のコールバック関数の命名規約によって、サーバー側のコンポーネントとメソッドを解決する仕組みがあります。ブラウザーからサーバーの処理を呼び出す構成では、その入口にも認証と認可を実装しなければなりません。
認証は利用者が誰かを確かめる処理、認可はその利用者に対象データへの操作を許すかを判断する処理です。ログイン済み(認証済み)でも、認可しなければ他人の免許証画像を取得できません。
例えば、画面の初期表示ではログインを確認していても、画像取得や検索を担当する非同期通信の処理に認証や認可の確認がなければ、そこから情報を取得される可能性があります。画面上のボタンを隠すだけでは、サーバーが直接受け取る要求を拒否できません。認可は画面遷移に依存させず、データを返す処理ごとに適用します。
この対策はTeedaに限らず必要で、OWASP(オワスプ)も、非同期通信を含むすべての要求で権限を確認し、許可条件を満たさないアクセスを原則として拒否する方針を示しています。これはエンジニアたるもの基本中の基本です。
3-2. 入力をSQLの命令として解釈させない
別の想定は、検索条件や会員番号を文字列でSQLへ連結する実装です。外部入力がSQLの構文へ入り込むと、意図した検索条件を変えられるSQLインジェクションが成立する場合があります。
ここでは実サービス想定は色々とまずいので、PythonとSQLiteで取得条件の例を示すことにします。login_member_idはサーバーが認証済みセッションから確定した本人IDであり、ブラウザーから指定された会員IDを渡してはいけません。
def get_document_key(conn, *, login_member_id, document_id):
# 書類IDの型と範囲をサーバー側で検証します
if type(document_id) is not int or document_id <= 0:
raise ValueError("invalid document id")
# 本人IDを検索条件に含め、値はSQLの構文へ連結しません
row = conn.execute(
"SELECT storage_key FROM identity_documents "
"WHERE id = ? AND member_id = ?",
(document_id, login_member_id),
).fetchone()
if row is None:
raise PermissionError("document not accessible")
return row[0]
?への値の束縛は入力をSQLの命令として扱わせないための対策で、member_id = ?は本人が所有する書類だけに取得を限る条件です。両方が必要で、SQLインジェクションを防いだだけでは他人のデータの取得まで止められません。
↓こういうのを広く知りたい方はこの記事をどうぞ。
3-3. サーバーの処理自体を乗っ取られる経路
依存ライブラリや実行環境の脆弱性、管理用の認証情報の窃取、設定不備から侵入される経路も考えられます。攻撃者がサーバー上で任意の処理を実行できれば、画面用の認可を経由せず、アプリが持つ権限で保存データへアクセスされるおそれがあります。
ただし、今回の件で任意のコード実行が成立したかは公表されてないので不明です。続報でも出てくるか不明です。
4. 侵入後の大量取得を減らす設計
約660万件という規模を考えると、侵入経路への対策に加え、侵入後に取得できる情報量も検討したいところです。もっとも、一つのデータベースに全情報が保存されていたのか、一回の操作で取得されたのかといった内部構造や取得の手順は断定できないものの、ですが。
一般的な対策として、会員画面用の処理には必要な項目だけを取得する権限を与え、全会員の出力や本人確認書類の一括取得を許可しない設計が考えられます。データベースの閲覧専用権限でも、全会員の個人情報を取得できるなら、漏えい対策の観点では強すぎます。
本人確認画像は通常の会員情報から独立した保管・取得機能にし、業務上必要な利用者と処理だけにアクセスを許可する方法もあります。保管場所だけを変更しても足りず、実際に使用する認証情報、取得できる範囲、返す項目まで制限します。
大量の取得に気づけるよう、取得件数や通信量、普段と異なる操作を監視対象にします。一回の応答件数に上限があっても、繰り返し取得できれば情報は蓄積されるため、利用者や処理ごとの取得量と頻度も確認します。検知後に権限を停止する手順まで用意しておきたいですね。
今回は退会済みの利用者や入会未完了の申込者も対象に含まれていたようです。当たり前ですが、保存し続ける情報は漏えい時に影響を受ける対象にもなります。保存の必要性と期間を決め、不要になった情報を削除する処理も仕様へ含めるべきです。
パスワードについて、第2報は復元できない形式で保管していたと説明していますが、具体的な方式や設定は公表されていません。一般論として、ハッシュ値が流出した場合、攻撃者は候補のパスワードから計算した値との照合を試せるため、パスワード専用の計算コストの高い方式と、利用者ごとのソルト(レインボーテーブル防止策として)を使用する必要があります。どの強度のハッシュを使っていたかによって大分話が変わってきます。
保存データの暗号化も必要ですが、アプリが復号したデータを取得できる場合、アプリを経由した漏えいは起こり得ます。必要なデータだけを取得させる権限設計は、暗号化した後も残る課題です。
おわりに
ここまでお付き合いいただきありがとうございます。
これまで住所とか名前が流出する事案はあれど、免許証が画像も含めて流出するのは中々聞かない(というか聞きたくない)ですよね。
で、ここまで書いておいて何ですが、実はたまたま一昨日免許更新がありまして、新たに写真など撮影した免許証はタイムズに登録していなかったので、私の場合不幸中の幸いみたいなことにはなってます。
とはいえ更新で変わらない流れちゃいけない情報も流れているので、色々と気をつけてすごします、、。
ではまた、お会いしましょう。
参考リンク
公式発表と公開HTML
- タイムズカーWebサイトへの不正アクセスに関する第1報 - タイムズカー
- タイムズカーWebシステムへの不正アクセスに関する第2報 - パーク24
- タイムズカーWebシステムへの不正アクセスに関する第3報 - パーク24
- トップページ - タイムズカー



