Skip to main content

Inyección de comandos en Python: ejemplos y prevención

Escrito por
Headshot of Rubaiat Hossain

Rubaiat Hossain

feature python command injection

21 de diciembre de 2023

0 minutos de lectura

A pesar de la reputación de Python por su simplicidad y versatilidad, garantizar la seguridad de los programas en Python puede ser un desafío si tú u otros miembros del equipo pasan por alto las prácticas recomendadas de seguridad durante el desarrollo.

Además, es probable que uses bibliotecas u otros proyectos de código abierto al crear una aplicación en Python. Sin embargo, estos recursos pueden introducir problemas de seguridad adicionales que dejan tu programa vulnerable a exploits como la inyección de comandos. Esta falla de seguridad crítica puede ser desastrosa para tus aplicaciones de Python. La inyección de comandos expone tus aplicaciones a la ejecución no autorizada de comandos, lo que puede provocar filtraciones de datos, vulneraciones del sistema y otras actividades maliciosas.

En este artículo, aprenderás todo sobre la inyección de comandos, incluido cómo puede manifestarse esta vulnerabilidad en tus programas. También conocerás las prácticas recomendadas de seguridad habituales para proteger tus aplicaciones de Python contra ataques de inyección de comandos.

¿Qué es la inyección de comandos?

La inyección de comandos ocurre cuando un atacante puede ejecutar comandos arbitrarios del sistema al inyectarlos en un programa vulnerable. La vulnerabilidad suele surgir cuando una aplicación pasa datos de usuario no seguros (por ejemplo, formularios, cookies y encabezados HTTP) a un shell del sistema.

Hay varios escenarios que pueden provocar vulnerabilidades de inyección de comandos, como pasar entradas de usuario sin depurar a comandos del sistema. Los atacantes pueden inyectar argumentos maliciosos cuando una aplicación pasa directamente la entrada del usuario a la línea de comandos. Un ejemplo es la vulnerabilidad de inyección de comandos encontrada en ciertas versiones de MLflow (una plataforma para el ciclo de vida del aprendizaje automático). Esta vulnerabilidad se debe a comprobaciones de validez insuficientes en el método predict() del archivo backend.py.

Además, las aplicaciones pueden construir cadenas de comandos de forma dinámica a partir de varios parámetros, incluida la entrada del usuario. Es posible aprovecharlas si el proceso de construcción no incluye las comprobaciones y validaciones adecuadas.

Lamentablemente, es fácil no comprender las implicaciones de los comandos del sistema. El uso incorrecto o la configuración errónea de estos comandos puede introducir fácilmente vulnerabilidades de inyección de comandos. Por ejemplo, las versiones afectadas de PaddlePaddle, una plataforma de aprendizaje profundo distribuido en paralelo, son vulnerables a la inyección de comandos en fs.py mediante el método os.system.

Las consecuencias de la inyección de comandos son graves. Los atacantes pueden obtener acceso no autorizado, exfiltrar datos confidenciales o corromper el sistema. Las brechas de seguridad de este tipo pueden generar pérdidas financieras, daños a la reputación y consecuencias legales para las organizaciones, además de causar daños incalculables a los usuarios cuya información confidencial podría ser robada.

Comprender y corregir la inyección de comandos en Python

La inyección de comandos sigue siendo una de las amenazas más graves para la seguridad de las aplicaciones. Para proteger las aplicaciones de Python contra la inyección de comandos, es necesario comprender a fondo estas vulnerabilidades y tomar medidas proactivas para contrarrestar posibles exploits.

Vulnerabilidades comunes que provocan la inyección de comandos

La siguiente sección describe algunas vulnerabilidades comunes que provocan la inyección de comandos en Python.

Explotación de entradas controladas por el usuario

Las entradas controladas por el usuario son datos o valores que este proporciona directamente, como datos de formularios, parámetros de URL o entradas a través de la línea de comandos. Cuando una aplicación procesa estas entradas sin validarlas adecuadamente y las usa directamente para ejecutar comandos del sistema, se vuelve vulnerable a los ataques de inyección de comandos.

Por ejemplo, observa este código en Python:

# Vulnerability: Directly using user-controlled input to execute a command.
command = input("Enter a command to execute: ")
os.system(command)

