php security apache file-upload temporary-files

nginx php 7



¿Se almacenan los archivos temporales de carga de PHP(/ PHP-FPM/Apache) en RAM en lugar de en el sistema de archivos(o solo encriptados)? (10)

Pregunta original

Entonces, el proyecto en el que estoy trabajando es mortalmente paranoico sobre las cargas de archivos.
En el alcance de esta pregunta, no estoy usando ese término en lo que respecta a las cargas útiles; Estoy hablando de confidencialidad .

Los programas siempre pueden bloquearse y dejar archivos temporales holgazaneando en el sistema de archivos. Eso es normal. El levemente confidencial paranoico puede escribir un cronjob que golpea la carpeta de archivos temporales cada pocos minutos y elimina cualquier cosa anterior a unos pocos segundos antes de la llamada cronjob (no todo , simplemente porque de lo contrario podría atrapar un archivo en proceso de carga).

... desafortunadamente, llevamos este paranoico un paso más allá:

Idealmente, nos encantaría nunca ver archivos temporales desde la carga de archivos en cualquier lugar, sino en la RAM asociada al proceso.

¿Hay alguna manera de enseñar PHP para buscar archivos temporales como blobs en la memoria en lugar de en el sistema de archivos? Usamos PHP-FPM como manejador de CGI y Apache como nuestro servidor web, en caso de que sea más fácil. (Tenga en cuenta también: ''Sistema de archivos'' es la palabra clave aquí, en lugar de ''disco'', ya que hay formas de asignar el sistema de archivos a la memoria RAM, pero eso no soluciona el problema de accesibilidad y la limpieza automática posterior al bloqueo. )

Alternativamente, ¿hay alguna forma de que estos archivos temporales puedan encriptarse inmediatamente cuando se escriben en un disco , para que nunca se retengan en el sistema de archivos sin cifrado?

Resumen del hilo

Desafortunadamente, solo puedo aceptar una respuesta, pero para cualquiera que lea esto, todo el hilo es extremadamente valioso y contiene las ideas colectivas de muchas personas. Dependiendo de lo que esperas lograr, la respuesta aceptada puede no ser interesante para ti . Si ha venido aquí a través de un motor de búsqueda, tómese un momento para leer todo el hilo .

Aquí hay una compilación de casos de uso como los veo para referencia rápida:

Re: Archivos temporales de PHP

  • RAM en lugar de disco (por ejemplo, debido a problemas de E / S) → RAMdisk / comparable ( plasmid87 , Joe Hopfgartner )

  • Cifrado inmediato (por sistema de archivos) → encFS ( ADW ) (+ a gotcha según Sander Marechal )

  • Permisos de archivos seguros → permisos de Linux nativos restrictivos (opcionalmente por vhost ) ( Gilles ) o SELinux (ver varios comentarios)

  • Memoria de procesos adjuntos en lugar de sistema de archivos (por lo que un bloqueo del proceso elimina los archivos) (originalmente previsto por la pregunta)

    • no permita que los datos del archivo lleguen directamente a PHP → reverse-proxy ( Cal )

    • desactivar la escritura de PHP en el sistema de archivos → ver el enlace de error de PHP en esta respuesta ( Stephan B ) o ejecutar PHP en modo CGI ( Phil Lello )

    • archivos de solo escritura → /dev/null filesystem ( Phil Lello ) (esto es útil si tiene acceso a los datos como una secuencia adicional, pero no puede desactivar la funcionalidad de escritura de archivos que se ejecuta en paralelo; si PHP permite esto no está claro)

Re: tus archivos, después de la carga


¿Has considerado crear un RAMdisk en Linux?

http://www.vanemery.com/Linux/Ramdisk/ramdisk.html

Como esto aparecerá como una ubicación del sistema de archivos nativo, solo necesita apuntar su instancia de PHP (suponiendo que tenga los permisos correctos) en esta ubicación.

No estoy seguro de cuáles son las ramificaciones si el disco falla, me imagino que la ubicación se volvería inescrutable o estaría ausente. Puede haber ramificaciones adicionales en relación con el rendimiento y los archivos de gran tamaño, pero como esta no es mi área de especialización, no puedo decirle mucho al respecto.

Espero que esto sea de alguna ayuda.


