Introdução
Em um recente projeto de hacker, conseguimos especificar variáveis de ambiente, mas não um processo em execução. Também não pudemos controlar o conteúdo do arquivo no disco, e a força bruta dos identificadores de processo (PIDs) e descritores de arquivo não deu resultados interessantes, excluindo exploits remotos LD_PRELOAD . Felizmente, um interpretador de linguagem de script foi executado, o que nos permitiu executar comandos arbitrários definindo certas variáveis de ambiente. Este blog discute como comandos arbitrários podem ser executados por vários intérpretes de linguagem de script em variáveis de ambiente mal-intencionadas.
Perl
Uma leitura rápida da seção da
ENVIRONMENT
página do manual perlrun(1)
revela muitas variáveis de ambiente que valem a pena explorar. A variável de ambiente PERL5OPT
permite definir opções de linha de comando, mas está limitada a apenas aceitar opções CDIMTUWdmtw
. Infelizmente, isso significa uma falta -e
, o que torna possível carregar o código perl para ser executado.
Nem tudo está perdido, no entanto, como mostrado na exploração para CVE-2016-1531 do Hacker Fantastic . A exploração grava um módulo perl malicioso em um arquivo
/tmp/root.pm
e fornece variáveis de ambiente PERL5OPT=-Mroot
e PERL5LIB=/ tmp
para a execução de código arbitrário. No entanto, isso foi uma exploração para uma vulnerabilidade de escalonamento de privilégio local, e o método genérico idealmente não deve exigir acesso ao sistema de arquivos. Olhando paraexploit by blasty para o CVE mesmo, ele não exigia a criação de um arquivo, usava variáveis de ambiente PERL5OPT=-d
e PERL5DB=system("sh");exit;
. As mesmas variáveis foram usadas para resolver o problema do CTF em 2013.
A sutileza final do método genérico é usar uma variável de ambiente em vez de duas. @justinsteven descobriu que isso é possível com
PERL5OPT=-M
. Enquanto o módulo perl é baixado, você pode usar -m
ou -M
, mas uma opção -M
permite adicionar código extra após o nome do módulo.
Prova de conceito
Exemplo 0: execução de código arbitrário com uma variável de ambiente versus perl executando um script vazio (/ 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ão
A julgar pela seção
ENVIRONMENT VARIABLES
em mana on python(1)
, PYTHONSTARTUP
inicialmente parece uma solução simples. Ele permite que você especifique o caminho para um script Python que será executado antes que o prompt seja exibido interativamente. O requisito para o modo interativo não parece ser um problema, pois uma variável de ambiente PYTHONINSPECT
pode ser usada para entrar no modo interativo, assim como -i
na linha de comando. No entanto, a documentação da opção -i
explica o PYTHONSTARTUP
que não será usado quando o python for iniciado com um script para execução. Isso significa que os PYTHONSTARTUP
dois PYTHONINSPECT
não podem ser combinados e PYTHONSTARTUP
só tem efeito quando o REPL do Python é iniciado imediatamente. Em última análise, isso significa quePYTHONSTARTUP
não é viável porque não tem efeito quando um script Python regular é executado.
Variáveis de ambiente
PYTHONHOME
e parecia promissor PYTHONPATH
. Ambos permitem a execução de código arbitrário, mas exigem que você seja capaz de criar diretórios e arquivos no sistema de arquivos também. Pode ser possível relaxar esses requisitos usando um sistema de arquivos virtual / proc e / ou arquivos ZIP.
A maioria das demais variáveis de ambiente é simplesmente verificada para uma string não vazia e, se for o caso, inclui uma configuração geralmente benigna. Uma das raras exceções é
PYTHONWARNINGS
.
Trabalhando com PYTHONWARNINGS
A documentação de
PYTHONWARNINGS
diz que isso é equivalente a especificar um parâmetro -W
. Este parâmetro é -W
usado para gerenciamento de alerta para especificar alertas e com que freqüência para exibi-los. A forma completa do argumento é action:message:category:module:line
. Embora monitorar alertas não pareça uma pista promissora, isso mudou rapidamente após testar a implementação.
Exemplo 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 mostra que, desde que nossa categoria especificada contenha um ponto, podemos começar a importar um módulo Python arbitrário.
O próximo problema é que a grande maioria dos módulos da biblioteca padrão do Python executa muito pouco código quando importados. Normalmente, eles apenas definem as classes a serem usadas posteriormente e, mesmo quando fornecem código para execução, o código normalmente é protegido verificando a variável __main__ (para determinar se o arquivo foi importado ou executado diretamente).
Uma exceção inesperada a esta regra é o módulo antigravidade . Os desenvolvedores de Python em 2008 incluíram um ovo de páscoa que pode ser invocado executando
import antigravity
... Essa importação abrirá imediatamente um quadrinho xkcd em seu navegador, brincando que a importação de antigravidade em Python torna possível voar.
Quanto à forma como o módulo
antigravity
abre seu navegador, ele usa outro módulo da biblioteca padrão chamado webbrowser
. Este módulo verifica seu PATH para uma ampla variedade de navegadores, incluindo mosaico, ópera, skipstone, konqueror, cromo, cromo, firefox, links, elinks e lynx. Ele também aceita uma variável de ambiente BROWSER
que indica qual processo executar. Nenhum argumento pode ser fornecido para um processo em uma variável de ambiente, e o url xkcd do comic é o único argumento embutido em código para o comando.
A capacidade de transformar isso em execução de código arbitrário depende de quais outros executáveis estão disponíveis no sistema.
Usando Perl para Executar Código Arbitrário
Uma abordagem é usar Perl, que geralmente é instalado no sistema e está até mesmo disponível na imagem padrão do Docker do Python. No entanto, você não pode usar o binário
perl
sozinho, porque o primeiro e único argumento é o url xkcd do comic. Este argumento gerará um erro e o processo será encerrado sem usar uma variável de ambiente PERL5OPT
.
Exemplo 2: PERL5OPT não tem efeito quando um URL é passado para 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
Felizmente, quando o Perl está disponível, scripts Perl padrão, como perldoc e perlthanks, também costumam estar disponíveis. Esses scripts também falharão com um argumento inválido, mas o erro, neste caso, ocorre depois do processamento da variável de ambiente PERL5OPT. Isso significa que você pode usar a carga útil da variável de ambiente Perl detalhada anteriormente neste blog.
Exemplo 3: PERL5OPT funciona como esperado com perldoc e 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)
Prova de conceito
Exemplo 4: execução de código arbitrário usando várias variáveis de ambiente com Python 2 e 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 postou a carga útil para o exploit Kibana (CVE-2019-7609) em seu blog . Uma vulnerabilidade de poluição de protótipo foi usada para definir variáveis de ambiente arbitrárias que resultaram na execução de comandos arbitrários. A carga útil de Michal usou a variável de ambiente
NODE_OPTIONS
e o sistema de arquivos proc em particular /proc/self/environ
.
Embora a técnica de Michal seja criativa e funcione muito bem no caso dele, nem sempre é garantido que funcione e tem algumas limitações que seria bom resolver.
A primeira limitação é que ele usa
/proc/self/environ
apenas se o conteúdo puder ser sintaticamente válido por JavaScript. Para fazer isso, você deve ser capaz de criar uma variável de ambiente e fazê-la aparecer primeiro no conteúdo do arquivo, /proc/self/environ
ou saber / enganar o nome da variável de ambiente que aparece primeiro e sobrescrever seu valor.
Outra limitação é que o valor da primeira variável de ambiente termina com um comentário de uma linha (//). Portanto, qualquer caractere de nova linha em outras variáveis de ambiente provavelmente causará um erro de sintaxe e impedirá a execução da carga útil. O uso de comentários de várias linhas (/ *) não resolverá o problema, pois eles devem ser fechados para serem sintaticamente corretos. Portanto, nos raros casos em que uma variável de ambiente contém um caractere de nova linha, é necessário saber / cancelar a definição do nome da variável de ambiente e sobrescrever seu valor com um novo valor que não contenha uma nova linha.
Deixaremos a remoção dessas limitações como um exercício para o leitor.
Prova de conceito
Exemplo 5. Executando código arbitrário com variáveis de ambiente no 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
Se você executá-lo
ltrace -e getenv php /dev/null
, descobrirá que o PHP está usando uma variável de ambiente PHPRC
. A variável de ambiente é usada ao tentar localizar e carregar um arquivo de configuração php.ini
. O exploit neex para CVE-2019-11043 usa vários parâmetros PHP para forçar a execução arbitrária de código. Em Orange, Tsai também tem um excelente post sobre como criar seu próprio exploit para o CVE, que usa uma lista de configurações ligeiramente diferente. Usando esse conhecimento, junto com o conhecimento obtido de técnicas anteriores do NodeJS e alguma ajuda de Brendan Scarwell , uma solução PHP foi encontrada com duas variáveis de ambiente.
Este método tem as mesmas limitações dos exemplos NodeJS.
Prova de conceito
Exemplo 6: Executando Código Arbitrário com Variáveis de Ambiente em 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)
Rubi
Nenhuma solução universal para Ruby foi encontrada ainda. Ruby aceita uma variável de ambiente
RUBYOPT
para especificar opções de linha de comando. A página do manual diz que RUBYOPT só pode conter -d, -E, -I, -K, -r, -T, -U, -v, -w, -W, --debug, --disable-FEATURE --enable-FEATURE
. A opção mais promissora é -r
forçar o Ruby a carregar a biblioteca usando require. No entanto, isso é limitado a arquivos com a extensão .rb
ou .so
.
Um exemplo de arquivo relativamente útil que encontrei
.rb
é tools/server.rb
o json gem, que está disponível após a instalação do Ruby em sistemas Fedora. Quando este arquivo é necessário, o servidor web é iniciado conforme mostrado abaixo:
Exemplo 7: Usando a variável de ambiente RUBYOPT para iniciar o processo ruby e iniciar o 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
Outra abordagem no Fedora é aproveitar o fato de que, na verdade,
/usr/bin/ruby
existe um script Bash que é iniciado /usr/bin/ruby-mri
. O script chama funções Bash que podem ser substituídas por variáveis de ambiente.
Prova de conceito
Exemplo 8: Usando uma função Bash exportada para executar um comando arbitrário
$ 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)
Conclusão
Esta postagem examinou alguns casos de uso interessantes para variáveis de ambiente que podem ajudar a obter a execução arbitrária de código por meio de vários interpretadores de linguagem de script sem gravar arquivos no disco. Espero que você tenha gostado de ler e esteja interessado em encontrar e compartilhar cargas úteis aprimoradas para essas e outras linguagens de script. Se você encontrar uma técnica genérica que funcione contra Ruby, será muito interessante ouvir sobre ela.
Veja também: " Dotfile Madness "