Em resumo: O código PHP ofuscado é código tornado propositadamente ilegível, em regra por codificação e compressão que só são resolvidas em tempo de execução. Os padrões de reconhecimento típicos são eval juntamente com base64_decode, gzinflate ou str_rot13, linhas muito longas, cadeias hexadecimais e nomes de funções compostos a partir de partes. Pesquise com comandos grep inofensivos, nunca execute o código e compare os achados com o pacote original.
Porque é que os atacantes escondem o seu código
Quem guarda uma webshell ou porta dos fundos num servidor quer que ela permaneça muito tempo por descobrir. O código legível daria nas vistas a qualquer administrador que abrisse o ficheiro, e qualquer scanner o encontraria. Por isso os atacantes embalam o seu código: é codificado, comprimido e só recomposto e executado quando é chamado.
Para o PHP2Shell não existem Indicators of Compromise confirmados. O código ofuscado não é uma característica desta vulnerabilidade, mas um sinal geral de que alguém introduziu código, seja por que caminho for. Como encontrar e remover webshells no seu conjunto descreve-o Reconhecer e remover webshells. Este artigo concentra-se na pergunta de como reconhecer código ofuscado.
eval, base64 e companhia: os blocos típicos
A ofuscação segue quase sempre o mesmo princípio: um bloco de dados ilegível é reconvertido por uma ou mais funções em código, que depois é executado. Os blocos seguintes aparecem repetidamente.
| Bloco | Exemplos | Porque chama a atenção |
|---|---|---|
| Execução | eval, assert com cadeia de caracteres (até ao PHP 7.x), create_function (até ao PHP 7.x) |
executa texto como código |
| Descodificação | base64_decode, str_rot13, hex2bin, strrev, convert_uudecode |
torna os dados legíveis outra vez |
| Descompressão | gzinflate, gzuncompress, gzdecode |
descomprime código embutido |
| Composição | cadeias de chr(), concatenação de pequenos fragmentos, escapes \x |
esconde nomes de funções |
| Chamada dinâmica | variáveis como nome de função, call_user_func, array_map com cadeia de caracteres |
a chamada não aparece em lado nenhum em texto claro |
| Acesso ao sistema | system, exec, shell_exec, passthru, proc_open, popen |
comandos no servidor |
Segundo o manual do PHP, o assert() deixou de avaliar cadeias de caracteres como código desde o PHP 8.0, e o create_function() foi removido no PHP 8.0. Em servidores com versões mais antigas do PHP, ambos continuam a ser termos de pesquisa relevantes.
Um bloco isolado raramente é suspeito; o base64_decode, por exemplo, é usado legitimamente por muitas aplicações. Torna-se suspeita a combinação: uma descodificação cujo resultado flui diretamente para uma execução.
Pesquisar sem perigo: comandos grep inofensivos
Os comandos seguintes pesquisam ficheiros apenas como texto. Não executam código PHP. Adapte o caminho.
# Execução combinada com descodificação ou descompressão
grep -rlE "(eval|assert)\s*\(\s*(base64_decode|gzinflate|gzuncompress|str_rot13|hex2bin)" /var/www --include="*.php*"
# Linhas muito longas (muitas vezes código comprimido)
grep -rlE ".{2000,}" /var/www --include="*.php*"
# Muitos escapes hexadecimais ou cadeias de chr() numa linha
grep -rlE "(\\\\x[0-9a-fA-F]{2}){20,}" /var/www --include="*.php*"
grep -rlE "(chr\s*\(\s*[0-9]+\s*\)\s*\.\s*){8,}" /var/www --include="*.php*"
# Chamada de função através de uma variável logo a seguir a uma atribuição a partir de cadeias de caracteres
grep -rnE "\\\$[a-zA-Z_]+\s*=\s*['\"][a-z_]{4,}['\"]\s*;\s*\\\$[a-zA-Z_]+\s*\(" /var/www --include="*.php*" | head -50
Verifique primeiro os resultados com ls -l e o carimbo de data/hora e depois abra-os apenas como texto, por exemplo com less. Nunca chame ficheiros suspeitos através do navegador, porque assim o servidor executá-los-ia.
Onde o código ofuscado costuma estar
O código ofuscado raramente está num ficheiro próprio e chamativo. Com mais frequência é inserido em ficheiros existentes, logo no início ou no fim, muitas vezes depois de muitas linhas em branco, para não saltar logo à vista ao abrir. Populares são os ficheiros carregados em cada chamada: o index.php central, ficheiros de configuração e os ficheiros de funções de um tema.
Um caminho particularmente discreto passa pela configuração. Com a definição auto_prepend_file, o PHP carrega um ficheiro adicional antes de cada script. Se ela estiver num .user.ini ou .htaccess criado por um atacante, o seu código corre em cada chamada de página sem que um único ficheiro da aplicação tenha sido alterado. O ficheiro incluído tem de bom grado uma extensão inofensiva como .ico ou .png.
# É carregado código adicional antes ou depois de cada script?
grep -rnE "auto_(prepend|append)_file" /var/www --include=".user.ini" --include=".htaccess" --include="*.conf" 2>/dev/null
# Código PHP em ficheiros com extensão de imagem ou ícone
grep -rlE "<\?php" /var/www --include="*.ico" --include="*.png" --include="*.jpg" --include="*.gif" 2>/dev/null
Distinguir a ofuscação legítima do código malicioso
Nem todo o código ilegível é malicioso. Três casos são frequentes:
- Codificadores comerciais: alguns fabricantes de plugins e software protegem o seu código com ferramentas como o ionCube ou o SourceGuardian. Os ficheiros são ilegíveis, mas remetem tipicamente para o loader necessário. Compare-os com o pacote original.
- Código minificado ou gerado: caches e motores de modelos produzem linhas longas sem quebras, mas sem executar texto descodificado.
- Dados embutidos: imagens ou tipos de letra como cadeia Base64 num ficheiro PHP são invulgares, mas inofensivos, desde que o resultado não seja executado.
O teste mais fiável é a comparação com a fonte: se o ficheiro vem inalterado do pacote do fabricante, é legítimo. Se não estiver em nenhum pacote original ou divergir dele, tem de ser investigado.
Não tente decifrar código ofuscado no servidor de produção, por exemplo substituindo eval por uma saída e chamando o ficheiro. Basta um erro nisso para executar o código malicioso. A análise pertence a um ambiente isolado.
Limites da pesquisa por padrões
Os atacantes conhecem os comandos de pesquisa habituais. Dividem nomes de funções, compõem-nos em tempo de execução ou transferem o código propriamente dito para a base de dados, ficheiros de imagem ou entradas de configuração. Uma pesquisa por padrões encontra por isso muito, mas não tudo. Porque é que também os scanners chegam aqui ao limite explica-o Scanners de malware para sites: o que encontram e o que não encontram.
Um achado raramente é além disso o único. Os atacantes usam muitas vezes o mesmo bloco em vários ficheiros, por exemplo um nome de variável chamativo ou uma sequência de caracteres marcante no início do bloco codificado. Depois de um achado confirmado, procure por isso especificamente essa sequência em todo o diretório web, também noutros sites no mesmo servidor. Assim encontra irmãos que nenhuma das pesquisas gerais por padrões apanhou.
Complemente a pesquisa com a comparação com ficheiros originais e com um olhar à base de dados, ver Verificar a base de dados quanto a código introduzido. Preventivamente, podem bloquear-se funções de sistema desnecessárias, como descrito no guia Usar o disable_functions corretamente.
Como ajuda concretamente a PHP SOS?
- Pesquisar: pesquisamos ficheiros e base de dados com padrões alargados e comparamos com pacotes originais.
- Analisar: examinamos código suspeito num ambiente isolado, sem pôr o seu sistema em risco.
- Limpar: com a remoção de malware, removemos todos os componentes e fechamos o caminho de entrada.
- Proteger: limitamos funções PHP e permissões de escrita e vigiamos ficheiros novos ou alterados.
Perguntas frequentes
O eval com base64_decode é sempre código malicioso?
Nem sempre, mas muito frequentemente. O código legítimo quase não precisa desta combinação. Em ficheiros que não pertencem à sua aplicação nem a um plugin conhecido, é um forte sinal de alerta.
Posso descodificar código ofuscado para o ler?
Só sem o executar e não no servidor de produção. Até uma execução acidental pode causar danos. Em caso de dúvida, deixe a análise a especialistas que trabalham com ambientes isolados.
Porque é que o meu scanner de malware não encontra o código?
Os scanners procuram padrões conhecidos. Os atacantes alteram constantemente o seu código, dividem nomes de funções ou compõem-nos em tempo de execução. Uma análise vazia não é por isso prova de um servidor limpo.
E os plugins cifrados comercialmente?
Alguns fabricantes protegem o seu código com codificadores como o ionCube ou o SourceGuardian. Esses ficheiros são ilegíveis, mas legítimos se vierem do fabricante. Compare-os com o pacote original.
O disable_functions ajuda contra código ofuscado?
Em parte. Funções como exec ou system podem ser bloqueadas, o eval não, porque é uma construção da linguagem e não uma função. O bloqueio dificulta o passo seguinte aos atacantes, mas não impede todo o código malicioso.