Hay una condición de carrera inherente a la forma en que shebang (#!
) normalmente se implementa:
- El kernel abre el ejecutable y encuentra que comienza con
#!
. - El núcleo cierra el ejecutable y abre el intérprete en su lugar.
- El kernel inserta la ruta al script en la lista de argumentos (como
argv[1]
), y ejecuta el intérprete.
Si se permiten secuencias de comandos setuid con esta implementación, un atacante puede invocar una secuencia de comandos arbitraria creando un enlace simbólico a una secuencia de comandos setuid existente, ejecutándola y cambiando el enlace después de que el núcleo haya realizado el paso 1 y antes de que el intérprete llegue a abriendo su primer argumento. Por esta razón, todos los dispositivos modernos ignoran el bit setuid cuando detectan un shebang.
Una forma de asegurar esta implementación sería que el kernel bloquee el archivo de script hasta que el intérprete lo haya abierto (tenga en cuenta que esto debe evitar no solo desvincular o sobrescribir el archivo, sino también cambiar el nombre de cualquier directorio en la ruta). Pero los sistemas Unix tienden a rehuir los bloqueos obligatorios, y los enlaces simbólicos harían que una característica de bloqueo correcta fuera especialmente difícil e invasiva. No creo que nadie lo haga de esta manera.
Algunos sistemas Unix implementan setuid shebang seguro usando una función adicional:la ruta /dev/fd/N
se refiere al archivo ya abierto en el descriptor de archivo N (entonces abriendo /dev/fd/N
es aproximadamente equivalente a dup(N)
).
- El núcleo abre el ejecutable y encuentra que comienza con
#!
. Digamos que el descriptor de archivo para el ejecutable es 3. - El núcleo abre el intérprete.
- El kernel inserta
/dev/fd/3
la lista de argumentos (comoargv[1]
), y ejecuta el intérprete.
Todas las variantes modernas de Unix, incluido Linux, implementan /dev/fd
, pero la mayoría no permite scripts setuid. OpenBSD, NetBSD y Mac OS X lo admiten si habilita una configuración de kernel no predeterminada. En Linux, la gente ha escrito parches para permitirlo, pero esos parches nunca se fusionaron. La página de shebang de Sven Mascheck tiene mucha información sobre shebang a través de unices, incluido el soporte de setuid.
Además, los programas que se ejecutan con privilegios elevados tienen riesgos inherentes que suelen ser más difíciles de controlar en lenguajes de programación de nivel superior, a menos que el intérprete haya sido diseñado específicamente para ello. La razón es que el código de inicialización del tiempo de ejecución del lenguaje de programación puede realizar acciones con privilegios elevados, según los datos heredados de la persona que llama con privilegios más bajos, antes de que el propio código del programa haya tenido la oportunidad de desinfectar estos datos. El tiempo de ejecución de C hace muy poco por el programador, por lo que los programas de C tienen una mejor oportunidad de tomar el control y desinfectar los datos antes de que suceda algo malo.
Supongamos que ha logrado que su programa se ejecute como root, ya sea porque su sistema operativo es compatible con setuid shebang o porque ha utilizado un contenedor binario nativo (como sudo
). ¿Ha abierto un agujero de seguridad? Quizás. El problema aquí es no sobre programas interpretados vs compilados. El problema es si su sistema de tiempo de ejecución se comporta de manera segura si se ejecuta con privilegios.
-
Cualquier ejecutable binario nativo vinculado dinámicamente es interpretado de alguna manera por el cargador dinámico (por ejemplo,
/lib/ld.so
), que carga las bibliotecas dinámicas requeridas por el programa. En muchos unices, puede configurar la ruta de búsqueda de bibliotecas dinámicas a través del entorno (LD_LIBRARY_PATH
es un nombre común para la variable de entorno), e incluso cargar bibliotecas adicionales en todos los archivos binarios ejecutados (LD_PRELOAD
). El invocador del programa puede ejecutar código arbitrario en el contexto de ese programa colocando unlibc.so
especialmente diseñado en$LD_LIBRARY_PATH
(entre otras tácticas). Todos los sistemas sanos ignoran elLD_*
variables en ejecutables setuid. -
En shells como sh, csh y derivados, las variables de entorno se convierten automáticamente en parámetros de shell. A través de parámetros como
PATH
,IFS
, y muchos más, el invocador del script tiene muchas oportunidades para ejecutar código arbitrario en el contexto de los scripts de shell. Algunos shells establecen estas variables en valores predeterminados sanos si detectan que el script se ha invocado con privilegios, pero no sé si hay alguna implementación en particular en la que yo confíe. -
La mayoría de los entornos de tiempo de ejecución (ya sean nativos, de código de bytes o interpretados) tienen características similares. Pocos toman precauciones especiales en los ejecutables setuid, aunque los que ejecutan código nativo a menudo no hacen nada más elegante que la vinculación dinámica (que sí toma precauciones).
-
Perl es una notable excepción. Admite explícitamente scripts setuid de forma segura. De hecho, su secuencia de comandos puede ejecutar setuid incluso si su sistema operativo ignoró el bit setuid en las secuencias de comandos. Esto se debe a que Perl se entrega con un ayudante raíz setuid que realiza las comprobaciones necesarias y vuelve a invocar al intérprete en los scripts deseados con los privilegios deseados. Esto se explica en el manual de perlsec. Solía ser que los scripts setuid perl necesitaban
#!/usr/bin/suidperl -wT
en lugar de#!/usr/bin/perl -wT
, pero en la mayoría de los sistemas modernos,#!/usr/bin/perl -wT
es suficiente.
Tenga en cuenta que el uso de un contenedor binario nativo no hace nada por sí mismo para evitar estos problemas. De hecho, puede empeorar la situación, ya que podría evitar que su entorno de tiempo de ejecución detecte que se invoca con privilegios y pase por alto su configuración de tiempo de ejecución.
Un contenedor binario nativo puede hacer que un script de shell sea seguro si el contenedor desinfecta el entorno. El script debe tener cuidado de no hacer demasiadas suposiciones (por ejemplo, sobre el directorio actual), pero esto es así. Puede usar sudo para esto siempre que esté configurado para desinfectar el entorno. La inclusión de variables en la lista negra es propensa a errores, así que siempre incluya en la lista blanca. Con Sudo, asegúrese de que env_reset
está activada, que setenv
está apagado, y eso env_file
y env_keep
solo contienen variables inocuas.
Todas estas consideraciones se aplican por igual a cualquier elevación de privilegio:setuid, setgid, setcap.
Reciclado de https://unix.stackexchange.com/questions/364/allow-setuid-on-shell-scripts/2910#2910
Principalmente porque
Muchos núcleos sufren de una condición de carrera que puede permitirle cambiar el shellscript por otro ejecutable de su elección entre los tiempos en que el proceso recién ejecutado se configura y cuando se inicia el intérprete de comandos. Si es lo suficientemente persistente, en teoría podría hacer que el kernel ejecute cualquier programa que desee.
así como otras razones que se encuentran en ese enlace (pero la condición de carrera del núcleo es la más perniciosa). Los scripts, por supuesto, se cargan de manera diferente a los programas binarios, que es donde aparece la falla.
Puede que le divierta leer este artículo del Dr. Dobb de 2001, que pasa por 6 pasos para escribir scripts de shell SUID más seguros, solo para llegar al paso 7:
Lección siete:no utilice secuencias de comandos de shell SUID.
Incluso después de todo nuestro trabajo, es casi imposible crear secuencias de comandos SUIDshell seguras. (Es imposible en la mayoría de los sistemas). Debido a estos problemas, algunos sistemas (p. ej., Linux) no respetarán SUID en los scripts de shell.
Esta historia habla sobre qué variantes tenían arreglos para la condición de carrera; la lista es más grande de lo que piensa... pero los scripts setuid todavía se desaconsejan en gran medida debido a otros problemas o porque es más fácil desalentarlos que recordar si está ejecutando una variante segura o insegura.
Este fue un problema lo suficientemente grande, en el pasado, que es instructivo leer acerca de la agresividad con la que Perl se acercó a compensarlo.