セキュリティ

Qzoo不正アクセス:40万件の対象項目と会員の対処、SQLインジェクション対策

Qzoo不正アクセス:40万件の対象項目と会員の対処、SQLインジェクション対策

アンケートサイト「Qzoo」を運営する株式会社モニタスは2026年10月21日、第三者からの不正アクセスにより、会員の個人情報が漏えいした可能性があると公表しました。10月17日にSQLインジェクションでデータベースが閲覧・更新され、対象はQzooが持つ全件の約40万件です。氏名や住所に加えて、会員IDとパスワード、口座番号、年収などのアンケート回答も項目に入っています。前半で公式発表をもとに整理するのは、会員が今やること、偽の連絡への注意、問い合わせ先です。後半は、SQLインジェクションで「閲覧」だけでなく「更新」まで起きる構造をPythonで再現し、プレースホルダとデータベースの権限分離で止める手順を示します。

まとめ:Qzoo不正アクセスで漏えいの可能性がある情報と、会員が今やること

モニタスのお詫びとお知らせによると、10月17日の午前8時30分に第三者による不正アクセスが始まり、12時22分にSQLインジェクションによるデータベースの閲覧・更新が行われました。同日18時44分に事象を検知してアクセスを遮断し、19時02分にQzooを緊急メンテナンスの状態に移しています。約40万件という数字は漏えいが確定した件数ではなく、10月17日時点でQzooが保有していた全件数で、影響の範囲は調査中です。

会員がまず手を付けたいのは2つです。Qzooと同じメールアドレスとパスワードを他のサービスで使っているなら、そちらのパスワードを今のうちに変えてください。もう1つは、Qzooやモニタスを名乗るメール・SMS・郵便物のURLや添付ファイルを開かないことです。Qzoo自体のパスワードは、サービスを再開するときに全会員が再設定する方針が示されています。

項目 公表内容(2026年10月28日時点)
公表した会社 株式会社モニタス
不正アクセスの開始 2026年10月17日 8時30分
攻撃の手口 SQLインジェクション
対象の件数 約40万件(保有する全件)
漏えいの可能性がある項目 基本情報・会員ID・パスワード
同上(続き) 口座番号・支店名・口座名義・年収など
保有していない情報 クレジットカード情報・マイナンバー
未公表 確定件数・侵入経路の詳細・再開日

Qzoo会員が最初に確かめること:パスワードの使い回し、偽の連絡、口座情報

自分が対象かの確認と、10月28日時点でQzooが開けない状態の見方

公表文は、対象となる会員へ順次個別に案内するとしています。10月28日時点で、会員IDなどを入れて対象かどうかを調べるページはありません。約40万件が「保有する全件」である以上、Qzooに登録していた人は対象に含まれる前提で動くのが無難です。

10月28日に確認したところ、Qzooのサイトは開けない状態(HTTP 503)が続いていました。モニタスは10月23日から26日にかけて、PHPの脆弱性への対応でコーポレートサイトや他のサービスを止めて再開したと告知しています。ただし10月26日の完了報告にQzooの名前はなく、今回の不正アクセスとの関係も書かれていません。「モニタスのサービスが再開した」という情報を見ても、Qzooが再開したとは読まないでください。

Qzooと同じパスワードを使っている他サービスの変更を先に済ませる

漏えいの可能性がある項目に、会員IDとパスワードが含まれています。パスワードがどの形式で保存されていたかは公表されていません。読める形で持ち出されたと仮定して動くほうが安全です。メールアドレスとパスワードの組を、別のサービスのログイン画面へ順に試す攻撃が知られているからです。

変える順番は、ネット銀行・証券、通販サイト、メールの順がよいでしょう。お金が動くサービスから先に済ませます。この攻撃の手口と、サービス側での検知の指標はパスワードリスト攻撃の検知の指標と漏えい照合で解説しています。

口座番号と年収が組み合わさった偽の連絡、メール・SMS・郵便物への警戒

今回の項目には、謝礼の受け取りに使う口座番号・支店名・口座名義と、年収や職業といったアンケートの回答が入っています。氏名と住所に口座と収入の情報までそろうと、本人しか知らないはずの内容を並べた偽の連絡が作れてしまいます。公表文が注意を呼びかけている経路に、メールとSMSに加えて郵便物が含まれている点にも注目してください。

口座番号だけで預金を引き出されることはありません。ただし、銀行や警察を名乗って暗証番号やワンタイムパスワードを聞き出す電話には応じないでください。偽の連絡が成り立つ仕組みはフィッシング詐欺の成立の仕組みと技術的対策で取り上げています。同じアンケートサイトの事案としては、infoQの不正アクセスとポイント交換を止める設計も参考になります。

公式発表で追う経過:10月17日8時30分の侵入開始から18時44分の遮断まで

