flawopen.com/Incidents/equifax-struts-ognl-rce-july-2017
Equifax: violação expôs dados de quase 150 milhões
A CVE-2017-5638 permitiu injetar OGNL pelo cabeçalho Content-Type no portal Struts sem correção da Equifax. O ataque durou cerca de 76 dias e expôs dados de quase 150 milhões de pessoas.[1], [4]
Imagine um sistema de correspondência que transforma uma etiqueta de envio rejeitada em um aviso de erro. Se o aviso tratar a etiqueta como um modelo de instruções executável, o remetente pode fazer o sistema seguir instruções em vez de apenas informar que o endereço é inválido. O Struts já tinha uma correção, mas o aviso da Equifax foi enviado a uma lista desatualizada de administradores e uma varredura da rede não detectou a vulnerabilidade no portal.[1], [4]
Conceitos Centrais e Termos
OGNL- Object-Graph Navigation Language, a linguagem de expressões que o Struts usa para ler e alterar propriedades de objetos Java. Avaliar texto do atacante como OGNL é executar o código do atacante.[1], [6]
Parser multipart Jakarta- O componente do Struts que trata uploads de arquivos. Ao rejeitar um Content-Type malformado, ele montava uma mensagem de erro que depois era avaliada.[1], [3], [2]
Injeção de expressão (CWE-917)- Uma entrada não confiável acaba dentro de uma expressão que um interpretador avalia, e os dados viram instruções.[6]
Inventário de ativos- Um registro de qual software e qual versão roda em cada sistema. Sem ele, não dá para associar um aviso de patch às máquinas que precisam dele.[4]
Mapeamentos de frameworks
- Referência CWE para a fraqueza descrita neste guia: CWE-20 CWE-917
- CISA KEV — adicionado em 2021-11-03 — CVE-2017-5638
Linha do Tempo do Incidente
A Apache publica o boletim de segurança S2-045 e as versões corrigidas 2.3.32 e 2.5.10.1 do Struts.[1]
A Equifax informou posteriormente à GAO que uma nova intrusão começou em 13 de maio e que os atacantes passaram a extrair dados pessoais; um acesso não autorizado anterior já havia ocorrido, sem roubo de dados.[4]
A Equifax percebe tráfego suspeito depois de renovar um certificado vencido num equipamento de inspeção de tráfego.[4]
A Equifax anuncia publicamente a violação. Mais tarde, a GAO informa que dados de pelo menos 145,5 milhões de pessoas nos EUA e quase 1 milhão fora dos EUA foram afetados.[4]
Fluxo de Ataque Passo a Paso
Um cabeçalho Content-Type forjado
O atacante envia uma requisição de upload cujo cabeçalho Content-Type contém uma expressão OGNL no lugar de um tipo de mídia.[1]
Exfiltração lenta e silenciosa
Os atacantes usaram credenciais encontradas em um repositório para ampliar o acesso dos três bancos de dados do portal a outros 48 bancos de dados sem relação com o portal. A GAO relata cerca de 9.000 consultas e um ataque de aproximadamente 76 dias; um certificado vencido impedia a inspeção do tráfego criptografado.[4]
Código-Fonte: Vulnerável vs. Seguro
Java ilustrativo, não é código-fonte do Struts; a correção real nas versões 2.3.32 e 2.5.10.1 mudou a forma de montar o erro do multipart. A versão reforçada usa uma chave de mensagem fixa e passa o cabeçalho só como argumento posicional, então ele é formatado como texto e nunca vira parte de uma expressão. Ela também troca por um marcador qualquer valor que não pareça um tipo de mídia, de modo que nem uma mudança futura na mensagem consegue devolver o cabeçalho a uma string avaliada.[3], [2], [6]
import com.opensymphony.xwork2.TextProvider;
import javax.servlet.http.HttpServletRequest;
public final class UploadErrors {
private final TextProvider text; // resolves messages and evaluates expressions in them
public UploadErrors(TextProvider text) {
this.text = text;
}
public String describe(HttpServletRequest request) {
String contentType = request.getHeader("Content-Type");
// O cabeçalho bruto passa a fazer parte da mensagem que será avaliada.
return text.getText("Invalid content type: " + contentType);
}
}import com.opensymphony.xwork2.TextProvider;
import java.util.Locale;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
public final class UploadErrors {
private static final Pattern MEDIA_TYPE = Pattern.compile(
"^[a-z]+/[a-z0-9.+-]+(;\\s*[a-z0-9-]+=[a-z0-9._-]+)*$");
private final TextProvider text;
public UploadErrors(TextProvider text) {
this.text = text;
}
public String describe(HttpServletRequest request) {
String contentType = request.getHeader("Content-Type");
boolean wellFormed = contentType != null && contentType.length() <= 256
&& MEDIA_TYPE.matcher(contentType.toLowerCase(Locale.ROOT)).matches();
// Use uma chave de mensagem fixa; o cabeçalho é apenas um argumento posicional,
// e somente quando tiver o formato de um tipo de mídia.
return text.getText("upload.invalid.content.type",
new String[] { wellFormed ? contentType : "(malformed)" });
}
}Lista de Verificação de Segurança para Engenharia
- ✓Mantenha um SBOM de cada aplicação implantada e seja capaz de responder "onde rodamos Struts 2.3.x?" poucas horas depois de um aviso como o S2-045.[4]
- ✓Confronte os alvos do scanner de vulnerabilidades com o inventário de ativos; uma varredura limpa de um host que nunca esteve no escopo não prova que o patch foi aplicado.[4]
- ✓Gere alertas de vencimento de certificados em equipamentos de inspeção TLS e de monitoramento com pelo menos 30 dias de antecedência; aqui, um certificado vencido escondeu meses de exfiltração.[4]
- ✓Na revisão de código, rejeite qualquer chamada que passe texto não confiável concatenado a um resolvedor de mensagens, motor de templates ou avaliador de expressões; passe-o como argumento.[6]
- ✓Segmente os bancos de dados das aplicações expostas à internet, tire credenciais dos arquivos da aplicação e gere alerta para volumes incomuns de consultas feitas por contas de aplicação.[4]
Fontes
- Apache Struts — boletim de segurança S2-045 (CVE-2017-5638)
- Apache Struts — multipart parser error-message fix (commit 3523064)
- Apache Struts — upstream multipart error-handling fix (commit 6b8272c)
- GAO dos EUA — Proteção de dados: medidas da Equifax e de órgãos federais após a violação de 2017 (GAO-18-559)
- CVE-2017-5638 — registro CVE
- MITRE CWE-917 — injeção em linguagem de expressões
- Log4Shell: quando gravar num arquivo de log virou execução remota de código
- Injeção de comandos — referência completa da classe