
Usando Struts2 y PowerShell para recrear la vulnerabilidad de inyección OGNL CVE-2017-5638.
Este repositorio es parte del proyecto del curso SC3010 Computer Security.
Recrea la CVE-2017-5638, una vulnerabilidad crítica de Ejecución Remota de Código en Apache Struts 2 (versiones 2.3.5–2.3.31 y 2.5–2.5.10), famosamente explotada en la filtración de datos de Equifax en 2017 para exfiltrar registros personales de aproximadamente 147 millones de personas.
La vulnerabilidad surge de un único fallo de diseño: cuando Apache Struts no puede analizar una solicitud de carga multipart/form-data, construye un mensaje de error incrustando el encabezado Content-Type sin depurarlo en una cadena que luego se pasa al evaluador de expresiones OGNL. Un atacante que coloca una expresión OGNL dentro del encabezado Content-Type logra que se ejecute en el lado del servidor como el usuario del proceso Tomcat.
Este repositorio contiene:
SC3010-Computer-Security/
├── simulation/
│ ├── backend/ # Servidor vulnerable Apache Struts2 2.3.28 (Java/Maven)
│ └── attack-script/ # Script de explotación para CVE-2017-5638
│ └── exploit_cve_2017_5638.ps1 # PowerShell (multiplataforma)
├── struts-src-code/ # Código fuente de referencia de Apache Struts2 + avisos legales
│ ├── licenses/ # LICENCIA, AVISO y licencias de componentes
│ └── src/
│ ├── struts2-core/ # Clases del pipeline de solicitudes + JakartaMultiPartRequest vulnerable
│ ├── xwork2/ # ActionContext.java, OgnlUtil.java
│ └── ognl/ # OgnlContext.java
├── diagrams/ # Diagramas de máquina de estados + secuencia, payload OGNL anotado
└── _notes/ # Lecturas complementarias
El diagrama a continuación muestra cada rama de decisión en el pipeline de solicitudes de Struts2. La ruta roja es la que el atacante fuerza; las grises → ... son rutas seguras que no se aplican durante el exploit.
flowchart TD
Start([Attacker sends HTTP POST /upload.action<br/>Content-Type: %{OGNL_PAYLOAD}.multipart/form-data])
Start --> PIPE
PIPE["<b>Struts2 filter → wrap → parse pipeline</b><br/>doFilter() → PrepareOperations.wrapRequest()<br/>→ Dispatcher.wrapRequest() → new MultiPartRequestWrapper()"]
PIPE --> D_CT{"Content-Type contains<br/>"multipart/form-data"?"}
D_CT -- "No → normal request ..." --> OUT_NORM([non-upload path ...])
D_CT -- "Yes (attacker appends<br/>.multipart/form-data to payload)" --> PARSE
PARSE[/"<b>JakartaMultiPartRequest.parse()</b><br/>→ Commons FileUpload.parseRequest()"/]
PARSE --> D_VALID{"FileUpload can parse<br/>Content-Type?"}
D_VALID -- "Yes → normal file upload ..." --> OUT_OK([files extracted, action executes ...])
D_VALID -- "No — malformed Content-Type<br/>throws InvalidContentTypeException<br/>(message = raw Content-Type string)" --> CATCH
CATCH["parse() catch block<br/>calls buildErrorMessage(e, {})"]
CATCH --> FIND
FIND[/"<b>buildErrorMessage()</b><br/>→ LocalizedTextUtil.findText(class, errorKey, locale, e.getMessage(), args)"/]
FIND --> D_KEY{"Resource bundle has key<br/>struts.messages.upload.error.*?"}
D_KEY -- "Yes → safe localised message ..." --> OUT_SAFE([error displayed safely ...])
D_KEY -- "No — falls back to<br/>e.getMessage() as the default<br/>(attacker's raw Content-Type)" --> TRANSLATE
TRANSLATE["TextParseUtil.translateVariables()<br/>scans string for %{...} expressions"]
TRANSLATE --> D_OGNL{"String contains<br/>%{...} OGNL expression?"}
D_OGNL -- "No → plain error string ..." --> OUT_PLAIN([safe error message ...])
D_OGNL -- "Yes — evaluates OGNL<br/>inside attacker content" --> SANDBOX
SANDBOX[/"<b>OGNL sandbox bypass</b><br/>#ognlUtil.getExcludedClasses().clear()<br/>#ognlUtil.getExcludedPackageNames().clear()"/]
SANDBOX --> ACCESS
ACCESS[/"<b>Unrestricted reflection</b><br/>#context.setMemberAccess(DEFAULT_MEMBER_ACCESS)"/]
ACCESS --> RCE
RCE[/"<b>RCE</b><br/>Runtime.getRuntime().exec(cmd)"/]
RCE --> Done([Command executed as Tomcat process user])
style Start fill:#d32f2f,color:#fff
style Done fill:#d32f2f,color:#fff
style OUT_NORM fill:#9e9e9e,color:#fff
style OUT_OK fill:#9e9e9e,color:#fff
style OUT_SAFE fill:#9e9e9e,color:#fff
style OUT_PLAIN fill:#9e9e9e,color:#fff
style SANDBOX fill:#b71c1c,color:#fff
style ACCESS fill:#b71c1c,color:#fff
style RCE fill:#b71c1c,color:#fff
style CATCH fill:#e65100,color:#fff
style TRANSLATE fill:#e65100,color:#fffPara la máquina de estados completa con explicaciones de las ramas, consulte diagrams/cve-2017-5638-state-machine.md.
Para una vista de diagrama de secuencia y desglose del payload OGNL anotado, consulte diagrams/cve-2017-5638-attack-chain.md.
Este repositorio reproduce partes del código fuente de Apache Struts 2.3.28 con fines académicos de investigación en seguridad.
Apache Struts es Copyright © 2000–2016 The Apache Software Foundation.
Licenciado bajo la Apache License, Version 2.0.
Una copia de la licencia está disponible enstruts-src-code/licenses/LICENSE.txt.
El aviso de atribución completo requerido por la Apache License está enstruts-src-code/licenses/NOTICE.txt.
Apache Struts 2 incluye componentes adicionales de terceros, cada uno regido por su propia licencia:
| Componente | Archivo de licencia |
|---|---|
| OGNL (Biblioteca de Navegación de Grafos de Objetos) | struts-src-code/licenses/OGNL-LICENSE.txt |
| XWork | struts-src-code/licenses/XWORK-LICENSE.txt |
| FreeMarker | struts-src-code/licenses/FREEMARKER-LICENSE.txt |