
Cargue de archivos
Es muy común que las aplicaciones, en algún momento u otro, necesiten permitir a los usuarios cargar un archivo (ya sea para usarlo o solo para almacenarlo) en algún lugar de la aplicación. Si bien parece bastante sencillo, la forma en que se implementa esta función puede ser bastante crítica debido a los posibles riesgos asociados a la forma en que se gestionan las subidas de archivos.
Eche un vistazo a este ejemplo rápido, solo para dar una mejor comprensión visual de lo que queremos decir.
Supongamos que se trata de una aplicación que permite a los usuarios subir una foto de perfil:
cadena pública Cargar imagen de perfil (archivo de formulario archivo subido)
{
//Generar la ruta para guardar el archivo subido en
var path = $». /uploads/avatars/ {request.user.id}/{UploadedFile.filename}»;
//Guarda el archivo
var localFile = archivo.openWrite (ruta);
LocalFile.write (UploadedFile.readToEnd ());
archivo local.flush ();
local.close file ();
//Actualiza la imagen de perfil
UserProfile.UpdateImagen de perfil de usuario (request.user, ruta)
ruta de regreso;
}
Esta sería una función de carga muy básica que también es vulnerable a Path Traversal.
Dependiendo de la implementación exacta de la aplicación, un atacante podría cargar otra página/script (por ejemplo, archivos.asp, .aspx o.php) que permitiera llamar directamente y ejecutar código arbitrario. Esto también podría permitir anular los archivos existentes.
Problema 1: Guardar en un disco local en lugar de en un almacén de datos externo
A medida que el uso de los servicios en la nube se hace más común, las aplicaciones se entregan en contenedores, las configuraciones de alta disponibilidad se han convertido en estándar y la práctica de escribir los archivos cargados en el disco local de la aplicación debe evitarse a toda costa.
Los archivos deben cargarse en una forma de almacenamiento central siempre que sea posible (almacenamiento en bloque o base de datos). En este caso, esto puede evitar clases enteras de vulnerabilidades de seguridad.
Problema 2: No se validan las extensiones
En muchos casos en los que se aprovecha una vulnerabilidad de carga de archivos, se basa en la capacidad de cargar un archivo con una extensión específica. Por ello, es muy recomendable utilizar una «lista de extensiones permitidas» para los archivos que se pueden subir.
Asegúrese de utilizar los métodos proporcionados por su lenguaje/marco para obtener la extensión del archivo y evitar problemas, como la inyección de bytes nulos.
También puede resultar tentador validar el tipo de contenido de la carga, pero hacerlo puede hacer que sea muy frágil, dado que los tipos de contenido utilizados para archivos específicos pueden diferir de un sistema operativo a otro. En realidad, tampoco te dice nada sobre el archivo en sí, ya que el tipo de contenido es simplemente una asignación desde una extensión.
Problema 3: No se impide el cruce de caminos
Otro problema común con la subida de archivos es que también suelen ser vulnerables al cruce de rutas. Eso es todo por sí solo, así que en lugar de intentar resumirlo aquí, dé la pauta completa sobre Recorrido de caminos una mirada.
More ejemplos
A continuación, tenemos algunos ejemplos más de subidas de archivos seguros e inseguros que puedes ver.
C# - Inseguro
cadena pública Cargar imagen de perfil (archivo cargado por iForm)
{
//Generar la ruta para guardar el archivo subido en
var path = $». /uploads/avatars/ {request.user.id}/{UploadedFile.filename}»;
//Guarda el archivo
var localFile = archivo.openWrite (ruta);
LocalFile.write (UploadedFile.readToEnd ());
archivo local.flush ();
local.close file ();
//Actualiza la imagen de perfil
UserProfile.UpdateImagen de perfil de usuario (request.user, ruta)
ruta de regreso;
}
C# - Seguro
lista pública <string>permitida de extensiones = new () {«.png», «.jpg», «.gif"};
cadena pública Cargar imagen de perfil (archivo cargado por iForm)
{
//NOTA: La mejor opción es evitar guardar archivos en el disco local.
var basePath = path.getFullPath (». /uploads/avatars/ «);
//Evite el cruce de rutas al no utilizar el nombre de archivo proporcionado. Tambien es necesario para evitar conflictos de nombres de archivo.
var newFileName = GenerateFileName (UploadedFile.filename);
//Generar la ruta para guardar el archivo subido en
var canonicalPath = Path.Combine (basePath, newFilename);
//Asegúrese de no haber guardado accidentalmente en una carpeta fuera de la carpeta base
seis (! Ruta canónica. Empieza con (BasePath)
{
return badRequest («Si intentó guardar el archivo fuera de la carpeta de carga»);
}
//Asegúrese de que solo se guarden las extensiones permitidas
seis (! Es la extensión permitida del archivo (extensiones permitidas cargadas)
{
return BadRequest («La extensión no está permitida»);
}
//Guarda el archivo
var localFile = File.openWrite (canonicalPath);
LocalFile.write (UploadedFile.readToEnd ());
archivo local.flush ();
local.close file ();
//Actualiza la imagen de perfil
UserProfile.UpdateUserImagen de perfil de usuario (Request.User, CanonicalPath)
ruta de regreso;
public bool generateFileName (cadena originalFileName) {
devuelve $ «{guid.newGuid ()} {path.getExtension (originalFileName)}»;
}
<string>public bool isFileAllowedExtension (cadena FileName, List extensions) {
devuelve extensions.contains (path.getExtension (fileName));
}