サウジアラビア政府のアプリから銀行の秘密鍵が流出。報告にはサウジアラビア居住者である必要があった

@iam_zachi
英語2026年9月08日
235K
1.5K
95
28
1.1K

TL;DR

あるセキュリティ研究者が、サウジアラビアの公式アプリ「Nusuk」から、サウジアラビア国立銀行の RSA 秘密鍵と OAuth 認証情報が流出していることを発見しました。しかも、それらは一桁のパスワードで保護されていました。脆弱性報告ポータルが地域制限されていたため、この問題を報告するにはツイートを拡散させる必要がありました。

政府アプリ(インストール数 1,000 万以上)内の 2,835 バイトのファイルに、サウジアラビア国立銀行の有効なクライアント証明書が含まれていました。誰かに見てもらうには、バイラルツイートが必要でした。

概要

公式 Nusuk アプリ(com.moh.nusukapp、ハッジ・ウムラ省、インストール数 1,000 万以上、Google Play の「政府」バッジ付き)には、RSA 秘密鍵とサウジアラビア国立銀行が発行したクライアント証明書を含む PKCS#12 ファイルが同梱されていました。そのファイルのパスワードは、アプリのコード内の数行先にハードコードされていました。それは、たった 1 文字の「2」でした。

その隣には、銀行の BaaS(Banking-as-a-Service)API 用の OAuth2 クライアント ID とクライアントシークレットが平文で存在し、スコープ identity accounts cards verification kyc cardpay transfers を要求していました。

Google Play からアプリをダウンロードした人は誰でも、これらすべてにアクセスできました。

報告しようとしましたが、脆弱性ポータルはサウジアラビア国内のユーザーのみ利用可能と言われました。そこでツイートしました。そのツイートは 150 万回表示され、突然同じポータルが詳細を求めてきました。1 日後、認証情報はアプリから削除されました。

https://x.com/iam_zachi/status/2094445016194207745

この記事では、銀行関連の発見のみを取り上げます。以下の内容はすべて出荷版アプリで修正済みであり、ベンダー側は認証情報がローテーションされたと述べています。

Nusuk とは

Nusuk は、ハッジとウムラのためのサウジアラビア政府公式プラットフォームです。巡礼許可証、電子ビザ、予約、Nusuk カードを扱います。ハッジ・ウムラ省が運営し、Google Play で確認済みの政府アプリとしてフラグが立てられており、インストール数は 1,000 万を超えています。また、サウジアラビア国立銀行と共同で構築され、サウジアラビア中央銀行(SAMA)の承認を受けたウォレット機能「Nusuk Wallet」も含まれています。この記事で取り上げるのは、このウォレット部分です。

発見内容

Google Play から直接 APK セット(バージョン 17.4.9、versionCode 131215)を取得し、解凍しました。特別な点はなく、標準的な Kotlin/Compose、パッカーなし、意味のある難読化もありませんでした。

アプリのリソース内、res/raw/nusuk.pfx に、2,835 バイトの PKCS#12 コンテナがありました。.pfx ファイルは、証明書とその秘密鍵をパスワードで暗号化してバンドルする標準的な形式です。

パスワードはアプリのコード内、ファイルが読み込まれる場所から数行先にありました。

text
1const-string v3, "2"

逆コンパイルされたバイトコードにリテラルとして存在する、たった 1 文字です。その後、openssl を 1 回実行すると、次の結果が得られました。

text
1RSA 秘密鍵、2048 ビット、2 つの素因数
2Subject: C=SA, ST=Jeddah, L=Jeddah, O=Nusuk.sa, CN=eshbeata@staq.io
3Issuer: C=SA, ST=Riyadh, L=Riyadh, O=The Saudi National Bank, OU=Finto,
4 CN=Application Issuer
5Serial: 0x24 (36)
6有効期間: 2026-04-27 -> 2027-04-27
7拡張キー使用法 (critical): TLS Web Client Authentication

