¿Qué cosas debo verificar cuando le doy a un cliente el control del parámetro de nombre de archivo a la función open () de C?

4

Estoy haciendo un proyecto escolar donde estamos construyendo un servidor web simple en C. Para implementar esto, leo la primera línea de la solicitud (todo lo que necesito para mis propósitos) y analizo la cadena central como el nombre del archivo usando %código%. Así que una petición típica sería algo como:

GET / HTTP/1.0

y mi código de respuesta sería algo como esto:

  // first token is request type
  token = strtok(buffer, " \n\r");
  if(strcmp(token, "GET") != 0) {
    // sendResponse just sends a response to the client
    // with the appropriate headers
    sendResponse(connection, -1, HTTP_11_400, strlen(HTTP_11_400), NULL);
    break;
  }
  char *request_type = token;

  // second token is url
  token = strtok(NULL, " \n\r");
  if(strstr(token, "../") != NULL) {
    sendResponse(connection, -1, HTTP_11_403, strlen(HTTP_11_403), NULL);
    break;
  }
  char *request_url = token;

  // third token is HTTP protocol
  token = strtok(NULL, " \n\r");
  if(strcmp(token, "HTTP/1.0") != 0 && strcmp(token, "HTTP/1.1") != 0) {
    sendResponse(connection, -1, HTTP_11_400, strlen(HTTP_11_400), NULL);
    break;
  }
  char *protocol = token;

  printf("filename: %s\n", request_url);
  if(strcmp(request_url, "/") == 0) {
    request_url = "index.html";
  }
  if(*request_url == '/') {
    request_url = request_url++;
  }
  char* fileType = strrchr(request_url, '.') + sizeof(char);
  fd = open(request_url, O_RDONLY);

Como puede ver, ya verifico que el cliente no pueda subir un directorio. En cuanto a mi código, básicamente estoy colocando una cadena ingresada por el usuario y confiando en él, ¿hay otras características de seguridad que debo tener en cuenta? (También tengo que escribir un documento sobre esta implementación y creo que sería bueno escribir sobre seguridad).

    
pregunta Mike Flynn 27.02.2014 - 00:32
fuente

4 respuestas

2

Esta es una pregunta engañosamente simple, ya que se reduce a cómo se pueden abusar de los caminos. Por mi parte, no pretendo saber de cuántas formas se pueden abusar porque hay muchas. Algunos de ellos son específicos de la plataforma y no se mencionó ninguna plataforma. No está haciendo ninguna coincidencia de rutas para el control de acceso, pasando el control a diferentes manejadores según el contenido de la ruta o descodificando el porcentaje de codificación, por lo que es un poco más simple.

