héros bg sans séparateur
Directives

Inyección: recorrido de trayectoria

Path Traversal es otro tipo de vulnerabilidad de inyección bastante común. Suelen ocurrir cuando la construcción de un URI (ya sea para una URL, una ruta de archivo u otro tipo) no garantiza adecuadamente que la ruta completamente resuelta no apunte fuera de la raíz del planeada camino.

Es importante señalar que el cruce de rutas también podría considerarse, de hecho, como una vulnerabilidad de *inyección* de rutas.

El impacto de una vulnerabilidad de cruce de ruta depende en gran medida del contexto en el que se produce el cruce y del endurecimiento general que se haya producido. Pero antes de entrar en eso, veamos un ejemplo práctico rápido de esta vulnerabilidad para ver de qué estamos hablando:

Un desglose rápido

Considera la posibilidad de incluir un punto final en tu solicitud que muestre documentos, como plantillas para contratos u ofertas de trabajo. Todos estos archivos, como los PDF, pueden ser estáticos en tu solicitud.

En esta situación, es posible que tengas un fragmento de código como este para recuperar los archivos a petición:

let BaseFolder = «/var/www/api/documents/»;
let path = baseFolder + request.params.filename;

devuelve el archivo.read (ruta);

Para demostrar cómo se desarrolla la vulnerabilidad, también debemos saber dónde está la raíz de nuestra aplicación, por lo que, en este ejemplo, supongamos que la raíz de la aplicación está en '/var/www/api/'.

Sabemos que la aplicación toma un parámetro de «nombre de archivo», veamos algunos ejemplos de entradas y cuál es el resultado:

Nom de fichier Chemin non résolu Chemin résolu
Vie privée.pdf /var/www/api/documents/Privacy.pdf /var/www/api/documents/Privacy.pdf
../config/prod.config /var/www/api/documents/../config/prod.config /var/www/api/config/prod.config
../../../../../etc/shadow /var/www/api/documents/../../../../../etc/shadow /etc/shadow

Observe cómo podemos recorrer el sistema de archivos usando '.. /'. Podemos salir de la carpeta «documentos», en la que suelen estar los archivos PDF, y pasarnos a la carpeta «/etc/» que contiene el archivo «shadow», que en Linux contiene los hashes de las contraseñas. Como puedes imaginar, eso no es realmente lo ideal.

Viendo Traversal en las URL

Otra variante del recorrido de rutas se puede producir al crear URL destinadas a interactuar con una API. Supongamos que tenemos una API con los siguientes métodos:

Modèle d'URL Description
/api/v1/order/get/{id} Obtenir les détails de la commande avec l'identifiant spécifié
/api/v1/commande/suppression/{id} Supprime un ordre avec l'ID spécifique

Otra aplicación interactúa con la API y puede llamarla, por ejemplo, cuando intenta obtener información sobre un pedido:

let ApiBase = "https://my.api/api/v1 «;
let orderApi = apiBase + «/order/get»;

let apiUrl = orderApi + request.params.orderId;

let response = http.get (apiUrl);

¿Qué ocurre ahora, en función del identificador de pedido proporcionado por el usuario? A continuación puedes ver la URL efectiva que se invoca en función de la entrada proporcionada.

La canonicalización generalmente no se realiza en el lado del cliente (aunque puede ser así), pero los servidores web canonicalizarán la solicitud en el formato que se muestra a continuación.

Numéro d'identification de la commande URL réel invoqué
1 /api/v1/ordre/get/1
1/../../supprimer/1 /api/v1/commande/suppression/1

Con la entrada del segundo ejemplo, en lugar de buscar el pedido con el número de identificación «1», hemos invocado el método de eliminación, lo que, por supuesto, resulta en la eliminación del pedido.

Mitigaciones

Cuando se habla de cruzar caminos, hay tanto mitigaciones directas como técnicas indirectas/de defensa que pueden y deben aplicarse con la mayor frecuencia posible. En primer lugar, veamos cómo gestionar las rutas.

Mitigación directa

Cuando se trata de gestionar un camino, tenemos que entender el proceso de resolución del camino, o canonicalización del camino, y su importancia.

Cuando tienes una ruta como '/var/www/api/documents/../../../.. /etc/shadow ', está en una ruta no canónica. Si solicitas esta ruta a tu sistema de archivos, la canonicalizará como '/etc/shadow'. Es fundamental que no intentes abrir rutas no canónicas. En su lugar, primero debes canonizar las rutas, comprobar que solo apuntan al archivo o la carpeta en cuestión y, a continuación, leerlos.