公表文の時刻を並べると、次のとおりです。

  1. 10月17日(土)8時30分:第三者による不正アクセスが始まる
  2. 同日12時22分:SQLインジェクションによりデータベースが閲覧・更新される
  3. 同日18時44分:事象を検知し、アクセスを遮断
  4. 同日19時02分:Qzooを緊急メンテナンス状態に移行
  5. 10月20日(火)18時07分:個人情報保護委員会へ速報
  6. 10月21日:モニタスが公表

問い合わせは「Qzoo」お客さまお問い合わせ窓口のメールで受け付けており、電話番号は載っていません。Qzooは独立したシステムで、他の社内システムへの不正アクセスは確認されていないとしています。市場調査の取引先には、調査や業務の一部に遅れが出る可能性があり、影響のある取引先へ個別に連絡するとしています。

個人情報保護委員会への速報は検知から3日後、確報の期限は60日以内

個人情報保護委員会の漏えい等の対応ページは、速報を発覚日から3〜5日以内、確報を30日以内とし、不正な目的のおそれがある場合の確報は60日以内としています。不正アクセスによる漏えいは人数にかかわらず報告の対象です。10月17日の検知から20日の速報は3日後で、この目安の範囲に入っています。確報の期限から逆算すると、調査結果の続報は12月中旬までに出る可能性があります。速報と確報に何を書くかは個人情報保護委員会への報告義務の実務で整理しました。

開発側の教訓:SQLインジェクションで「閲覧」に加えて「更新」まで許した構造

公表文の「閲覧・更新」が示す、DB接続が書き込み権限を持っていた状態

SQLインジェクションは、画面から受け取った値をそのままSQL文に連結しているときに、値の中に書いたSQLが命令として実行されてしまう脆弱性です。攻撃者が使えるのは、そのアプリがデータベースに接続しているアカウントの権限そのものです。公表文が「閲覧」だけでなく「更新」と書いているのは、注入されたSQLが書き込みのできる接続で動いたことを意味します。

どの画面のどの処理が狙われたのか、何が書き換えられたのかは公表されていません。ここからは一般論として、検索の画面から閲覧が、プロフィール変更の画面から書き換えが起きる流れを再現します。仕組みの基礎と試験の出題例はSQLインジェクションの攻撃例と対策で扱っており、本記事では会員データの書き換えに絞ります。

文字列連結のSQLで閲覧と書き換えが起きる様子をPythonで再現する手順

次のコードは標準ライブラリの sqlite3 だけで動きます。架空の会員2人と口座番号を入れたデータベースを作り、脆弱な実装と修正後の実装に同じ文字列を渡して結果を比べます。

# SQLインジェクションで「閲覧」と「更新」が起きる様子と、プレースホルダ・読み取り専用接続で止まる様子を再現する(Python 3.8以上・標準ライブラリのみ)
# 使い方: python sqli_demo.py
import os, sqlite3, tempfile

DB = os.path.join(tempfile.mkdtemp(), "panel.db")

def setup():
    con = sqlite3.connect(DB)
    con.executescript("""
    CREATE TABLE members(id INTEGER PRIMARY KEY, login_id TEXT, nickname TEXT, bank_account TEXT);
    INSERT INTO members VALUES (1, 'm0001', 'aoi',   '0001-1234567'),
                               (2, 'm0002', 'hikaru', '0002-7654321');
    """)
    con.commit()
    return con

# 脆弱な実装:入力を文字列連結でSQLに埋め込む
def find_nick_bad(con, login_id):
    return con.execute(f"SELECT nickname FROM members WHERE login_id = '{login_id}'").fetchall()

def rename_bad(con, uid, nick):
    con.execute(f"UPDATE members SET nickname = '{nick}' WHERE id = {uid}")
    con.commit()

# 修正後:プレースホルダで値として渡す
def find_nick_ok(con, login_id):
    return con.execute("SELECT nickname FROM members WHERE login_id = ?", (login_id,)).fetchall()

def rename_ok(con, uid, nick):
    con.execute("UPDATE members SET nickname = ? WHERE id = ?", (nick, uid))
    con.commit()

READ = "x' UNION SELECT login_id || ' ' || bank_account FROM members --"
WRITE = "x', bank_account = '9999-0000000' --"

def accounts(con):
    return [r[0] for r in con.execute("SELECT bank_account FROM members ORDER BY id")]

if __name__ == "__main__":
    con = setup()
    print("[bad] read :", find_nick_bad(con, READ))
    rename_bad(con, 1, WRITE)
    print("[bad] write:", accounts(con))

    con.close(); os.remove(DB); con = setup()              # 作り直して修正版を試す
    print("[ok]  read :", find_nick_ok(con, READ))
    rename_ok(con, 1, WRITE)
    print("[ok]  write:", accounts(con))

    ro = sqlite3.connect(f"file:{DB}?mode=ro", uri=True)   # 読み取り専用の接続(参照系の画面用)
    try:
        ro.execute("UPDATE members SET bank_account = '9999-0000000'")
    except sqlite3.OperationalError as e:
        print("[ro]  write:", e)