El problema principal que veo con su código es la verificación incompleta de una ruta absoluta. El deseo parece ser que solo se devolverán los archivos en el directorio actual y, por lo tanto, se eliminará una barra diagonal inicial. Esto está incompleto, ya que al iniciar la ruta con más de una barra, un atacante podrá solicitar archivos fuera del directorio actual (por ejemplo: //etc/passwd ). Puede solucionar este problema eliminando cualquier número de barras diagonales iniciales o prepagando la ruta de la raíz del servidor a la ruta de la solicitud. Cualquiera de las dos haría. Ambos pueden ser sabios.

Después de las comprobaciones de apertura y error, deberías usar fstat para hacer un poco de cordura, comprobando que lo que acabas de abrir es solo un archivo simple y, de lo contrario, salta de la cuenta. Aunque no es probable que esté en la raíz del servidor, es probable que no quiera leer un archivo de dispositivo. Además, algunas plataformas (de nuevo, no especificadas en la pregunta) devolverán felizmente una secuencia de estructuras dirent con la simple apertura de un directorio que proporciona las listas de directorios del atacante.

También necesitas conocer las posibles rarezas de la plataforma en la que estás (tu código parece ser posix, pero podría ser una capa de biblioteca como cygwin). Si tiene la mala suerte de estar ejecutando en Windows, debe considerar cuestiones como si desea permitir el acceso a secuencias de archivos alternativas ( :stream sufijo). En particular, debe asegurarse de no permitir que el atacante acceda a los nombres de dos dispositivos ( enlace ).

Un último pedacito pedante. strtok no es seguro para subprocesos. No especifique en la pregunta si se trata de un servidor multihebra o no. Si es así tienes una condición de carrera. Incluso si no es multiproceso, cambiaría su código para usar strtok_r . Una vez vi un mod de apache escrito para 1.3 que hacía que los trabajadores pregenados se portaran a 2.x que usualmente hace hilos que usaban strtok . Fue una solución perfecta para la versión 1.3, pero los porteadores no notaron el problema cuando lo portaron.

    
respondido por el Graham 27.02.2014 - 04:04
fuente
1

El mínimo que debe hacer es asegurarse de que .. nunca se use como componente de ruta. Basta con comprobar que:

  • La ruta solicitada comienza con / . (Si no es así, es posible que desee rechazarlo o anteponer forzosamente / antes de realizar la próxima verificación).
  • La ruta solicitada no contiene la subcadena /../ .
  • La ruta solicitada no termina con el sufijo /.. .

Su cheque no es completamente correcto: le permite a través de /.. .

Preponga la raíz web a la ruta provista por el usuario (recuerde que debe comenzar con / ). La raíz web debe ser una ruta absoluta (en un programa multiproceso, a menudo no puede contar con el directorio actual).

Esto es suficiente para limitar los clientes a la raíz web. Tenga en cuenta que si hay algún enlace simbólico en la raíz web, se seguirán. Asegúrese de no tener archivos especiales (canalizaciones con nombre, nodos de dispositivos, etc.) en la raíz web.

Si tiene control de acceso, tenga en cuenta que puede haber varias rutas que hagan referencia al mismo archivo, por muchas razones, entre ellas:

  • // es equivalente a /
  • /./ es equivalente a /
  • enlaces simbólicos
  • algunos sistemas de archivos equiparan caracteres, por ejemplo, A = a en sistemas de archivos que no distinguen entre mayúsculas y minúsculas
  • el significado de caracteres no ASCII puede ser diferente en la solicitud y en el sistema de archivos, ya que depende de la codificación de caracteres.

Si convierte la solicitud de codificación de URL (con % -hexadecimal escapes) a una cadena sin formato, tenga en cuenta que los caracteres / y . pueden aparecer en esa etapa. Las verificaciones de ruta deben realizarse en la cadena final que utilizará como fragmento de ruta.

Mi respuesta asume Linux. Windows es más peligroso debido a sus nombres de archivo especiales (consulte la respuesta de Graham ).

    
respondido por el Gilles 27.02.2014 - 04:27
fuente
0

Enfóquelo como una asignación de "lo que el usuario debería hacer" a "los archivos en el servidor".

Comprobar que no pueden "subir" un directorio es un ejemplo de una limitación en la asignación: se supone que la configuración del servidor ha designado una carpeta "segura", de manera que todos los archivos de esa carpeta, recursivamente, son seguros.

Todo lo demás depende mucho de tu situación. Apache, por ejemplo, utiliza un archivo llamado .htaccess para controlar los permisos. En consecuencia, Apache debe asegurarse de que el usuario final no solicite una copia de .htaccess directamente (lo que les indicaría los permisos en todas las demás carpetas). Como otro ejemplo de las versiones de Apache basadas en Unix, Apache también verifica que el archivo solicitado no sea un enlace de sistema de archivos a un archivo fuera de la carpeta "segura".

    
respondido por el Cort Ammon 27.02.2014 - 03:32
fuente
0

C es un lenguaje complicado para realizar la manipulación de cadenas relacionadas con la seguridad. Pero presumiblemente no tienes otra opción.

Si necesita compatibilidad con Unicode, le recomiendo que utilice una biblioteca adecuada para ello. De lo contrario (ya que este es un proyecto escolar) sugeriría simplemente rechazar cualquier solicitud que no esté limitada a ascii de 7 bits. Entonces eso es (uchar) c > 127 para cualquier carácter en la solicitud.

También es posible que desee filtrar los caracteres que puedan representar cualquier negocio tonto. Si sabe que todos sus archivos consisten en A-Za-z0-9._- , entonces rechace cualquier solicitud que no siga este patrón. Mejor a la lista blanca en lugar de lista negra. Este paso podría tomar el lugar del anterior. Haga una lista de caracteres aceptables y, para cada carácter de la solicitud, verifique que existe en su lista blanca.

Ahora que tiene un conjunto de caracteres limpio, verifique el recorrido del directorio. Es más fácil buscar .. en cualquier lugar de la solicitud (los casos peligrosos son /../ , ../ al inicio y potencialmente /.. al final y .. solo). También es posible que desee buscar // , que en términos estrictos no es ilegal, pero podría ser un problema si se interpreta incorrectamente (es especialmente arriesgado al comienzo de la ruta local). Verifique que la solicitud comience con / (no opcional en HTTP por RFC),

Y luego, desde allí, debería estar seguro agregándolo a la raíz de su documento y verificando access (path,R_OK) to Verifique que exista y que sea legible. stat () para verificar que sea un archivo (no un directorio ni un enlace simbólico ... a menos que estén permitidos; pero ciertamente no es un zócalo o algo así, y ENTONCES puedes abrirlo para leer.

    
respondido por el tylerl 27.02.2014 - 04:12
fuente

Lea otras preguntas en las etiquetas