Inyección de comandos en Python: ejemplos y prevención
Rubaiat Hossain
21 de diciembre de 2023
0 minutos de lecturaA 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:
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:

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:
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:

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:
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:

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:
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:
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:

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:
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:
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:
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:
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:
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:
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:

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.