Este código solicita al usuario que proporcione un comando mediante el método input. Luego, esta entrada controlada por el usuario se pasa directamente al método os.system para ejecutarla, sin validarla ni comprobarla de ninguna manera. Esto significa que el intérprete de comandos del sistema procesa y ejecuta las entradas del usuario de inmediato:

Terminal de Ubuntu que muestra una prueba de inyección de comandos en Python con un comando malicioso y el listado de directorios

En este sencillo ejemplo, se usan comandos básicos del shell para probar el código. Sin embargo, es probable que los atacantes usen comandos peligrosos que provoquen una escalada de privilegios, pérdida de datos y otras situaciones no deseadas.

Uso inseguro de los comandos del sistema y del módulo subprocess

Los comandos del sistema y el módulo subprocess de Python permiten ejecutar comandos del shell y ofrecen formas sólidas de interactuar con el sistema operativo subyacente. Sin embargo, si no se usan de forma segura, pueden introducir vulnerabilidades de inyección de comandos, especialmente cuando se combinan con entradas controladas por el usuario.

Para ilustrarlo, observa esta vulnerabilidad:

# Vulnerability: Using subprocess with shell=True and unsanitized user input.
command = input("Enter a command for subprocess: ")
subprocess.run(command, shell=True)

En este caso, la entrada del usuario se ejecuta directamente mediante el método subprocess.run con shell=True habilitado. El argumento shell=True significa que el shell del sistema ejecuta el comando, lo que permite encadenar comandos y realizar otras operaciones potencialmente maliciosas:

Terminal de Ubuntu que muestra una inyección de comandos en subprocess de Python al ejecutar los comandos echo y ls.

Si no valida ni depura la entrada del usuario, el script queda expuesto a ataques de inyección de comandos y, en esencia, les entrega a posibles atacantes el control del shell del sistema.

Riesgos asociados con la construcción dinámica de comandos

Es común que los programas en Python construyan y ejecuten comandos del sistema de forma dinámica, especialmente cuando necesitan incorporar datos variables, como las entradas de los usuarios. Sin embargo, si esta construcción dinámica carece de las medidas de seguridad adecuadas, abre la puerta a vulnerabilidades de inyección de comandos.

Observa el siguiente fragmento de código en Python:

# Vulnerability: Dynamically constructing commands without sanity checks.
user_input = input("Enter a parameter: ")
command = f"echo Printing {user_input}"
os.system(command)

Este código solicita un parámetro al usuario y luego construye una cadena de comando que incorpora directamente esa entrada. El propósito del comando es imprimir la entrada del usuario. Sin embargo, como esta entrada no se valida ni se depura antes de incorporarla al comando, el código es vulnerable a la inyección de comandos:

Terminal de Ubuntu que muestra una demostración en Python que recibe “Hello; ls -l” e imprime “Hello” seguido de una lista de directorios.

Un atacante puede aprovechar esta vulnerabilidad al ingresar un payload que incluya separadores de comandos u operadores de control. Por ejemplo, un usuario podría ingresar Hello; ls -l, lo que primero imprimiría "Hello" y luego mostraría el contenido del directorio actual debido al punto y coma, que separa los comandos. Es un ejemplo sencillo, pero los atacantes pueden ingresar comandos más maliciosos, como rm -rf / o cat /etc/shadow.

Uso inseguro de eval()

La función eval() de Python evalúa dinámicamente una cadena como expresión de Python y devuelve el resultado. Aunque es una herramienta potente, también puede ser peligrosa. Considera el siguiente fragmento de código en Python:

# Vulnerability: Insecure use of the eval() method.
user_input = input("Enter a Python expression to evaluate: ")
try:
    result = eval(user_input)
    print(f"Result:\n{result}")
except Exception as e:
    print(f"Error:\n{e}")

Usar eval() en este código es peligroso porque no solo evalúa expresiones matemáticas o expresiones sencillas de Python, sino cualquier código de Python. Esto, combinado con la capacidad de Python para interactuar con el sistema subyacente mediante módulos como os.

Por ejemplo, un atacante puede ingresar lo siguiente:

__import__('os').popen('ls').read()

La entrada de ejemplo anterior importa el módulo os y luego ejecuta el comando ls en sistemas basados en Unix para mostrar el contenido del directorio. Esta es una demostración sencilla, pero los actores maliciosos pueden ingresar comandos más dañinos para manipular archivos, exfiltrar datos o incluso obtener acceso no autorizado al sistema:

Terminal ejecutando demo.py con una expresión de Python que usa os.popen('ls').read() y muestra el contenido del directorio.

Cómo mitigar las vulnerabilidades de inyección de comandos

Dado el posible impacto negativo de la inyección de comandos en tus aplicaciones de Python, es fundamental abordar este problema de manera eficaz. Por suerte, puedes mitigar de forma proactiva muchas vulnerabilidades de inyección de comandos desde las primeras etapas del ciclo de vida del desarrollo de software (SDLC) si sigues estas pautas.

Implementa una validación y depuración adecuadas de las entradas

Nunca debes confiar en las entradas de los usuarios ni de fuentes externas. Valida siempre las entradas según los patrones esperados y depúralas:

import re

def sanitize_input(user_input):
    # A simple example: Allow only alphanumeric characters
    return re.sub(r'[^a-zA-Z0-9]', '', user_input)

Ten en cuenta que el ejemplo anterior solo elimina los caracteres no alfanuméricos de la entrada. Aunque esto impide que los atacantes encadenen comandos, en una aplicación real probablemente necesitarás una depuración mucho más estricta; por ejemplo, permitir solo ciertas respuestas conocidas.

Usa consultas parametrizadas y sentencias preparadas

Una consulta parametrizada es una forma de estructurar un comando en la que primero defines el comando y su estructura y luego proporcionas por separado los parámetros que se insertarán en él. De esta manera, el comando y los datos nunca se concatenan como cadenas. En cambio, se mantienen separados y el sistema trata los datos únicamente como datos, no como código ejecutable.

Una sentencia preparada lleva este concepto un paso más allá. Primero se prepara la estructura del comando y luego solo se envían los parámetros para completar los marcadores de posición. Por ejemplo, el módulo subprocess de Python permite ejecutar comandos separándolos de sus argumentos, de forma similar a las consultas parametrizadas:

import subprocess

command = "ls"
directory = input("Which directory to list? ")
subprocess.run([command, directory])

Usa shell=True con precaución

Al usar el módulo subprocess de Python, evita el argumento shell=True, especialmente si trabajas con entradas de usuarios. Cuando ejecutas un comando con shell=True, el shell del sistema interpreta cualquier cadena que le pases. Si la cadena contiene entradas de usuarios o se construye a partir de varias fuentes sin depurarlas adecuadamente, un atacante puede inyectar comandos maliciosos:

command = input("Enter the directory to list: ")
subprocess.run(f"ls {command}", shell=True)

Esto puede ser desastroso y abrir la puerta al acceso no autorizado al sistema, el robo de datos, la instalación de malware, la escalada de privilegios y la ejecución de comandos arbitrarios. Evita usar shell=True o, si debes hacerlo, depura primero la entrada.

Evita pasar entradas de usuarios a los métodos de os

Pasar directamente entradas de usuarios a métodos de os, como os.spawn o similares, deja tu aplicación de Python vulnerable a la inyección de comandos. Por eso, debes usar otras API de Python si existen para tu caso de uso. Si necesitas un método de os, intenta usarlo sin entradas de usuarios. Si no puedes prescindir de ellas, depura todo y usa técnicas de codificación adaptadas al contexto.

Limita los privilegios e implementa controles de acceso sólidos

Ejecuta siempre tu aplicación con los privilegios mínimos necesarios. Si un atacante logra explotar una vulnerabilidad, esto limita el daño que puede causar. Además, considera aislar tu aplicación con herramientas o técnicas como chroot, contenedores o espacios de nombres de usuario.

Convenciones de programación segura en Python

En las siguientes secciones se describen algunas convenciones de programación segura que, al implementarse, pueden ayudar a proteger tus aplicaciones de Python contra las vulnerabilidades de inyección de comandos.

Valida y depura correctamente las entradas

La validación y depuración de entradas son fundamentales para proteger tus aplicaciones de Python contra la inyección de comandos. Al validar rigurosamente y depurar adecuadamente las entradas de los usuarios, reduces de manera eficaz la superficie de ataque, evitas la ejecución no autorizada de comandos y mejoras la seguridad general de tu software.

Observa el siguiente código:

def get_username():
    username = input("Enter your username: ")
    if len(username) < 3 or not username.isalnum():
        raise ValueError("Invalid username!")
    return username

Esta función garantiza que la entrada tenga una longitud especificada y solo contenga caracteres alfanuméricos antes de procesarla. Ten en cuenta que, en una aplicación real, probablemente necesitarás reglas de validación más estrictas que las de este sencillo ejemplo.

Usa con cuidado el módulo subprocess y funciones similares

El módulo subprocess puede iniciar procesos nuevos, conectarse a sus canales de entrada, salida y error, y obtener sus códigos de retorno. Por eso, es fundamental usar este módulo de forma segura.

Evita usar shell=True con el módulo subprocess, a menos que sea necesario, ya que esto puede ejecutar comandos en un shell y provocar ataques de inyección de comandos:

# Avoid
subprocess.run("ls -l", shell=True)

# Instead Use
subprocess.run(["ls", "-l"])

Maneja de forma segura las entradas de usuarios y los comandos externos

Nunca concatenes ni interpoles directamente las entradas de los usuarios en las cadenas de comandos. Usa siempre listas de argumentos:

user_input = input("Enter a directory name: ")

# Avoid
subprocess.run(f"ls {user_input}", shell=True)

# Prefer
subprocess.run(["ls", user_input])

Usa herramientas SAST y SCA para encontrar y corregir problemas de seguridad

Las herramientas de pruebas de seguridad de aplicaciones estáticas (SAST) y de análisis de composición de software (SCA) pueden ayudarte a analizar tanto el código que escribes como los componentes de código abierto que usa tu software, para identificar vulnerabilidades en las primeras etapas del ciclo de desarrollo.

La plataforma de Snyk facilita al máximo encontrar y corregir problemas de seguridad gracias a nuestro enfoque de IA híbrida, que detecta y corrige vulnerabilidades en tu código y te brinda comentarios en tiempo real sobre posibles riesgos de seguridad, además de recomendaciones para corregirlos. El uso de herramientas SAST y SCA como Snyk (que ofrece un plan gratuito para siempre) te ayuda a monitorear continuamente los problemas de seguridad en tu base de código y resolverlos a tiempo, antes de que lleguen a producción. Así podemos ayudar a prevenir lo siguiente:

  • La exposición a paquetes maliciosos que pueden comprometer tu sistema o tus datos

  • Los ataques a la cadena de suministro, en los que se compromete un componente de confianza y esto afecta a todo el software que depende de él

  • Las dependencias desactualizadas que pueden tener vulnerabilidades sin corregir

En general, incorporar herramientas SAST y SCA como Snyk en tu proceso de desarrollo garantiza que no solo crees software funcional, sino que también lo protejas frente al panorama cambiante de las ciberamenazas.

Prácticas recomendadas para desarrollar en Python de forma segura

En esta sección, explorarás hábitos de programación y flujos de trabajo de alto nivel que puedes implementar para promover el desarrollo seguro en Python en todos los equipos.

Realiza revisiones de código y auditorías de seguridad periódicas

Las revisiones periódicas de código, que incluyen exámenes minuciosos de la validación de entradas, los mecanismos de autenticación y el manejo de datos confidenciales, pueden ayudar a identificar posibles vulnerabilidades en las primeras etapas del ciclo de desarrollo. Al incluir en el proceso a integrantes del equipo especializados en seguridad, puedes detectar problemas que podrían pasar desapercibidos durante el desarrollo inicial, como prácticas de programación inseguras, configuraciones incorrectas y entradas sin depurar.

Realizar auditorías de seguridad periódicas es fundamental para garantizar que tu base de código siga siendo resistente frente a las amenazas cambiantes. Puedes usar herramientas como OpenVAS, ZAP y Metasploit para analizar tu programa y detectar vulnerabilidades.

Mantén el software y las bibliotecas actualizados

El software y las bibliotecas desactualizados suelen contener vulnerabilidades de seguridad conocidas. Debes actualizar continuamente tu intérprete de Python, tus frameworks y tus bibliotecas de terceros a sus versiones más recientes. Usa administradores de paquetes como pip y archivos de configuración como requirements.txt para administrar y actualizar las dependencias de manera eficiente.

Aprovecha las herramientas y los frameworks de seguridad