Python 3.14.7(Windows)で実行した結果は次のとおりです。

$ python sqli_demo.py
[bad] read : [('m0001 0001-1234567',), ('m0002 0002-7654321',)]
[bad] write: ['9999-0000000', '9999-0000000']
[ok]  read : []
[ok]  write: ['0001-1234567', '0002-7654321']
[ro]  write: attempt to write a readonly database

1行目では、ニックネームを1件返すはずの検索に UNION SELECT を足され、全会員の会員IDと口座番号が返っています。2行目はもっと深刻です。ニックネームの変更欄に入れた文字列が SET 句に口座番号の書き換えを足し、末尾の -- で WHERE id = 1 を無効にしたため、本人以外も含めた全員の口座番号が同じ値に変わりました。謝礼の振込先が書き換えられると、気付かないまま第三者の口座へ送金する事故につながります。

プレースホルダと読み取り専用の接続で、同じ攻撃文字列が止まる確認

3行目と4行目は、同じ文字列を ? のプレースホルダで渡した修正版の結果です。文字列は値として比較されるだけなので、検索は0件、口座番号も元のままです。Pythonのsqlite3のドキュメントも、文字列操作でSQLを組み立てるとSQLインジェクションを招くとして、プレースホルダを使うよう書いています。

OWASPのSQL Injection Prevention Cheat Sheetは、第一の防御にパラメータ化したプリペアドステートメントを挙げ、手作業のエスケープを「STRONGLY DISCOURAGED」としています。5行目は、読み取りだけの接続では書き換えがデータベース側で拒否されることを示しています。プレースホルダの書き漏らしが1か所残っても、その画面の接続が読み取り専用なら「更新」までは起きません。日本語で手順を確かめるなら、IPAの安全なウェブサイトの作り方もSQLインジェクションの根本的な対策と保険的な対策を分けて解説しています。

被害を小さくする設計:DB権限の分離、パスワードの保存形式、検知の指標

PostgreSQLの列単位GRANTで、画面ごとに読める列と書ける列を分ける設定

本番のデータベースでは、アプリの接続を用途ごとのロールに分けます。次はPostgreSQL 18のGRANTの列単位の構文に沿った設定例です(この記事の検証環境では実行していません)。

-- 会員向け画面の接続:口座番号は読めず、ニックネームだけ書ける
CREATE ROLE app_member LOGIN;
GRANT SELECT (id, login_id, nickname) ON members TO app_member;
GRANT UPDATE (nickname) ON members TO app_member;

-- 謝礼の支払い処理だけが口座番号を読み書きする(別の接続・別の認証情報)
CREATE ROLE app_payout LOGIN;
GRANT SELECT (id, bank_account), UPDATE (bank_account) ON members TO app_payout;

公式ドキュメントは、テーブル単位で権限を与えたあとに一部の列だけ取り消しても、テーブル単位の権限は残ると注意しています。最初から列単位で与えるのが正しい順番です。OWASPのチートシートも、アプリのアカウントにDBAや管理者の権限を与えないよう大文字で書いています。この分け方なら、会員向け画面にSQLインジェクションが残っていても、口座番号は読めず書き換えもできません。

パスワードはArgon2idなどで保存し、漏れても元に戻されにくい形にする

Qzooのパスワードがどの形式で保存されていたかは公表されていません。一般論として、OWASPのPassword Storage Cheat SheetはArgon2idを第一に挙げ、最小の設定をメモリ19MiB・反復2回・並列度1としています。Argon2idが使えなければscrypt(N=2^17・r=8・p=1)、古い環境ではbcrypt(work factor 10以上・入力は72バイトまで)の順です。FIPS-140への準拠が必要な場合はPBKDF2-HMAC-SHA256を600,000回以上回します。

この形式で保存していれば、持ち出されても元のパスワードを割り出すには1件ごとに大きな計算が要ります。会員が他サービスのパスワードを変えるまでの時間を稼げるわけです。MD5やSHA-1を1回かけただけの保存は、漏えい時には平文とほぼ同じと考えてください。

侵入開始から検知まで約10時間、SQLエラーと応答件数の急増を見る監視

公表文の時刻では、不正アクセスの開始が8時30分、データベースの閲覧・更新が12時22分、検知が18時44分です。開始から検知まで約10時間、閲覧・更新からでも約6時間20分ありました。SQLインジェクションは、攻撃が成功する前に構文エラーを何度も起こすのが普通です。注入する文字列の形を探る段階で、データベースのエラーログにふだん出ない構文エラーが短時間に集中します。

