# Evitar Command Injection em PHP

> Evitar Command Injection em PHP: dispensar a shell, usar proc_open com array, aplicar bem o escapeshellarg e encontrar chamadas perigosas no código.

Fonte: https://phpsos.de/pt/blog/command-injection-php/  
Atualizado em: 25/10/2026  
Prestador: PHP SOS, Assistência de emergência PHP e segurança de servidores. Ajuda imediata por WhatsApp: https://wa.me/491736494914?text=Ol%C3%A1%2C%20PHP%20SOS%2C%20precisamos%20de%20ajuda%20com%20o%20nosso%20servidor%20PHP.%20Empresa%3A%20%E2%80%A6%20Site%3A%20%E2%80%A6

**Em resumo:** A forma mais segura de evitar a Command Injection em PHP é não chamar qualquer shell: muitas tarefas são resolvidas diretamente por funções ou bibliotecas PHP. Onde é necessário um programa externo, passe o comando, desde o PHP 7.4, como array ao `proc_open()`; o PHP inicia então o programa sem shell. Verifique os valores contra uma lista de permissão, termine as opções com `--` e deixe o processo correr com permissões mínimas.

## Porque é que uma chamada ao sistema tem tanto poder

Tratamento de imagem, geração de PDF, arquivo, análise antivírus, consulta de uma entrada DNS: para tarefas como estas, as aplicações PHP chamam de bom grado programas externos. Tecnicamente é cómodo, basta uma linha com `exec()` ou `shell_exec()`. Do ponto de vista da segurança, é um dos pontos mais perigosos do código.

Se um comando é montado como texto e executado por uma shell, é a shell que decide o que é comando e o que são dados. Se um valor externo chegar aí sem verificação, pode alterar o significado do comando. No pior dos casos, a consequência é a mesma do PHP2Shell: execução de comandos arbitrários no servidor com as permissões do processo PHP.

A diferença: o PHP2Shell corrige-se com uma atualização do PHP. Uma Command Injection no código próprio persiste após qualquer atualização. A isso acresce que tais pontos estão muitas vezes em scripts auxiliares pensados apenas para administradores, por exemplo para manutenção, diagnóstico ou importações, e que mais tarde continuam acessíveis sem proteção.

## Onde nasce a Command Injection em PHP

Está em risco qualquer ponto em que o PHP inicia uma shell ou um programa. As funções para isso são:

| Função | Usa uma shell? | Nota |
|---|---|---|
| `exec()`, `system()`, `passthru()`, `shell_exec()` | sim | comando como texto, a shell interpreta-o |
| operador de acento grave | sim | equivale a `shell_exec()` |
| `popen()` | sim | comando como texto |
| `proc_open()` com string | sim | comando como texto |
| `proc_open()` com array | não | desde o PHP 7.4, os argumentos são passados diretamente |
| `mail()` com quinto parâmetro | indiretamente | parâmetros adicionais para o programa de correio |

Um padrão tipicamente inseguro tem este aspeto. O nome do ficheiro vem do pedido e vai parar diretamente à linha de comando:

```php
<?php
// INSEGURO: o valor do pedido passa a fazer parte de um comando de shell
$datei = $_POST['datei'];
exec('convert uploads/' . $datei . ' -resize 800x800 vorschau/' . $datei);
```

## Primeira escolha: nenhuma shell

A proteção mais eficaz é substituir a chamada ao sistema. A OWASP indica-o expressamente como primeira linha de defesa. Para muitas tarefas existem no PHP funções ou extensões próprias:

- Diretórios e ficheiros: `mkdir()`, `copy()`, `rename()`, `unlink()`
- Arquivos: `ZipArchive` ou `PharData`
- Imagens: GD ou Imagick como extensão PHP
- Consultas de rede: `dns_get_record()`, `gethostbyname()`, cURL
- Somas de verificação: `hash_file()`