Puedes usar herramientas y frameworks de seguridad diseñados para Python para automatizar la detección de vulnerabilidades. Herramientas como Snyk pueden ayudar a identificar problemas en el código, configuraciones de seguridad incorrectas y vulnerabilidades en paquetes de terceros.

Encuentra y corrige vulnerabilidades en Python

Protege tus aplicaciones con el análisis de vulnerabilidades de Snyk y recomendaciones para corregirlas. 

No se requiere tarjeta de crédito.

O regístrate con Azure AD Docker ID Bitbucket

Al usar Snyk, aceptas cumplir con nuestras políticas, incluidos nuestros Términos de servicio y nuestra Política de privacidad.

Fomenta una mentalidad centrada en la seguridad

Fomentar una cultura de conciencia sobre seguridad entre los desarrolladores es fundamental. Procura ofrecer capacitaciones, talleres y recursos de seguridad para que los desarrolladores cuenten con las herramientas necesarias en esta materia. Cuando los desarrolladores priorizan la seguridad desde el inicio, mejora la calidad general del software y se reduce el riesgo de vulnerabilidades.

Un excelente recurso para aprender más sobre cómo crear culturas empresariales centradas en la seguridad es el podcast Secure Developer, que incluye numerosas entrevistas con líderes de seguridad de distintas empresas y profundiza en cómo crean sus programas de desarrollo.

Las extensiones para IDE que detectan vulnerabilidades en tiempo real son herramientas valiosas para identificar código vulnerable mientras lo escribes. Estas extensiones se integran directamente en tu entorno de desarrollo y resaltan posibles problemas de seguridad en tiempo real:

Visual Studio Code muestra código Python con advertencias de Snyk sobre inyección de comandos por entrada de usuario sin sanitizar.

Si detectas vulnerabilidades durante el desarrollo, ahorras tiempo y esfuerzo que, de otro modo, invertirías más adelante en el ciclo de desarrollo, cuando corregirlas sería mucho más costoso.

Conclusión 

La inyección de comandos es un problema grave que afecta a muchas aplicaciones de Python. En esta guía aprendiste cómo puede manifestarse esta vulnerabilidad y cuáles son los riesgos asociados. Al reconocer patrones de código vulnerables, puedes obtener información valiosa sobre posibles amenazas y las medidas necesarias para contrarrestarlas.

También aprendiste convenciones de programación segura, como la validación de entradas y el uso cuidadoso de los módulos de sistema integrados para ejecutar comandos. Al implementar estas estrategias, puedes reforzar la seguridad de tus aplicaciones de Python, frustrar posibles ataques y contribuir a la resiliencia general de tu software.

Para reforzar aún más tu proceso de desarrollo, considera usar herramientas como Snyk. Las extensiones de IDE de Snyk te brindan comentarios de seguridad en tiempo realk, analizan tu código mientras lo escribes y sugieren soluciones inmediatas. Es como tener a un experto en seguridad supervisándote para garantizar que escribas código seguro y resiliente desde el principio.

Empieza a proteger tus aplicaciones de Python

Encuentra y corrige vulnerabilidades en Python gratis con Snyk.

No se requiere tarjeta de crédito.

O regístrate con Azure AD Docker ID Bitbucket

Al usar Snyk, aceptas cumplir nuestras políticas, incluidos nuestros Términos del servicio y nuestra Política de privacidad.

Leer más

Blog

Los modelos de frontera encontraron las vulnerabilidades. Solo el atacante encontró las cadenas.

El análisis estático encontró las fallas, pero solo las pruebas de ataque en vivo demostraron cómo podían encadenarse para provocar brechas. Una comparación de Evo COS, Claude Security y Claude Code Security.

feature insights context
Blog

Los ataques autónomos ya están aquí. La defensa debe estar a su altura.

Los atacantes autónomos están reduciendo el tiempo disponible para defenderse. Descubre cómo el descubrimiento, la corrección, la validación y la prevención continuos pueden ayudar a los equipos de seguridad a seguirles el ritmo.

Blog

Por qué los agentes de programación con IA siguen generando fallas de control de acceso

Los agentes de programación con IA pueden generar lógica de autorización que compila y supera la revisión, pero expone los datos de un inquilino a otro. Descubre por qué es difícil detectar el control de acceso roto y cómo prevenirlo.