¿Has considerado poner una capa entre el usuario y el servidor web? Utilizar algo como perlbal con algún código personalizado frente al servidor web le permitirá interceptar los archivos cargados antes de que se escriban en cualquier parte, encriptarlos, escribirlos en un disco RAM local y luego realizar un proxy de la solicitud en el servidor web adecuado (con el nombre de archivo y clave de descifrado de los archivos).

Si el proceso de PHP falla, el archivo encriptado se deja pero no se puede descifrar. No se graban datos no cifrados en el disco (ram).


¿Has estudiado el uso de FUSE para crear un directorio cifrado al que solo puede acceder un usuario específico?

http://www.arg0.net/encfs

La memoria no se asociará con un proceso específico, pero los archivos solo serán accesibles para un usuario específico (¡el mismo que ejecuta su servidor web para que sea útil!), ¿Qué podría ser suficiente?


No estoy familiarizado con PHP, por lo que mi respuesta no se correlacionará directamente con un "cómo hacerlo", pero creo que está trabajando bajo conceptos erróneos sobre la protección que proporcionan diversas funciones del sistema, lo que le ha llevado a rechazar soluciones válidas a favor de soluciones que tienen exactamente las mismas propiedades de seguridad. De sus comentarios, supongo que está ejecutando Linux; la mayor parte de mi respuesta se aplica a otros equipos, pero no a otros sistemas como Windows.

Por lo que puedo ver, te preocupan tres escenarios de ataque:

  1. El atacante obtiene acceso físico a la máquina, la apaga, saca el disco y lee su contenido a su gusto. (Si el atacante puede leer tu RAM, ya has perdido).
  2. El atacante puede ejecutar código como usuario en la máquina.
  3. Un error en los scripts CGI permite que un proceso lea archivos temporales creados por otros procesos.

El primer tipo de atacante puede leer todo lo que no está encriptado en el disco, y nada que esté encriptado con una clave que no tiene .

Lo que el segundo tipo de atacante puede hacer depende de si puede ejecutar el código como el mismo usuario que ejecuta los scripts CGI.

Si solo puede ejecutar código como otros usuarios, entonces la herramienta para proteger los archivos es permisos . Debería tener un directorio que sea el modo 700 (= drwx------ ), es decir, al que solo drwx------ acceder un usuario y que sea propiedad del usuario que ejecuta los scripts CGI. Otros usuarios no podrán acceder a los archivos en este directorio. No necesita ningún cifrado adicional u otra protección.

Si puede ejecutar código como el usuario de CGI (que, por supuesto, incluye ejecutar el código como root), entonces ya habrá perdido. Puede ver la memoria de otro proceso si está ejecutando código como el mismo usuario: ¡los depuradores lo hacen todo el tiempo! En Linux, puede verlo fácilmente explorando /proc/$pid/mem . En comparación con la lectura de un archivo, leer la memoria de un proceso es un poco más desafiante desde el punto de vista técnico, pero en lo que respecta a la seguridad, no hay diferencia.

Por lo tanto, tener los datos en los archivos no es en sí mismo un problema de seguridad .

Examinemos ahora la tercera preocupación. La preocupación es que un error en el CGI le permita al atacante husmear en los archivos pero no ejecutar código arbitrario . Esto está relacionado con un problema de confiabilidad: si el proceso CGI muere, puede dejar atrás los archivos temporales. Pero es más general: el archivo puede leerse mediante un script que se ejecute simultáneamente.

La mejor forma de protegerse contra esto es evitar que los datos se almacenen en un archivo. Esto debería hacerse a nivel de PHP o sus bibliotecas, y no puedo ayudar con eso. Si no es posible, entonces nullfs como sugiere Phil Lello es una solución razonable: el proceso de PHP nullfs que está escribiendo datos en un archivo, pero el archivo en realidad nunca contendrá ningún dato.

Hay otro truco de Unix común que podría ser útil aquí: una vez que haya creado un archivo, puede desvincularlo (eliminarlo) y continuar trabajando con él. Tan pronto como se desvincula, no se puede acceder al archivo por su nombre anterior, pero los datos permanecen en el sistema de archivos siempre que el archivo esté abierto en al menos un proceso. Sin embargo, esto es principalmente útil para la confiabilidad, para que el sistema operativo elimine los datos cuando el proceso fallece por algún motivo. Un atacante que puede abrir archivos arbitrarios con los permisos del proceso puede acceder a los datos a través de /proc/$pid/fd/$fd . Y un atacante que puede abrir archivos en cualquier momento tiene una pequeña ventana entre la creación del archivo y su desvinculación: si puede abrir el archivo, puede mirar los datos que se agreguen posteriormente. Sin embargo, esta puede ser una protección útil, ya que convierte el ataque en uno que es sensible al tiempo y puede requerir muchas conexiones concurrentes, por lo que podría contrarrestarlo o al menos dificultarlo mucho más con un limitador de velocidad de conexión.