Assim desaparece por completo a questão de saber que carateres uma shell interpreta. Além disso, as funções para comandos de sistema podem então ser bloqueadas na configuração, como descreve o guia [Usar corretamente o PHP disable_functions](https://phpsos.de/pt/ratgeber/disable-functions/).

## Quando é necessário um programa externo: proc_open com array

Desde o PHP 7.4, o `proc_open()` aceita o comando como array. Segundo o manual do PHP, o processo é então iniciado diretamente, sem passar pela shell, e o PHP assume o necessário mascaramento dos argumentos. Carateres como ponto e vírgula ou pipe deixam de ter significado especial.

```php
<?php
declare(strict_types=1);

$name = basename((string) ($_POST['datei'] ?? ''));
if (!preg_match('/^[a-z0-9_-]{1,64}\.(jpg|png)$/', $name)) {
    throw new InvalidArgumentException('Ungültiger Dateiname.');
}

$befehl = ['/usr/bin/convert', '/srv/app/uploads/' . $name, '-resize', '800x800',
           '/srv/app/vorschau/' . $name];

$prozess = proc_open($befehl, [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes);
if (!is_resource($prozess)) {
    throw new RuntimeException('Programm konnte nicht gestartet werden.');
}
$fehlerausgabe = stream_get_contents($pipes[2]);
$status = proc_close($prozess);
```

Três coisas tornam o exemplo robusto: o programa está fixo no código com o caminho completo, o nome do ficheiro é verificado contra um padrão estreito e cada argumento variável começa por um caminho absoluto, pelo que não pode ser lido como opção. Onde um valor está sozinho, termine as opções com `--`, desde que o programa o suporte segundo a documentação, ou rejeite valores que comecem por um hífen.

Em tarefas que precisam regularmente de programas externos, por exemplo tratamento de imagens ou PDF em maior escala, vale a pena desacoplar: a aplicação web limita-se a colocar um trabalho numa fila de espera, e um serviço de fundo separado, com utilizador e permissões próprios, executa-o. O processo PHP que responde a pedidos vindos da Internet deixa então de precisar de funções para comandos de sistema, e estas podem ser-lhe bloqueadas. Os programas externos devem ainda ter limites de tempo e de memória, para que um único trabalho não bloqueie o servidor.

Um caso especial é o `mail()`. O quinto parâmetro passa opções adicionais ao programa de correio do sistema. Valores de formulários, por exemplo um endereço de remetente, nunca podem aí chegar sem verificação. Mais robusta é uma biblioteca de correio que envie por SMTP e não precise de chamada ao sistema.

## escapeshellarg: aplicar corretamente e conhecer os limites

Onde o código existente ainda trabalha com linhas de comando, o `escapeshellarg()` para cada valor individual é a medida mínima. A função coloca o valor entre aspas, de modo que a shell o trate como exatamente um argumento. O `escapeshellcmd()` é inadequado para entradas do utilizador, porque permite parâmetros adicionais, como a OWASP mostra com um exemplo.

Limites importantes do `escapeshellarg()`:

1. **A injeção de argumentos continua possível:** um valor mascarado que comece por um hífen é, mesmo assim, lido pelo programa de destino como opção.
2. **O Windows comporta-se de forma diferente:** aí a função substitui, segundo o manual, certos carateres como `%` e `"` por espaços; os valores chegam, portanto, alterados.
3. **Conjunto de carateres:** consoante a definição de locale do servidor, os carateres não ASCII podem perder-se.

## Encontrar os pontos de ocorrência no código existente

Uma pesquisa de chamadas ao sistema faz-se depressa e dá uma lista fácil de gerir:

```bash
grep -rnE "\b(exec|system|passthru|shell_exec|popen|proc_open)\s*\(" src/ --include="*.php"
grep -rn '`' src/ --include="*.php" | grep -v '^\s*//'
```

Cada ocorrência precisa de resposta a três perguntas: a chamada pode ser substituída por funções PHP? Entram valores do exterior? Com que permissões corre o processo? A última pergunta é decisiva para o dano em caso grave. Um processo PHP com utilizador próprio e sem permissão de escrita sobre o código faz menos estragos, ver [Least Privilege: permissões do utilizador do servidor web](https://phpsos.de/pt/blog/rechte-des-webserver-benutzers/).

> **Importante:**
> Se um processo PHP-FPM ou do servidor web iniciar uma shell, é um forte sinal de alerta de intrusão. Como o reconhecer é descrito em [Sinal de alerta: o servidor web inicia uma shell](https://phpsos.de/pt/blog/webserver-startet-shell/).

## Como ajuda concretamente a PHP SOS?

- Procuramos todas as chamadas ao sistema na sua aplicação, nos plugins e nos scripts auxiliares.
- Substituímos chamadas, sempre que possível, por funções PHP e convertemos as restantes em chamadas sem shell.
- Bloqueamos na configuração do PHP as funções desnecessárias e verificamos as permissões do utilizador PHP.
- Testamos as alterações e documentamos que programas a aplicação ainda pode chamar.

A verificação de software próprio é oferecida por nós como [auditoria de segurança e teste de intrusão](https://phpsos.de/pt/leistungen/sicherheitsaudit-penetrationstest/).


## Perguntas frequentes

**O escapeshellarg chega contra a Command Injection?**  
Impede que um valor seja executado como outro comando. Não impede que o valor seja lido como opção do programa chamado, se começar por um hífen. Por isso, verifique também os valores e, onde o programa o suporta, termine as opções com --.

**Qual é a diferença entre escapeshellarg e escapeshellcmd?**  
O escapeshellarg trata um único valor como exatamente um argumento. O escapeshellcmd apenas mascara metacarateres numa linha de comando inteira e permite parâmetros adicionais. Para entradas do utilizador, o escapeshellarg é a escolha certa; uma chamada sem shell é ainda melhor.

**Devo bloquear exec e system com disable_functions?**  
Se a sua aplicação não precisa delas, sim. Isso limita os danos após uma intrusão. Mas não substitui código seguro, pois as aplicações que precisam de comandos continuam a precisar das funções.

**Como reconheço se uma Command Injection foi explorada?**  
Um forte sinal de alerta são processos PHP ou do servidor web que iniciam uma shell como sh ou bash. A isso juntam-se processos desconhecidos, novos ficheiros em pastas de upload e ligações de saída invulgares.

## Fontes

- [OWASP OS Command Injection Defense Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/OS_Command_Injection_Defense_Cheat_Sheet.html)
- [Manual do PHP: proc_open](https://www.php.net/manual/de/function.proc-open.php)
- [Manual do PHP: escapeshellarg](https://www.php.net/manual/de/function.escapeshellarg.php)