let BaseFolder = «/var/www/api/documents/»;
let path = baseFolder + request.params.filename;

let ResolvedPath = path.resolve (ruta);

si (! Ruta resuelta. Comienza con (Carpeta base)
devuelve «Intenté leer fuera de la carpeta base»;
más
devuelve file.read (ResolvedPath);

Antipatrón: intentando desinfectar los nombres de los archivos

Puede resultar tentador hacer algo como esto:


let BaseFolder = «/var/www/api/documents/»;
let path = baseFolder + request.params.filename.replace («../», «»);
...

Sin embargo, este enfoque debería no ser usado. La clave a la hora de gestionar las rutas es tener siempre en cuenta la ruta canónica.

Mientras el camino canónico no infrinja ninguna regla, la forma en que se construya el camino en última instancia no hace ninguna diferencia. Intentar desinfectar una ruta como esta es muy propenso a errores y rara vez es seguro, si es que lo es.

Limitar el acceso

En nuestros ejemplos anteriores, hemos usado la lectura del archivo '/etc/shadow', que es el archivo con hashes de contraseña en Linux. Pero realmente no hay ninguna razón para que una aplicación pueda leer ese archivo, u otros archivos, fuera de su raíz.

Si emplea contenedores, es probable que ya esté mitigando muchos riesgos. Es vital tomar medidas para endurecer el recipiente (no lo corra como raíz, etc.). Se recomienda encarecidamente eliminar todos los privilegios de su proceso web y limitar sus permisos de lectura en el sistema de archivos solo a los archivos que sean estrictamente necesarios.

Ejemplos

Ahora compartiremos algunos ejemplos en varios idiomas para ayudar a demostrar mejor las cosas mientras están en acción.

C# - Inseguro

Al no resolver la ruta completa o al garantizar que solo se usa la parte del nombre de archivo de una ruta, el código queda vulnerable a Path Traversal.

var baseFolder = «/var/www/app/documents/»;
var FileName = «../../../../.. /etc/passwd «;

//INSEGURO: lee /etc/passwd
var FileContents = File.readAllText (Path.Combine (BaseFolder, FileName));

C# - Seguro - canónico

En este ejemplo, nos protegemos contra el recorrido de rutas resolviendo la ruta completa (absoluta) y asegurándonos de que la ruta resuelta del archivo esté dentro de nuestra carpeta base.

var baseFolder = «/var/www/app/documents/»;
var FileName = «../../../../.. /etc/passwd «;

var canonicalPath = path.getFullPath (Path.Combine (BaseFolder, FileName));

//SEGURO: rechaza cualquier intento de lectura fuera de la base especificada.
si (! Ruta canónica. Comienza con (Carpeta base)
devuelve «Intentando leer el archivo fuera de la carpeta base»;

var FileContents = File.readAllText (canonicalPath);

C# - Secure - nombre de archivo

En este ejemplo, nos protegemos contra el recorrido por rutas tomando solo la parte del nombre del archivo de la ruta, asegurándonos de que sea imposible salir de la carpeta especificada.

var baseFolder = «/var/www/app/documents/»;

//Úsalo solo si no permites navegar a otras subcarpetas
var FileName = path.getFileName («../../../../../.. /etc/passwd «);

//SEGURO: lee /var/www/app/documents/passwd
var FileContents = File.readAllText (Path.Combine (BaseFolder, FileName));

Java: inseguro

Al no resolver la ruta completa o al garantizar que solo se usa la parte del nombre de archivo de una ruta, el código queda vulnerable a Path Traversal.

Cadena BaseFolder = «/var/www/app/documents/»;
Nombre de archivo de cadena = «../../../../.. /etc/passwd «;

//INSEGURO: lee /etc/passwd
Ruta FilePath = Paths.get (BaseFolder + FileName);
<String>Líneas de lista = Files.readAllLines (FilePath);

Java - Seguro - Canónico

En este ejemplo, nos protegemos contra el recorrido de rutas resolviendo la ruta completa (absoluta) y asegurándonos de que la ruta resuelta del archivo esté dentro de nuestra carpeta base.

Cadena BaseFolder = «/var/www/app/documents/»;
Nombre de archivo de cadena = «../../../../.. /etc/passwd «;

//INSEGURO: lee /etc/passwd
Ruta NormalizedPath = Paths.get (BaseFolder + FileName) .normalize ();
si (! Ruta normalizada.toString () .StartWith (BaseFolder)
{
devuelve «Intentando leer la ruta fuera de la raíz»;
}
más
{
<String>Líneas de lista = files.readAllLines (normalizedPath);
}

Java - Secure - Nombre de archivo

En este ejemplo, nos protegemos contra el recorrido por rutas tomando solo la parte del nombre del archivo de la ruta, asegurándonos de que sea imposible salir de la carpeta especificada.

Cadena BaseFolder = «/var/www/app/documents/»;

//Úsalo solo si no permites navegar a otras subcarpetas
Nombre de archivo de cadena = Paths.get («../../../../.. /etc/passwd «) .getFileName () .toString ();

//SEGURO: lee /var/www/app/documents/passwd
Ruta FilePath = Paths.get (BaseFolder + FileName);
<String>Líneas de lista = Files.readAllLines (FilePath);

Javascript: inseguro

Al no resolver la ruta completa o al garantizar que solo se usa la parte del nombre de archivo de una ruta, el código queda vulnerable a Path Traversal.

const fs = require ('fs');

const baseFolder = «/var/www/app/documents/»;
const FileName = «../../../../.. /etc/passwd «;

//INSEGURO: lee /etc/passwd
const data = fs.readFileSync (BaseFolder + FileName, 'utf8');

Javascript - Seguro - Canónico

En este ejemplo, nos protegemos contra el recorrido de rutas resolviendo la ruta completa (absoluta) y asegurándonos de que la ruta resuelta del archivo esté dentro de nuestra carpeta base.

const fs = require («fs»);
const path = require («ruta»);

const baseFolder = «/var/www/app/documents/»;
const FileName = «../../../../.. /etc/passwd «;

const normalizedPath = path.normalize (path.join (BaseFolder, FileName));

//SEGURO: lee /var/www/app/documents/passwd
datos constantes = fs.readFileSync (normalizedPath, 'utf8');

Javascript - Seguro - Nombre de archivo

En este ejemplo, nos protegemos contra el recorrido por rutas tomando solo la parte del nombre del archivo de la ruta, asegurándonos de que sea imposible salir de la carpeta especificada.

const fs = require («fs»);
const path = require («ruta»);

const baseFolder = «/var/www/app/documents/»;
const FileName = path.basename («../../../../../.. /etc/passwd «);

//SEGURO: lee /var/www/app/documents/passwd
const data = fs.readFileSync (path.join (BaseFolder, FileName), 'utf8');

Python: inseguro

Al no resolver la ruta completa o al garantizar que solo se usa la parte del nombre de archivo de una ruta, el código queda vulnerable a Path Traversal.

BaseFolder = «/var/www/app/documents/»
Nombre de archivo = «../../../../.. /etc/passwd»

# INSEGURO: lee /etc/passwd
Contenido del archivo = abrir (BaseFolder + FileName) .read ()

Python - Seguro - Canónico

En este ejemplo, nos protegemos contra el recorrido de rutas resolviendo la ruta completa (absoluta) y asegurándonos de que la ruta resuelta del archivo esté dentro de nuestra carpeta base.

importar os.path

BaseFolder = «/var/www/app/documents/»
Nombre de archivo = «../../../../.. /etc/passwd»

NormalizedPath = os.path.normpath (BaseFolder + FileName)

# SEGURO: rechaza cualquier intento de leer archivos fuera de la carpeta base especificada
si no es normalizedPath.startsWith (BaseFolder):
devuelve «Intentando leer desde la carpeta base»

# SEGURO: lee /var/www/app/documents/passwd
Contenido del archivo = abrir (normalizedPath) .read ()

Python - Secure - Nombre de archivo

En este ejemplo, nos protegemos contra el recorrido por rutas tomando solo la parte del nombre del archivo de la ruta, asegurándonos de que sea imposible salir de la carpeta especificada.

importar os.path

BaseFolder = «/var/www/app/documents/»
FileName = os.path.basename («../../../../.. /etc/passwd («)

# SEGURO: lee /var/www/app/documents/passwd
FileContents = open (os.path.join (BaseFolder, FileName)) .read ()