2009-04-20 17 views
5

Tengo una aplicación Django que estoy desarrollando que debe hacer una llamada al sistema a un programa externo en el servidor. Al crear el comando para la llamada al sistema, la aplicación toma valores de un formulario y los usa como parámetros para la llamada. Supongo que esto significa que esencialmente se pueden usar parámetros falsos y escribir comandos arbitrarios para que se ejecute el shell (por ejemplo, simplemente coloque un punto y coma y luego rm -rf *).¿Cómo evito la ejecución de comandos arbitrarios desde una aplicación Django que hace llamadas al sistema?

Esto es malo. Si bien la mayoría de los usuarios no son maliciosos, es un posible problema de seguridad. ¿Cómo maneja uno de estos posibles puntos de explotación?

EDIT (para aclarar): Los usuarios verán un formulario que se divide con varios campos para cada uno de los parámetros y opciones. Sin embargo, algunos campos estarán disponibles como campos de texto abierto. Todos estos campos se combinan y alimentan a subprocess.check_call(). Técnicamente, sin embargo, esto no se separa demasiado de simplemente darles a los usuarios un símbolo del sistema. Esto tiene que ser bastante común, entonces, ¿qué hacen otros desarrolladores para desinfectar la entrada para que no obtengan un Bobby Tables?

Respuesta

10