PHP almacenará los archivos cargados en el sistema de archivos antes de que su script tenga la posibilidad de interceptar los datos. Php: // input y $ HTTP_RAW_POST_DATA estarán vacíos en este caso (incluso cuando configure file_uploads = Off ).

Para archivos pequeños, podría intentar establecer <form enctype="application/x-www-form-urlencoded" ... pero no tuve éxito al usar esto. Le sugiero que recompile php y comente la parte que maneja las cargas de archivos, como en este informe de errores (Comentario de [email protected]).

Pro: Sin carga de archivos, Data in php: // input Con: Recompile, no proveedor support


Puede crear un tmpfs y montarlo con una umask adecuada. De esta forma, los únicos procesos que pueden leer archivos son los usuarios que lo crearon. Y, como se trata de un tmpfs , nunca se almacena nada en el disco.

Aconsejaría en contra de la solución encfs de ADW. Encfs no encripta los volúmenes, pero encripta los archivos archivados por archivo, dejando aún muchos metadatos expuestos.


Tus preocupaciones son validas Hay algunas soluciones a este problema. Una es almacenar el archivo en una base de datos. Una base de datos NoSQL como MongoDB o CouchDB está construida para almacenar archivos de manera eficiente. MySQL es otra opción y tiene ventajas sobre NoSQL. Una base de datos relacional como MySQL hace que sea muy fácil implantar el control de acceso porque puede relacionar los files y la tabla de users con una clave principal.

En MySQL puede usar el tipo de datos longblob contiene 2 ^ 32 bits o aproximadamente ~ 500mb. Puede crear una tabla que resida en la memoria utilizando el motor MEMORY: CREATE TABLE files ENGINE=MEMORY ... Además. MySQL tiene cifrado en forma de aes_encrypt() y des_encrypt() pero ambos usan el modo ECB, que es basura .

$sensi_file=file_get_contents($_FILES[''sensitive''][''tmp_name'']); unlink($_FILES[''sensitive''][''tmp_name'']);//delete the sensitive file. $sensi_file=mysql_real_escape_string($sensi_file);//Parametrized quires will also use this function so that should also be binary safe. mysql_query("insert into files (file)values(''$sensi_file'')");

Simplemente seleccione el archivo y $sensi_file como lo haría con $sensi_file . Tenga en cuenta que está escapando de la entrada para obtener los literales de caracteres y así almacenar el binario en bruto.


Tuve un destello de inspiración sobre esto: sistemas de archivos de agujero negro.

Básicamente, este es un sistema de archivos falso, donde los datos nunca se escriben, pero todos los archivos existen y no tienen contenido.

Hay una discusión sobre unix.se sobre esto, y una respuesta implica una implementación de FUSE de esto (citado aquí):

Esto no es compatible desde el primer momento en cualquier Unix que conozco, pero puedes hacer casi cualquier cosa con FUSE . Hay al menos una implementación de nullfs¹ , un sistema de archivos donde cada archivo existe y se comporta como /dev/null (esta no es la única implementación que he visto).

¹ No debe confundirse con * BSD nullfs , que es análogo a bindfs .