銀行が発行した、あと 1 年間有効な、TLS 経由でクライアントをサーバーに認証するための有効なクライアント証明書です。

これだけではありませんでした。同じコードパスはウォレットコンポーネントであり、com.walletstaq としてパッケージ化され、SNB の Finto BaaS プラットフォームを運営する Staq Technologies の Trustless SDK に組み込まれていました。さらに 3 つの値が平文で存在しました。

要求された OAuth スコープは次のとおりです。

text
1identity accounts cards verification kyc cardpay transfers

両方の認証情報は、1 つの文字列に連結され、ベース URL とともにデバッグロガーに渡されていました。

(鍵素材や完全なシークレット値は公開しません。ここで重要なのは問題の構造です。)

これがなぜ問題なのか、平易な言葉で

銀行の API を、2 つの鍵がかかったドアと考えてください。

1 つ目の鍵は、相互 TLS(mTLS)です。通常、サーバーは証明書であなたに自身の身元を証明します。mTLS では、あなたも証明書でサーバーに自身の身元を証明する必要があります。それが .pfx ファイルです。つまり、証明書と、それを所有していることを証明する秘密鍵です。これは、正当なクライアントシステムだけが持つべきものとされています。

2 つ目の鍵は、OAuth クライアントシークレットです。これは、アプリケーションが銀行の API にアクセストークンを要求するために使用するパスワードです。

両方の鍵が、Google Play の無料アプリ内に同梱されて出荷され、最初の鍵が入った箱の鍵は、数字の「2」でした。

ターゲットホストが実際に mTLS を強制していることを確認しました。api.baas.alahli.com への TLS ハンドシェイクはクライアント証明書を要求し(Acceptable client certificate CA names を送信)、サーバー証明書は CN=*.baas.alahli.com, O=The Saudi National Bank と表示されました。つまり、これはテスト環境の飾り的な証明書ではありませんでした。本番環境の銀行 API の正面玄関の認証情報であり、スコープは identity、accounts、cards、KYC、card payments、transfers をカバーしていました。

漏洩とは別に、その根底には設計上の問題があります。1,000 万台のデバイスに同一のクライアント証明書を出荷しても、あるインストールを別のインストールと区別することはできません。すべてのコピーが同じ認証情報を提示するため、証明書は銀行に対して「どのアプリが呼び出しているか」は伝えますが、「誰が呼び出しているか」は何も伝えません。このような API の認証情報は、自社のバックエンドの背後に置くべきです。アプリはあなたのサーバーと通信し、あなたのサーバーが銀行と通信するのです。

私がしなかったこと

トークンエンドポイント(POST /api/tppa/token)に対して、認証情報が有効かどうかを確認するために、ちょうど 1 回だけチェックを実行しました。nginx から HTTP 403 が返されました。証明書をまったく使用しないリクエストでも、ベアのルート URL でも同じ結果でした。これは API の前に配置されたネットワークレベルのブロックであり、ほぼ確実に地理的なものであり、認証情報が機能するかどうかについては何も示していません。

サウジアラビア国外からは、これらの認証情報がアクティブかどうかを判断できませんでした。そこで止めました。それ以上は、銀行のアクセス制御を回避しようとする試みであり、発見内容はそれに依存しません。つまり、転送スコープを持つ秘密鍵と銀行の OAuth シークレットが、公開ダウンロード可能なアーティファクトに存在していること自体が発見であり、私が個人的にエンドポイントに到達できるかどうかは関係ありません。

報告の試み

これがツイートをバイラルにした部分であり、より興味深い半分です。

責任を持って報告する方法を探しました。存在するものは次のとおりです。

チャネル

結果

nusuk.sa、haj.gov.sa、hajj.nusuk.sa の security.txt

存在しない

Saudi CERT(cert.gov.sa)脆弱性報告ページ

NCA にリダイレクト;独自の報告ページは閉鎖

NCA 脆弱性フォーム(haseen.gov.sa)

