10月14日、TOPPANペイメンツが不正アクセスの調査結果を公表しました。
データベースの「テーブル名一覧」が参照された痕跡が確認された一方、個別データへのアクセスや取得は確認されず、情報漏えいは発生していないと判断しています。
これは興味深い結果です。
攻撃者はデータベースの構造情報まで到達した。しかし、個別データには進んでいない。
さらに過去の公表を調べると、もう一つ気になることがありました。
証明書ダウンロード、精算情報ダウンロード、売上連携Web APIは順次再開されているのに、決済データなどを確認する「各種検索機能」だけが、10月下旬まで停止する予定になっているのです。
この二つは関係しているのでしょうか。
今回は、加盟店ポータルで使われているとみられるJavaServer Faces(JSF)を手掛かりに、認証バイパス、SQLインジェクション、データベースのアクセス権限という三つの観点から考えてみます。
以下の攻撃経路は公表されたものではありません。公表事実から考えられる技術的なシナリオを、架空のコードで検証します。
| 日付 | 公表内容 |
|---|---|
| 9月28日 | 不正アクセスを検知し、22時に両サイトを停止 |
| 10月6日 | 加盟店ポータルの証明書ダウンロード機能を再開 |
| 10月10日 | 精算情報ダウンロード、売上連携Web APIの再開予定日 |
| 10月11日 | 動作確認の延長後、午前1時頃に再開 |
| 10月13日 | 検索機能は停止継続。決済データ等の確認は個別問い合わせで対応 |
| 10月14日 | テーブル名一覧へのアクセスを確認した調査結果を公表 |
| 10月下旬 | 各種検索機能の再開予定 |
10月14日の総括では10月10日を再開日として整理していますが、第11報には、動作確認の延長で10月11日午前1時頃になったことも記録されています。
検索機能が停止している間、加盟店は決済データや処理未了情報について、サポート窓口へ個別に問い合わせるよう案内されています。
つまり、今回停止しているのは単なるサイト内検索ではありません。決済業務で利用するデータ照会機能です。
そして、加盟店ポータルの公表URLには、login.jsf というファイル名が使われています。
.jsf はJavaServer Facesでよく使われる拡張子です。JSF系の画面処理を採用している可能性を示す手掛かりになります。
そこで考えたのが、外部からの自動スキャンで認証の抜け道が見つかり、SQLインジェクションによってDB構造情報まで参照されたというシナリオです。
通常、加盟店ポータルは契約者向けです。誰でも自由に登録して取引検索できる一般会員サイトとは違います。
しかし、ここで一つ注意が必要です。
画面のログイン認証と、JavaアプリケーションからDBへの認証は別物です。
一般的なJavaの業務システムでは、サーバーがDB接続用のアカウントを持ち、JDBC接続プールを利用してデータベースへアクセスします。
利用者がログインしているかを確認するのはWebアプリ側の役割です。
もし認証前のリクエストが誤って業務処理まで届いてしまうと、アプリケーションは自身のDB接続アカウントを使って問い合わせを実行できる場合があります。
外部の利用者はDBのパスワードを知る必要がありません。
正規アプリケーションが持っているDB接続権限を、間接的に使わせてしまうからです。
例えば、古いJavaアプリが次のような認証Filterを使っていたとします。
<filter-mapping>
<filter-name>AuthenticationFilter</filter-name>
<url-pattern>/secure/*</url-pattern>
</filter-mapping>
この設定では、Filterが保護するのは /secure/ 以下です。
一方、JSFの画面処理は次のように設定されていたとします。
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.jsf</url-pattern>
</servlet-mapping>
この二つの設定が問題なく共存している限り、危険とは限りません。
ところが、開発途中の画面移動や機能追加によって、検索処理が /secure/ の外側に配置され、ほかの認証機構でも保護されていなかった場合はどうでしょう。
開発者は「検索画面はログイン後のメニューからしか表示されない」と考えていたかもしれません。
しかし、HTTPリクエストはメニューを経由する必要がありません。サーバーに届いたリクエストが認証処理を通らず、JSFの業務処理まで進む構造なら、画面の表示制御だけでは守れません。
JSFでは、画面の表示、フォーム送信、Ajaxによる部分更新などが使われます。URLマッピングと認証Filterの適用範囲を一致させる必要があります。
これはJSF固有の脆弱性というより、JSFを使ったJavaアプリケーションで生じ得る認証設計のミスです。
実際、Jakarta Facesの公式仕様も、FacesServletのマッピング方式にセキュリティ上の注意が伴うことに触れています。
ただし、そこで直接説明されているのは、プレフィックスマッピングを使う場合に、FacesServletを経由せず画面のソースファイルそのものにアクセスされないよう、<security-constraint> などで保護する必要があるという点です。
さらに公式仕様は、こうした注意事項がセキュリティ上の懸念の完全な一覧ではなく、アプリケーション全体のセキュリティレビューの代わりにはならないと明記しています。
認証Filterの適用範囲と実際のリクエスト経路が一致しているかは、各アプリケーションで確認すべき問題です。
ログイン画面へ転送されることを確認しただけで、検索処理まで保護できていると判断してはいけません。
次に、架空の決済検索プログラムを考えます。
public List<String> search(String transactionId)
throws SQLException {
String sql =
"SELECT transaction_id FROM payments " +
"WHERE transaction_id = '" + transactionId + "'";
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql);
List<String> results = new ArrayList<>();
while (rs.next()) {
results.add(rs.getString(1));
}
return results;
}
一見、単純な取引番号の検索です。
しかし、検索条件をSQLへ文字列連結しています。
もう一つ注目したいのは、検索結果を getString(1) で取り出している点です。
このコードは、最初の列がどのテーブルから返ってきたものなのかを検査していません。
SQLインジェクションによって元のSELECTに別のSELECT結果が組み込まれると、文字列形式のデータがそのまま検索結果として返る可能性があります。
SHOW TABLES でなくても、テーブル名は取得できるMySQLを仮定すると、テーブル名一覧を調べる方法には、例えば次のSELECTがあります。
SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = DATABASE();
これは、データベースの構成情報を参照するSQLです。
SQLインジェクションでこのような問い合わせを元のSELECTと組み合わせられる条件がそろえば、テーブル名を検索結果として取得できる場合があります。
ここで重要なのは、二つのSQL文を連続実行する必要がないことです。
MySQLでは SHOW TABLES という専用の文もありますが、メタデータをSELECTする方法なら、元の検索処理がSELECT文であっても説明できます。
ただし、UNIONなどを利用するには列数とデータ型の整合が必要で、DB権限やJava側の結果処理にも左右されます。
SQL文字列連結があるだけで、必ずテーブル名が外部へ表示されるわけではありません。
今回の仮説は、元の問い合わせが文字列を返す検索であり、その結果がブラウザまで渡される実装だった場合に成立するシナリオです。
OWASPのSQL Injection Prevention Cheat Sheetでも、文字列連結による動的SQLの危険性と、PreparedStatementによる防御が説明されています。
攻撃者が最初からデータベースのテーブル名を知っている必要はありません。
公開されたJSFのフォームやHTTP通信から、入力項目と処理の関係を把握できる場合があります。
例えば、ブラウザへ返されたHTMLに、次のような構造があったとします。
<form method="post"
action="/example/paymentSearch.jsf">
<input name="search:transactionId"
type="text">
<input type="hidden"
name="javax.faces.ViewState"
value="...">
<button type="submit">検索</button>
</form>
こうしたHTMLは、どの画面で、どんな入力を受け付け、どこへ送信しているかの手掛かりになります。
ただし、これはフォームが未認証で取得可能だった場合の例です。
スキャナは入力値を変えながら応答の違いを調べます。エラーが発生するか、結果件数が変わるか、応答時間が変化するか。そうした反応から入力処理の問題を見つけることがあります。
今回の仮説では、さらに認証Filterの適用漏れが重なっています。
本来はログインできない外部スキャナが検索処理へ到達し、SQLインジェクションを検出し、DBの構造情報まで参照した。
認証が必要な加盟店ポータルであっても、サーバー側の処理に抜け道があれば、このような攻撃は成立し得ます。
ここからが今回、特に考えたい部分です。
公表ではテーブル名一覧へのアクセスが確認されています。しかし、個別データへのアクセスや取得は確認されていません。
その理由は、いくつかの実装によって説明できます。
例えば、Javaの検索処理が次のような作りだった場合です。
Merchant merchant =
sessionContext.getCurrentMerchant();
if (merchant == null) {
return Collections.emptyList();
}
return paymentService.search(
merchant.getMerchantId(),
condition
);
ログイン後の正常な処理なら、セッションから加盟店IDを取得し、その加盟店の決済データを検索します。
しかし、認証を迂回しただけでは、加盟店IDが存在しない場合があります。
そのため、通常の検索は成立しない。
このコードそのものは、merchant == null の時点でDB処理を止めています。したがって、この処理だけを使ってテーブル名一覧を取得することもできません。
一方、別の検索処理や、セッション検証前に実行されるDB問い合わせにSQLインジェクションが残っていれば、構造情報だけが参照されたというシナリオは成立します。
つまり、認証制御の漏れに加えて、DBアクセスがセッション検証より前に実行されていないか、検証のない別経路が存在しないかを確認する必要があります。
MySQLでは、テーブル名などのメタデータが見えることと、そのテーブルのすべてのデータをSELECTできることは同じではありません。
もしDB接続ユーザーが必要最小限の権限しか持っていなかったなら、攻撃者が参照できる範囲も限定された可能性があります。
ただし、検索機能に使うDBアカウントなら、正規検索の対象テーブルにはSELECT権限が必要な場合があります。
したがって、テーブル名だけが参照されたことを、DB権限によって個別データが守られた証拠と考えることはできません。
会社は9月28日に不正アクセスを検知し、22時にサービスを停止したと説明しています。
自動スキャナがテーブル名を列挙し、その後に個別データを調べようとしていたなら、停止によって次の作業ができなくなった可能性があります。
テーブル名一覧の最終参照時刻が分かれば、停止との前後関係を検証できます。
構造情報を取得するところまで自動化され、個別データの取得は別の工程だった可能性もあります。
その後の作業が行われなかっただけなら、技術的に防御できていたとは限りません。
ここは実際のSQLで考えます。
例えば、加盟店ポータルが merchant_app というDBアカウントを使用していたと仮定します。
GRANT SELECT ON payment_db.*
TO 'merchant_app'@'%';
この場合、payment_db 内の各テーブルに対して広範な読み取り権限があります。
認証バイパスとSQLインジェクションが成立し、任意のSELECT結果を取り出せる条件がそろえば、通常の検索機能では表示しないテーブルまで参照される危険があります。
加盟店Aだけの取引を検索する画面だったとしても、DBアカウント自体が加盟店Bのデータを読めるなら、アプリケーション側のWHERE条件だけが境界になってしまいます。
GRANT SELECT ON payment_db.payments
TO 'merchant_app'@'%';
こちらなら、ほかのテーブルへのアクセスを制限できます。
ただし、payments テーブルに全加盟店のデータが入っている場合、そのテーブルの全行を読める権限は依然として広すぎるかもしれません。
テーブル単位の権限だけでなく、加盟店単位のデータ分離も必要です。
GRANT SELECT ON payment_db.payment_lookup_view
TO 'merchant_app'@'%';
元の業務テーブルへの直接アクセスを許可せず、検索用のビューを通じて必要な情報だけを参照させる設計です。
ビューに必要な列だけを定義すれば、機密情報を返さない構成にできます。
ただし、MySQLのビューの実行権限や定義者権限、加盟店ごとの行制御については、別途正しく設計する必要があります。
OWASPも、SQLインジェクション対策のAdditional Defensesで、Least Privilegeを独立した項目として取り上げています。
さらに、SQL Viewsを使った権限の制限や、複数のWebアプリケーションでDBユーザーを分離する設計も推奨しています。
特に確認したいのは、管理者アカウントではなく、本番アプリケーションがJDBC接続プールで使用しているDBユーザーです。
その接続で、例えば以下を確認します。
SELECT USER(), CURRENT_USER();
SHOW GRANTS;
USER() は接続時に指定されたユーザー情報、CURRENT_USER() は権限判定に使用されるアカウントを確認するための関数です。
SHOW GRANTS は、MySQLが提供する権限確認の仕組みです。ロールを使用している場合は、ロールの付与・有効化状況や、必要に応じた USING 句での権限確認も行います。
さらに、接続ユーザーのロール、ストアドプロシージャ、ビュー、ほかのアプリとの権限共有を調べます。
検索処理が必要とする範囲以上に、DBアカウントへ権限を与えていないか。
そしてもっと重要なのは、フロントでログインしていないリクエストがJavaの処理へ到達したとして、そのDB接続アカウントの権限でどこまで読めてしまうかです。
ここを一度確認する価値があります。
10月14日時点でも検索機能は停止したままで、再開予定は10月下旬です。
これも、今回の技術仮説と重ねると考える材料になります。
SQLインジェクションが一か所で見つかったとしても、そのコードだけを修正して終わりとは限りません。
認証Filterの適用漏れがあれば、ほかの業務処理にも同じ問題がないかを確認する必要があります。
共通の検索ライブラリでSQLを組み立てていた場合は、それを使う機能全体が調査対象になります。
さらに、DB接続ユーザーを複数の業務機能で共有していたら、権限を変更するだけで正常な機能まで動かなくなる可能性があります。
例えば従来、検索、精算、証明書ダウンロードが共通のDB接続アカウントを利用していたとします。
この構造を改修するなら、検索機能専用のDBユーザーを分け、必要なテーブルやビューだけを許可する設計も考えられます。
そうなると、原因究明、修正、結合テスト、回帰テストに時間がかかります。
場合によっては、共通バックエンドの責務を分離するなど、アーキテクチャー自体を見直す必要もあるでしょう。
TOPPANペイメンツが実際にそのような改修を行っているという情報はありません。
しかし、自分たちのシステムで同じ問題が見つかった場合、検索機能を安全に再開するまでにどの程度の作業が必要になるかは、考えておきたいところです。
今回の仮説を踏まえ、Java/JSFアプリケーションを運用している場合は、次の点を確認したいと思います。
| 対象 | 確認内容 |
|---|---|
| 認証Filter | .jsf、フォーム送信、Ajax、別マッピングが認証対象から漏れていないか |
| Java業務処理 | 検索処理の実行前に利用者と加盟店の権限を検証しているか |
| SQL | 入力値を文字列連結せず、PreparedStatement等を使っているか |
| セッション | ログアウト時に破棄され、他人の加盟店情報が使われないか |
| DB接続ユーザー | 共通アカウントに不要なSELECT権限を与えていないか |
| DBアクセス範囲 | ほかの加盟店や機密テーブルの情報まで読めないか |
| 監視 | メタデータ列挙、SQLエラーの増加、異常な検索要求を検知できるか |
これらは別々に確認すべき項目です。
認証Filterを修正してもSQLインジェクションが残っていれば、正規アカウントを使った攻撃には弱いままです。
SQLインジェクションを修正してもDBアカウントの権限が広すぎれば、将来の別の脆弱性で被害が広がる可能性があります。
そして、ログイン後の正常な画面操作だけをテストしていても、認証前の異常なリクエストが業務処理に到達する問題は見つからないかもしれません。
今回、TOPPANペイメンツはデータベースのテーブル名一覧が参照されたことを確認しました。
一方、個別データへのアクセスや取得は確認されなかったと報告しています。
もし認証バイパスとSQLインジェクションが重なっていたなら、その先の被害を左右したのは何だったのでしょうか。
加盟店セッションが存在せず、通常の検索処理が成立しなかったのか。
DB接続ユーザーの権限が限定されていたのか。
攻撃者が構造情報を調べた段階で処理を終えたのか。
あるいは、検知とサービス停止が間に合ったのか。
今回はDB側の権限制御で踏みとどまったのかもしれません。
もしそうなら、認証やSQLの入力処理に問題があったとしても、最後の層で被害を限定できたことになります。
一方、DB接続アカウントに広い権限があれば、次の段階で大量のデータを取得される危険もあります。
認証していない外部の利用者でも、Javaの業務処理を経由すれば、DBには正規の接続ユーザーとしてアクセスできてしまう。
その接続ユーザーには、いったいどこまで読む権限があるのか。
今回の事例をきっかけに、まずは自分たちのアプリケーションが使用するDB接続アカウントの権限を棚卸ししておきたいと思います。
Aikido Community Japan
公開日: 2026/10/15
最終更新: 2026/10/15
セキュリティの最新情報をお届けします。