No he tenido la oportunidad de probar esto, pero si configura upload_tmp_dir en una ubicación de agujero negro, la carga (nunca | debería) nunca se escribirá en el disco, pero seguirá estando disponible en $HTTP_RAW_POST_DATA (o php: // input ) Si funciona, es mejor que parchar PHP


el enfoque más obvio sería:

no veo ningún problema con eso. solo asegúrate de asignar suficiente espacio duro.

puede encriptar en tiempo real con LUKS o Truecrypt

editar:

después de tu comentario, creo que ahora entiendo tu problema

Apache / php no es compatible con esto.

sin embargo, puede escribir su propio deamon, abrir una conexión de socket para escuchar y manejar los datos entrantes de la manera que desee. básicamente escribiendo su propio servidor web en php. no debería ser demasiado trabajo. también hay algunas buenas clases disponibles. zend tiene algunas bibliotecas de servidores que facilitan el manejo de http.

pero podrías hacer esto mucho más fácil en Perl. puede simplemente archivar la porción de datos de publicación por fragmento para procesar la memoria asociada. php solo tiene un flujo de trabajo diferente.


CGI al rescate!

Si crea un directorio cgi-bin y se configura correctamente, obtendrá el mensaje a través de stdin (por lo que puedo ver, los archivos no se escriben en el disco de esta manera).

Entonces, en tu configuración de apache agrega

ScriptAlias /cgi-bin/ /var/www/<site-dir>/cgi-bin/ <Directory "/var/www/<site-dir>/cgi-bin"> AllowOverride None Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch Order allow,deny Allow from all </Directory>

Luego, escriba un script PHP en modo CGI para analizar los datos de la publicación. A partir de mis pruebas (limitadas), no parece que se creen archivos locales. La muestra volca lo que lee de stdin así como las variables de entorno, para darle una idea de qué hay para trabajar.

Script de muestra instalado como / var / www // cgi-bin / test

#!/usr/bin/php Content-type: text/html <html><body> <form enctype="multipart/form-data" action="/cgi-bin/test" method="POST"> <!-- MAX_FILE_SIZE must precede the file input field --> <input type="hidden" name="MAX_FILE_SIZE" value="30000" /> <!-- Name of input element determines name in $_FILES array --> Send this file: <input name="userfile" type="file" /> <input type="submit" value="Send File" /> </form> <pre> <? echo "/nRequest body/n/n"; $handle = fopen ("php://stdin","r"); while (($line = fgets($handle))) echo "$line"; fclose($handle); echo "/n/n"; phpinfo(INFO_ENVIRONMENT); echo "/n/n"; ?> </pre> </body></html>

Salida de muestra Esta es la salida (fuente) cuando cargo un archivo de texto sin formato:

<html><body> <form enctype="multipart/form-data" action="/cgi-bin/test" method="POST"> <!-- MAX_FILE_SIZE must precede the file input field --> <input type="hidden" name="MAX_FILE_SIZE" value="30000" /> <!-- Name of input element determines name in $_FILES array --> Send this file: <input name="userfile" type="file" /> <input type="submit" value="Send File" /> </form> <pre> Request body -----------------------------19908123511077915841334811274 Content-Disposition: form-data; name="MAX_FILE_SIZE" 30000 -----------------------------19908123511077915841334811274 Content-Disposition: form-data; name="userfile"; filename="uploadtest.txt" Content-Type: text/plain This is some sample text -----------------------------19908123511077915841334811274-- phpinfo() Environment Variable => Value HTTP_HOST => dev.squello.com HTTP_USER_AGENT => Mozilla/5.0 (X11; U; Linux x86_64; en-GB; rv:1.9.2.16) Gecko/20110323 Ubuntu/10.04 (lucid) Firefox/3.6.16 HTTP_ACCEPT => text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 HTTP_ACCEPT_LANGUAGE => en-gb,en;q=0.5 HTTP_ACCEPT_ENCODING => gzip,deflate HTTP_ACCEPT_CHARSET => ISO-8859-1,utf-8;q=0.7,*;q=0.7 HTTP_KEEP_ALIVE => 115 HTTP_CONNECTION => keep-alive HTTP_REFERER => http://dev.squello.com/cgi-bin/test CONTENT_TYPE => multipart/form-data; boundary=---------------------------19908123511077915841334811274 CONTENT_LENGTH => 376 PATH => /usr/local/bin:/usr/bin:/bin SERVER_SIGNATURE => <address>Apache/2.2.14 (Ubuntu) Server at dev.squello.com Port 80</address> SERVER_SOFTWARE => Apache/2.2.14 (Ubuntu) SERVER_NAME => dev.squello.com SERVER_ADDR => 127.0.0.1 SERVER_PORT => 80 REMOTE_ADDR => 127.0.0.1 DOCUMENT_ROOT => /var/www/dev.squello.com/www SERVER_ADMIN => webmaster@localhost SCRIPT_FILENAME => /var/www/dev.squello.com/cgi-bin/test REMOTE_PORT => 58012 GATEWAY_INTERFACE => CGI/1.1 SERVER_PROTOCOL => HTTP/1.1 REQUEST_METHOD => POST QUERY_STRING => REQUEST_URI => /cgi-bin/test SCRIPT_NAME => /cgi-bin/test </pre> </body></html>