監視するなら、次の3つを1時間単位で見ると変化を拾えます。1つ目はデータベースの構文エラーの件数、2つ目は1回の応答で返した行数の最大値、3つ目は会員テーブルの口座や連絡先の列への更新件数です。夜間や休日に通知を受けて遮断まで動ける体制がなければ、検知しても止められません。今回の侵入も土曜日の朝に始まっています。APIの入口での防御はAPI不正アクセス対策の実装手順で扱っています。

事業者の判断:会員の口座情報を持つWebサービスで、外部診断を入れる時期

謝礼やポイントの振込先を持つ会員サイトは、年1回の診断を前提にする

会員に謝礼やポイントを払うサービスは、口座番号や連絡先という書き換えられると実害の出る情報を持っています。SQLインジェクションは古くから知られた脆弱性ですが、検索・絞り込み・並び替えなど、画面の条件が増えるたびに文字列連結が紛れ込みます。古い画面と新しい画面が混ざった会員サイトは、外部の診断で全画面を一度洗い出すべきです。

次の条件に当てはまるなら、年1回の診断と大きな改修ごとの再診断を前提にしてください。会員の口座・カード以外の決済手段・本人確認書類のいずれかを持つこと、画面ごとのDB接続を分けていないこと、構文エラーや更新件数を監視していないことの3つです。逆に、会員情報を持たない社内向けの集計画面だけなら、まずはプレースホルダの全数確認と読み取り専用の接続への切り替えを先に済ませれば足ります。診断の種類と費用の考え方は脆弱性診断の種類・費用相場と外注の判断基準、企業として点検する範囲は不正アクセス対策の点検手順にまとめました。

会員サイトや管理画面にSQLインジェクションが残っていないかを第三者に確かめてもらいたいなら、脆弱性診断・セキュリティ診断で相談できます。

よくある質問

Qzooの不正アクセスについて、会員から出やすい質問をまとめました。

Qzooの不正アクセスで自分の情報が漏れたか確かめられますか?

10月28日時点で、対象かどうかを調べるページは公開されていません。約40万件は10月17日時点でQzooが保有していた全件数なので、会員だった人は対象に含まれる前提で動くのが無難です。モニタスは対象者へ順次個別に案内するとしています。届いた案内のリンクは開かず、モニタスの公式サイトのお知らせと照らし合わせてください。

Qzooのパスワードはいつ変更すればいいですか?

Qzooはメンテナンス中でログインできないため、今は変えられません。公表文は、サービスを再開するときに全会員がパスワードを再設定するとしています。今すぐやるべきなのは、Qzooと同じメールアドレスとパスワードの組を使っている他のサービスのパスワード変更です。

Qzooに登録した口座番号が漏れたら預金は引き出されますか?

口座番号・支店名・口座名義だけで預金を引き出すことはできません。注意したいのは、漏れた情報を並べて本物らしく装い、暗証番号やワンタイムパスワード、キャッシュカードを聞き出す電話や訪問です。銀行や警察がそれらを聞くことはないので、応じずに自分で銀行の窓口へ確認してください。

Qzooはいつ再開しますか?たまったポイントはどうなりますか?

10月28日時点で再開日は公表されておらず、サイトはメンテナンス状態です。モニタスは安全性を確認してから再開し、再開の目途は告知画面で知らせるとしています。保有ポイントの扱いについても、10月21日の公表文には書かれていません。10月26日に再開したと告知されたのはPHPの脆弱性で止めていた別のサービスで、Qzooの再開ではありません。

Qzooの問い合わせ窓口に電話はありますか?

10月28日時点で、問い合わせは「Qzoo」お客さまお問い合わせ窓口のメールで受け付けており、電話番号は載っていません。電話番号を名乗ってQzooの窓口だと言う連絡が来た場合は、モニタスの公式サイトに同じ番号が載っているかを確かめてください。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2025.02.27 コラム トラッキングビーコン(Webビーコン)とは?仕組み・用途・確認と対策を徹底解説
  2. 2026.10.27 テックブログ Joshin(ジョーシン)不正アクセス:最大600万件の対象とサブスク初期パスワードの対処
  3. 2026.10.27 テックブログ Brotli 脆弱性:CVE-2026-111497の影響確認とNode.js・Python・.NETの更新手順
  4. 2026.07.09 テックブログ Muse Imageとは?Metaの画像生成AIの使い方・API料金(1枚0.01ドル)と日本での提供状況【2026年10月】
  5. 2026.10.21 テックブログ ユミルリンク不正アクセス第二報で延べ5,162万件:Cuenote FC利用企業と受信者の確認点

RELATED POSTS 関連記事

目次