ドイツから到達不可:タイムアウト、地理的ブロック

bugbounty.sa

クローズドプログラム、外部から HTTP 403

HackerOne / Bugcrowd

Nusuk、省、Elm のプログラムなし

アプリストア掲載情報の連絡先

サポートアドレス、セキュリティ権限なし

これらの中には、私が実際に到達できるセキュリティ権限を持つチャネルはありませんでした。それでもメールを送りました。Haseen サポートからの返信は次のとおりです。

「Haseen ポータルへのアクセスは、サウジアラビア王国国内のユーザーに制限されています。詳細なお問い合わせは、公式 Haseen ポータルで利用可能な「We Care」サービスからご連絡ください。」

Haseen ポータルは、私がアクセスできないものです。私は、自分がサウジアラビア人ではないこと、これは政府アプリで公開された民間銀行の証明書であること、単にそれを提供したいだけであることを説明して返信しました。再び、フォームは KSA 国民のみ利用可能であるという回答でした。

そこで、CERT/CC の VINCE プラットフォームを通じて、調整仲介機関として報告書を提出しました(VRF#26-08-DXMKL)。これは、影響を受ける側に到達可能な独自のチャネルがない場合に取るルートです。

そして、ほとんどフラストレーションから、それについてツイートしました。

ツイートは 150 万回表示されました。数時間以内に、Haseen サポートから、ポータルは私のためのものではないと二度伝えたのと同じスレッドで、突然メールが届きました。

「関係チームによると、セキュリティ脆弱性に関する詳細情報を提供していただけますか。」

完全な詳細を送付しました。プロセスについて正しいと主張するよりも、問題が修正されることを望みました。

修正

次のアプリアップデートが Android と iOS の両方でリリースされました。新しい Android ビルド(17.5.0、versionCode 156635)を Play から直接取得し、分析したものと比較しました。3 つのチェックを行いました。

  1. 新しい APK セットのどこにも証明書コンテナはありません。.pfx、.p12、.pkcs12、.jks、.bks、.pem、.key 拡張子のファイルはありません。また、古い nusuk.pfx のハッシュを取得し、新しいビルド内の同じサイズのすべてのファイルとバイト単位で比較しました(名前が変更された可能性があるため)。一致しませんでした。
  2. 既知の認証情報はありません。ベース URL、スコープ、クライアント ID、クライアントシークレットの正確な古い値を、すべての DEX ファイル、ネイティブライブラリ、アセット、XML、JSON、生リソースにわたって検索しました。4 つすべてでヒットはゼロでした。
  3. コードパスはありません。バージョン 17.4.9 には、com.walletstaq パッケージと com.trustless パッケージの下に 4,571 のファイルがありましたが、17.5.0 にはゼロです。マーカー baas.alahlitppa/tokennusuk.pfx、証明書のサブジェクトと発行者名は、デコードされたビルドでゼロヒットを返します。

ウォレット全体と BaaS 統合が削除されました。ステップ 1 の名前変更チェックにより、値が単にパッケージ内の別の場所に移動したわけではないことが確認されます。

外部の誰も検証できない部分

アプリからシークレットを削除しても、シークレットが無効になるわけではありません。古い APK のコピーは永久に利用可能なままであり、証明書は 2027 年 4 月まで有効でした。そのため、未解決の疑問は、それが失効されたかどうか、そして OAuth シークレットがローテーションされたかどうかです。

それを独立して確認する方法を探しました。ありません。そして、その理由自体が発見です。

証明書には crlDistributionPoints 拡張機能が含まれていないため、失効リストはまったく参照されていません。その唯一の失効エンドポイントは次のとおりです。

text
1OCSP - URI: http://finto-ocsp-responder.prod.svc.cluster.local:8080/api/v1/ocsp

.cluster.local は、Kubernetes クラスターの内部 DNS サフィックスです。定義上、パブリックインターネットからはルーティング不可能であり、そのクラスター外のどこからでも NXDOMAIN を返します。しかも、プレーン HTTP のポート 8080 です。

したがって、この証明書の失効ステータスは、銀行のインフラストラクチャ外部からは確認できません。なぜなら、ここには問い合わせるものがないからです。その 1 つのクラスター外のすべての依存関係にとって、この発行者からの証明書は事実上失効不可能であり、これは誰かのアーキテクチャレビューでそれだけで 1 段落を割く価値があります。

同じフィールドは、銀行の BaaS プラットフォームの本番 PKI のクラスター名、名前空間、サービス名、ポートも公開しており、1,000 万人に配布されたアプリ内にありました。

ローテーションを確認できるのは、SNB、Finto、または Staq だけです。ベンダー側は認証情報がローテーションされたと述べています。私はそれを独立して検証する方法がありません。

タイムライン

日付(2026 年)

イベント

8 月 29 日

APK 分析、発見をローカルで確認

8 月 29 日

報告メール送信;Haseen がポータルは KSA 国内ユーザー限定と返信

8 月 31 日

再度試みるも同じ回答。ツイート;約 150 万回表示

9 月 1 日

Haseen が突然スレッドを再開、詳細を求める。詳細を送信

9 月 1 日

仲介機関として CERT/CC VINCE(VRF#26-08-DXMKL)にも報告

9 月 1 日

バージョン 17.5.0 が Google Play に公開

9 月 3 日

未修正の 17.5.0 APK セットで再テスト、完全な削除を確認

9 月 8 日

本記事

アップデートが私の報告によって引き起こされたことを証明することはできません。17.5.0 はすでにパイプラインにあった可能性があります。示せることは、その素材が 17.4.9 に存在し、17.5.0 には存在しないということです。

ここから得られる教訓

  1. アプリ内に何かを隠すことは、セキュリティ境界にはなりません。リソース内でも、ネイティブ .so 内でも、難読化や同じバイナリに保存されたパスワードの背後でも同様です。アプリがそれを読み取れるなら、アプリをインストールするすべての人が読み取れます。いくつかのチームが毎年、これを公の場で学んでいます。
  2. 共有クライアント証明書は認証ではありません。1,000 万台のデバイスが同じ証明書を提示する場合、それはどのアプリが呼び出しているかを示すだけで、誰が呼び出しているかは何も示しません。そして、アプリは誰でもダウンロードできるファイルです。サードパーティ API、特に銀行の API の認証情報は、あなたが管理するサーバーに属するべきです。
  3. 脆弱性開示チャネルを地理的に制限すること自体が脆弱性です。攻撃者はフォームに記入しません。世界中の 1,000 万人に公開されているアプリの欠陥を報告する唯一の方法が、物理的に 1 つの国内にいることであるなら、フォームに到達できない人々こそ、まさに最も意見を聞くべき人々です。security.txt ファイルがあれば無料で開かれたであろうチャネルを開くのに、バイラルツイートが必要でした。

私は、公開されている Play ストアの APK セットを、アカウントなし、実際の個人データなしのゲストモードで分析しました。銀行 API の前にあるネットワークレベルのブロックをバイパスしようとは一切しませんでした。この記事のすべての値は、構造的なもの(パス、クラス名、証明書メタデータ)であるか、編集済みです。秘密鍵の素材や完全なシークレットは公開されていません。

ワンクリック保存

YouMindでバイラル記事をAI深読み

ソースを保存し、的を絞った質問をし、主張を要約して、バイラル記事を再利用できるノートに変えます。すべてを1つのAIワークスペースで行えます。

YouMindを探索
クリエイターのために

あなたの Markdown をきれいな 𝕏 記事に

自分の長文を投稿するとき、画像・表・コードブロックを 𝕏 向けに整形するのは手間がかかります。YouMind は Markdown 全体を、そのまま投稿できるきれいな 𝕏 記事に変換します。

Markdown → 𝕏 を試す

解読すべきパターンをもっと

最近のバイラル記事

バイラル記事をもっと見る