JPCERT/CC調査で攻撃者サーバーからBlutterを発見 — Flutterアプリの秘密鍵が抜かれる手口と対策

はじめに

2026年10月15日、JPCERT/CC が公式ブログ「JPCERT/CC Eyes」に「連続不正アクセス事案の攻撃手法に関する調査」を公開しました。国内で相次いで公表された情報漏えいについて、攻撃に使われた可能性のあるサーバーを調べた結果をまとめたものです。

この調査では、攻撃者のサーバーから Android アプリ(APK)と、解析ツールの jadx、Flutter アプリ向けの Blutter が見つかりました。同じ日には、ソースネクストが最大約2,251万件、バーガーキングが公式アプリの会員情報約1,118万件の漏えいの可能性を発表しています。

Tech Tide では10月9日に、JPCERT/CC の注意喚起(API の認可チェック漏れ)を解説しました。今回は続報として、アプリの中身がどう読まれるのかに絞って、Flutter で開発している人が何を見直せばよいかを考えます。

調査で見つかったもの

JPCERT/CC は複数の組織と連携し、攻撃に使われた可能性のあるサーバーを調べました。報告によると、攻撃者は AI を使って既知の脆弱性をスキャンし、API 経由の攻撃をしていました。日本以外の地域向けの Web サービスも標的になっていたといいます。

サーバーに残っていた痕跡は、大きく3つです。

  1. APK の解析と SSRF の検証: APK を解析した跡に加えて、HTTP ステータス 302 でリダイレクトを返す Python スクリプトが見つかりました。JPCERT/CC は、これが SSRF の検証に使われたと見ています。SSRF は、サーバーに外部 URL を取得させる機能を悪用し、リダイレクトなどで意図しない宛先(内部のシステムなど)にアクセスさせる攻撃です。
  2. Flutter アプリの解析: 複数の APK とともに、Android アプリの解析ツール jadx と、Flutter 向けの Blutter が置かれていました。APK の展開に関係しそうな apk_extract/ というディレクトリや、ネイティブライブラリから抽出した文字列の保存先を思わせる strings_libapp.txt という名前のファイルも確認されています。どのアプリを解析したのかは公表されていません。
  3. 内部ネットワークへの侵入の試み: MySQL、Jenkins、メールゲートウェイを狙った跡、Web シェル、EDR(端末の監視製品)を回避するツールが確認されました。

ソースネクストの件は、会員情報を取得する API を不正に使われたものです。10月9日10時頃に始まり、10月13日13時36分に遮断されました。流出した可能性がある項目は、メールアドレス・パスワード・氏名・住所・電話番号・生年月日などで、クレジットカード情報は保存していなかったとしています。ただし、これらの個別の事案が JPCERT/CC の調査した攻撃者と同じかどうかは、公表されている情報からはわかりません。

なぜ Flutter アプリは読まれるのか

Flutter のリリースビルドでは、Dart のコードは事前にネイティブコードへコンパイルされ、Android では libapp.so というファイルに入ります。Java/Kotlin のアプリと違って jadx ではそのまま読めないため、「Flutter は解析されにくい」と言われることがありました。

Blutter はこの前提を崩すツールです。libapp.so に合った Dart のランタイムを自分でビルドし、アプリを端末で動かさずにバイナリを静的に解析します。出力されるのは、関数名つきのアセンブリ(asm/)、オブジェクトプールに入っている Dart のオブジェクトの一覧(pp.txt、objs.txt)、動的解析に使う Frida スクリプトのひな型(blutter_frida.js)などです。現時点では Android の arm64 版で、比較的新しい Dart のバージョンにのみ対応しています。難読化されたアプリでは関数の復元が不完全になりますが、文字列定数は残ります。

オブジェクトプールには、コード中の文字列定数が入ります。つまり、ソースに書いた API キーや PEM 形式の秘密鍵は、一覧からそのまま見つかるということです。JPCERT/CC は、参考にした外部の技術記事で、RSA 秘密鍵や API の認証処理、リクエストの署名を作るロジックが特定され、その署名処理を再現して本番 API に独自のクライアントからアクセスした事例が報告されていると紹介しています。

「難読化すればよい」とも言えません。Flutter 公式ドキュメントは、--obfuscate はシンボル名を読みにくい別名に置き換えるだけで、リソースを暗号化することも、リバースエンジニアリングを防ぐこともないと説明しています。また、アプリに秘密情報を保存するのは「poor security practice(よくないセキュリティ慣行)」だとも書いています。

エンジニアへの影響 — Flutter アプリで見直すこと

JPCERT/CC の推奨対策は、秘密鍵や有効期間の長い認証情報をアプリに埋め込まない、サーバー側で認証と認可をする、レート制限と異常なリクエストの監視を行う、外部 URL を取得する API でアクセスできる URL を制限しリダイレクト先を検証する、の4つです。ここからは筆者の考えとして、Flutter の現場で具体的に何をするかを挙げます。

  • ビルド時に値が入る方式は、すべて「公開済み」として扱う。 ソースへの直書きだけでなく、--dart-define や .env を読み込むパッケージで渡した値も、ビルドするとアプリに含まれます。仕組みの違いは、取り出す手間の違いでしかありません。
  • 「アプリで署名すれば正規アプリの証明になる」という設計をやめる。 署名に使う鍵がアプリに入っていれば、攻撃者も同じ署名を作れます。リクエストの正当性は、ログイン後にサーバーが発行する、ユーザーごとの短命なトークンで確かめます。
  • アプリの真正性の確認は、追加の判断材料にとどめる。 Play Integrity API や App Attest、Firebase App Check は改造アプリや自動化ツールの排除に役立ちますが、それだけで API を守ることはできません。認可はサーバー側で、操作対象ごとに確認します。
  • 自分のアプリを Blutter にかけてみる。 リリース用 APK から libapp.so を取り出して解析すれば、攻撃者と同じものが見えます。pp.txt に鍵や内部 API の URL が並んでいないかを確認します。.env のようにアセットとして同梱したファイルは libapp.so には入らないので、APK を展開した assets/ 配下もあわせて確認します。

ネイティブアプリから Flutter へ移行するプロジェクトでは、旧アプリの鍵の持ち方をそのまま移しがちです。移行は設計を見直す良い機会なので、鍵の置き場所と認証の流れを最初に洗い出しておくと安全です。

まとめ

  • JPCERT/CC が攻撃に使われた可能性のあるサーバーを調べ、APK と jadx・Blutter が置かれていることを確認した
  • Blutter は libapp.so を静的に解析し、文字列定数を含む Dart のオブジェクトを一覧にできる
  • Flutter の難読化は名前を変えるだけで、文字列や鍵は守れない
  • 対策の中心は、アプリに秘密を置かないことと、サーバー側の認証・認可

JPCERT/CC は、攻撃者が AI を使って既知の脆弱性のスキャンや API 経由の攻撃をしていたと報告しています。「アプリの中身は読まれる」という前提で、手元のアプリを一度点検しておくことをおすすめします。

ソース