FlawOpen

flawopen.com/Command Injection/Java Runtime.exec()

Runtime.exec() em Java é seguro contra Command Injection?

CWE-78: OS Command InjectionPágina de referência
Resposta curta

Runtime.exec(String) não executa um shell implicitamente, portanto operadores de shell não são interpretados automaticamente. Ainda assim, a string é dividida em programa e argumentos, e texto não confiável pode alterar os argumentos recebidos pelo programa. ProcessBuilder deixa as fronteiras explícitas; valide cada valor e limite o executável e as opções permitidas.

ARRISCADO
// Entrada do usuário é concatenada à string do comando
Runtime.getRuntime().exec(
  "approved-tool --input " + filename
);
MAIS SEGURO
// Executável fixo e argumentos separados; valide filename primeiro
String safeName = validateFilename(filename);
new ProcessBuilder(
  "/opt/app/bin/approved-tool",
  "--input", safeName
).start();

Injeção em shell e injeção de argumentos são diferentes

Desde o Java 18, a Oracle marca Runtime.exec(String) como obsoleto por ser propenso a erros e recomenda exec(String[]) ou ProcessBuilder. A sobrecarga de string separa tokens por espaços em branco, podendo dividir argumentos como caminhos que contêm espaços. Runtime.exec(String) não inicia implicitamente sh nem outro shell; a documentação da Oracle descreve a conversão da string em um array de argumentos. Isso impede que metacaracteres comuns sejam tratados como sintaxe de shell, mas não torna a entrada confiável: a tokenização ainda pode criar argumentos adicionais, e o programa pode interpretar um argumento como opção. ProcessBuilder recebe uma lista de strings com o executável e os argumentos; em plataformas comuns, cada elemento define uma fronteira. Mantenha o executável fixo, valide os valores conforme a gramática esperada pelo programa e rejeite ou trate valores que pareçam opções. Use -- apenas quando a documentação do programa garantir esse comportamento. Não invoque um shell explicitamente, como sh -c, com entrada não confiável.

Perguntas frequentes

O fato de Runtime.exec(String) não iniciar um shell torna a chamada segura?

Não. Isso reduz o risco de injeção em shell nesse overload, mas ainda é possível injetar argumentos. O programa pode interpretar entradas controladas pelo atacante como opções ou nomes de arquivo. Use ProcessBuilder com executável fixo e argumentos separados e valide cada valor conforme o programa.

ProcessBuilder valida os argumentos por mim?

Não. Ele monta a lista de programa e argumentos e inicia um processo; não verifica se são seguros ou válidos. Aplique allowlists e validação na aplicação e siga as regras de argumentos documentadas pelo programa.

Referências

Veja também Injeção de comandos do SO (CWE-78): como funciona e como evitar em todas as linguagens — flawopen.com Risco de command injection em Node.js exec() Node.js exec() vs execFile(): diferenças e segurança