GNU/Linux >> Tutoriales Linux >  >> Linux

¿Por qué SUID está deshabilitado para scripts de shell pero no para binarios?

Hay una condición de carrera inherente a la forma en que shebang (#! ) normalmente se implementa:

  1. El kernel abre el ejecutable y encuentra que comienza con #! .
  2. El núcleo cierra el ejecutable y abre el intérprete en su lugar.
  3. 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) ).

  1. El núcleo abre el ejecutable y encuentra que comienza con #! . Digamos que el descriptor de archivo para el ejecutable es 3.
  2. El núcleo abre el intérprete.
  3. El kernel inserta /dev/fd/3 la lista de argumentos (como argv[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 un libc.so especialmente diseñado en $LD_LIBRARY_PATH (entre otras tácticas). Todos los sistemas sanos ignoran el LD_* 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.


Linux
  1. ¿Por qué no usar "cuál"? ¿Qué usar entonces?

  2. ¿Permitir Setuid en scripts de Shell?

  3. ¿Referencias de nombres circulares en la función Bash Shell, pero no en Ksh?

  4. ¿Por qué `zip` en un bucle For funciona cuando el archivo existe, pero no cuando no existe?

  5. ¿Cómo probar el cumplimiento de Posix de los scripts de Shell?

Shell Scripting para principiantes:cómo escribir Bash Scripts en Linux

Linux – ¿Por qué Locale Es_mx funciona pero no Es?

¿Por qué el documento principal Shell Here no funciona para el subcomando en Dash pero Bash funciona?

¿Por qué este "mientras se lee" funciona en una terminal, pero no en un script de Shell?

Comprender el bucle for en los scripts de Shell

Pruebas unitarias para scripts de shell