2011-08-20 12 views
6

En C, usando llamadas POSIX, ¿cómo puedo determinar si una ruta está dentro de un directorio de destino?¿Cómo determinar si una ruta está dentro de un directorio? (POSIX)

Por ejemplo, un servidor web tiene su directorio raíz en /srv, esto es getcwd() para el daemon. Al analizar una solicitud para /index.html, devuelve el contenido de /srv/index.html.

¿Cómo puedo filtrar las solicitudes de rutas fuera de /srv?

/../etc/passwd, /valid/../../etc/passwd, etc.

Splitting la trayectoria en / y rechazar cualquier matriz que contiene .. romperá válido accede /srv/valid/../index.html.

¿Hay alguna manera canónica de hacer esto con las llamadas al sistema? ¿O necesito caminar manualmente la ruta y contar la profundidad del directorio?

+2

Creo que esta es la razón por la cual 'chroot (2)' se inventó! –

+0

@Carl Norum: chroot es mejor si le das a alguien acceso de shell limitado. si no quieres limitar el acceso a un programa que crees, hay mejores opciones que chroot. – Dani

Respuesta

6

Siempre hay realpath:

La función realpath() deberá derivar, a partir de la ruta a la que apunta * nombre_archivo *, una ruta absoluta que se resuelve en la misma entrada de directorio, cuya resolución no implica ''. , '..', o enlaces simbólicos.

Luego compare lo que realpath le da con su directorio raíz deseado y vea si coinciden.

También puede limpiar el nombre del archivo a mano expandiendo los puntos dobles antes de anotar el "/srv". Dividir la ruta entrante en barras y caminar a través de ella pieza por pieza. Si obtiene un ".", quítelo y continúe; si obtiene un "..", luego quítelo y el componente anterior (teniendo cuidado de no pasar la primera entrada en su lista); si obtienes algo más, solo pasa al siguiente componente. Luego pegue lo que queda atrás junto con barras entre los componentes y anteponga su "/srv/". Entonces, si alguien le da "/valid/../../etc/passwd", terminará con "/srv/etc/passwd" y "/where/is/../pancakes/house" terminará como "/srv/where/pancakes/house".

De esa manera no se puede salir a la calle "/srv" (excepto a través de enlaces simbólicos, por supuesto) y un entrante "/../.." será el mismo que "/" (al igual que en un sistema de archivos normal). Pero aún querrá usar realpath si le preocupa lo simbólico bajo "/srv".

Trabajar con el componente de nombre de ruta por componente también le permitiría romper la conexión entre el diseño que presenta al mundo exterior y el diseño del sistema de archivos real; no es necesario "/this/that/other/thing" asignar a un archivo real "/srv/this/that/other/thing" en cualquier lugar, la ruta podría ser simplemente una clave en algún tipo de base de datos o algún tipo de ruta de espacio de nombres a una llamada de función.

0

Simplemente debe procesar .. sí mismo y eliminar el componente de trayectoria anterior cuando se encontró, por lo que no hay ocurrencias de .. en la cadena final se utiliza para abrir archivos.

2

Para determinar si un archivo F está dentro de un directorio D, primero stat D para determinar su número de dispositivo y número de inodo (miembros st_dev y st_ino de struct stat).

Luego stat F para determinar si se trata de un directorio. De lo contrario, llame a basename para determinar el nombre del directorio que lo contiene. Establezca G al nombre de este directorio. Si F ya era un directorio, establezca G = F.

Ahora, F está dentro de D si y solo si G está dentro de D. Luego tenemos un ciclo.

while (1) { 
    if (samefile(d_statinfo.d_dev, d_statinfo.d_ino, G)) { 
    return 1; // F was within D 
    } else if (0 == strcmp("/", G) { 
    return 0; // F was not within D. 
    } 
    G = dirname(G); 
} 

La función samefile es simple:

int samefile(dev_t ddev, ino_t dino, const char *path) { 
    struct stat st; 
    if (0 == stat(path, &st)) { 
    return ddev == st.st_dev && dino == st.st_no; 
    } else { 
    throw ...; // or return error value (but also change the caller to detect it) 
    } 
} 

Esto funcionará en sistemas de archivos POSIX. Pero muchos sistemas de archivos no son POSIX. Los problemas a tener en cuenta incluyen:

  1. Sistemas de archivos donde el dispositivo/inode no son únicos. Algunos sistemas de archivos FUSE son ejemplos de esto; a veces crean números de inodo cuando los sistemas de archivos subyacentes no los tienen. No deberían volver a usar números de inodo, pero algunos sistemas de archivos FUSE tienen errores.
  2. Implementaciones de Broken NFS. En algunos sistemas, todos los sistemas de archivos NFS tienen el mismo número de dispositivo. Si pasan a través del número de inodo tal como existe en el servidor, esto podría causar un problema (aunque nunca lo he visto suceder en la práctica).
  3. Linux bind puntos de montaje. Si /a es un montaje de enlace de /b, entonces /a/1 aparece correctamente dentro de /a, pero con la implementación anterior, /b/1 también parece estar dentro de /a. Creo que esa es probablemente la respuesta correcta. Sin embargo, si este no es el resultado que prefiera, esto se soluciona fácilmente cambiando el caso return 1 para llamar al strcmp() y comparar también los nombres de ruta. Sin embargo, para que esto funcione, deberá comenzar por llamar al realpath tanto en F como en D. La llamada realpath puede ser bastante costosa (ya que puede necesitar golpear el disco varias veces).
  4. La ruta especial //foo/bar. POSIX permite que los nombres de ruta que comienzan con // sean especiales de una manera que no está bien definida. En realidad, me olvido del nivel preciso de garantía sobre semántica que ofrece POSIX. Creo que POSIX permite //foo/bar y //baz/ugh para referirse al mismo archivo. La verificación de dispositivo/inodo aún debe hacer lo correcto para usted, pero puede encontrar que no (es posible que encuentre que //foo/bar y //baz/ugh pueden referirse al mismo archivo pero tienen diferentes números de dispositivo/inodo).

Esta respuesta supone que empezamos con una ruta absoluta para ambos F y D. Si esto no está garantizado es posible que tenga que hacer alguna conversión usando realpath() y getcwd(). Esto será un problema si el nombre del directorio actual es más largo que PATH_MAX (que ciertamente puede suceder).

Cuestiones relacionadas