Basado en mi comprensión de la pregunta, supongo que no está permitiendo que los usuarios especifiquen comandos para ejecutar en el shell, sino solo argumentos para esos comandos. En este caso, se puede evitar shell injection ataques utilizando el módulo subprocess y no usando la cáscara (es decir, especificar utilizar el parámetro por defecto shell=False en el subprocess.Popen constructor.

Ah, y Nunca uso os.system() de las cadenas que contengan cualquier entrada proveniente de un usuario

+0

Leyendo a través de http://blog.littleimpact.de/index.php/2008/08/11/avoiding-shell-injection-in-ruby-python-and-php/ parece que voy a estar bien, así que siempre como shell = False, que es el predeterminado. (Es decir, se ejecutará uno y solo un comando). ¿Esto también es lo que dices? – gotgenes

+0

Sí, eso es lo que estoy diciendo. –

3

¡La respuesta es que los usuarios no deben escribir comandos de shell! No hay excusa para permitir la ejecución de comandos de shell arbitrarios.

Además, si realmente debe permitir que los usuarios proporcionen argumentos a comandos externos, no use un shell. En C, puede usar execvp() para suministrar argumentos directamente al comando, pero con django, no estoy seguro de cómo lo haría (pero estoy seguro de que hay una manera). Por supuesto, aún deberías hacer algunos argumentos sanitarios, especialmente si el comando tiene el potencial de causar algún daño.

0

Dependiendo del rango de sus comandos, puede personalizar el formulario, de modo que los parámetros se ingresen en campos de formulario separados. Aquellos que puede analizar para ajustar valores más fácilmente. Además, ten cuidado con los backticks y otras cosas específicas de shell.

6

Nunca confiando en los usuarios Cualquier información que provenga del navegador web debe considerarse contaminada. Y no intente validar los datos a través de JS ni limitar lo que se puede ingresar en los campos de FORMULARIO . Debe realizar las pruebas en el servidor antes de pasarlo a su aplicación externa. catión.

Update después de tu edición: no importa cómo se presente el formulario a los usuarios en su front-end el backend debe tratarlo como si viniera de un conjunto de cuadros de texto con texto grande parpadeo alrededor de ellos diciendo "inserto lo ¡quieres aquí! "

+0

Buen punto. No asumo que el cliente será responsable y validará; Voy a hacer la validación en el lado del servidor antes de ejecutar el comando. Sin embargo, vale la pena declararlo en beneficio de los demás. – gotgenes

4

Para hacer esto, debe hacer lo siguiente. Si no sabe qué son las "opciones" y los "argumentos", lea optparse background.

Cada "Comando" o "Solicitud" es en realidad una instancia de un modelo. Defina su modelo de Solicitud con todos los parámetros que alguien pueda proporcionar.

  1. Para obtener opciones simples, debe proporcionar un campo con una lista específica de CHOICES. Para opciones que están "on" o "off" (-x en la línea de comandos) que debe proporcionar una lista de opciones con dos valores humanos comprensibles ("Do X" y "no hacer X".)

  2. Para las opciones con un valor, debe proporcionar un campo que tome el valor de la opción. Debe escribir un Formulario con la validación de este campo. Volveremos a la validación del valor de la opción en un momento.

  3. Para los argumentos, tiene un segundo modelo (con un FK al primero). Esto puede ser tan simple como un solo campo de FilePath, o puede ser más complejo. De nuevo, es posible que también deba proporcionar un Formulario para validar las instancias de este Modelo.

La validación de la opción varía según el tipo de opción. Debe limitar los valores aceptables para que sean el conjunto de caracteres más reducido posible y escribir un analizador que esté absolutamente seguro de pasar solo caracteres válidos.

Sus opciones estarán en las mismas categorías que los tipos de opción en optparse - string, int, long, choice, float y complex. Tenga en cuenta que int, long, float y complex tienen reglas de validación ya definidas por los modelos y formularios de Django. La opción es un tipo especial de cadena, ya soportada por los Modelos y Formas de Django.

Lo que queda son "cadenas". Definir las cadenas permitidas Escribe una expresión regular para esas cuerdas. Validar usando la expresión regular. La mayoría de las veces, nunca puede aceptar presupuestos (", ' o `) en ninguna forma.

Paso final. Su modelo tiene un método que emite el comando como una secuencia de cadenas lista para subprocess.Popen.


Editar

Esta es la columna vertebral de nuestra aplicación. Es tan común que tenemos un único modelo con numerosas formas, cada una para un comando de lote especial que se ejecuta. El modelo es bastante genérico. Los formularios son formas bastante específicas para construir el objeto modelo. Esa es la forma en que Django está diseñado para funcionar, y ayuda a encajar con los patrones de diseño bien pensados ​​de Django.

Cualquier campo que esté "disponible como campos de texto abierto" es un error. Cada campo que está "abierto" debe tener una expresión regular para especificar lo que está permitido. Si no puede formalizar una expresión regular, debe reconsiderar lo que está haciendo.

Un campo que no se puede restringir con una expresión regular no puede ser un parámetro de línea de comandos. Período. Debe almacenarse en una columna de archivo a base de datos antes de ser utilizado.


Editar

gusta esta.

class MySubprocessCommandClass(models.Model): 
    myOption_1 = models.CharField(choice = OPTION_1_CHOICES, max_length=2) 
    myOption_2 = models.CharField(max_length=20) 
    etc. 
    def theCommand(self): 
     return [ "theCommand", "-p", self.myOption_1, "-r", self.myOption_2, etc. ] 

Su forma es un modelo modelo para este modelo.

No es necesario que save() las instancias del modelo. Los guardamos para que podamos crear un registro de lo que se ejecutó con precisión.

+0

Gracias por su respuesta. La respuesta de Rick Copeland indica que hay seguridad incluida en el módulo de subproceso, siempre que shell = False, se ejecute uno y solo un comando; todas las demás partes que siguen el comando inicial se consideran argumentos para el comando. Como controlo un comando al principio, ¿necesito diseñar realmente expresiones regulares para los argumentos? – gotgenes

+0

Además, tengo curiosidad, ¿por qué es necesario un modelo? Estoy teniendo dificultades para colocar todo junto como lo dijiste. Nuestro formulario ya está representado como una subclase de formularios. Formulario; estamos usando eso para la validación en este momento. ¿Cómo se relaciona esto con un modelo? ¿El comando al que se refiere es una clase de Comando real (Base), de django.core.management.base? – gotgenes

+0

No tiene nada que ver con la jerarquía de Management.Command. No, este Comando está completamente separado. –

Cuestiones relacionadas