Apache Struts2 文件上传逻辑绕过(CVE-2024-53677)(S2-067)

admin 2024年12月20日09:51:43评论19 views字数 11106阅读37分1秒阅读模式

前言

Apache官方公告又更新了一个Struts2的漏洞,考虑到很久没有发无密码的博客了,再加上漏洞的影响并不严重,因此公开分享利用的思路。

分析

影响版本

Struts 2.0.0 - Struts 2.3.37 (EOL), Struts 2.5.0 - Struts 2.5.33, Struts 6.0.0 - Struts 6.3.0.2

环境搭建

Struts2的环境搭建比较简单,分析时使用了两种不同漏洞场景的代码

UploadsAction对应多文件上传的场景,也是最简单的场景,不需要任何其他背景知识方便理解

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
package com.struts2;import com.opensymphony.xwork2.ActionSupport;import java.io.*;import java.util.ArrayList;import java.util.List;publicclassUploadsActionextendsActionSupport{privatestaticfinallong serialVersionUID =1L;private List<File> upload;private List<String> uploadContentType;private List<String> uploadFileName;private List<String> uploadedFileNames =new ArrayList<String>();public List<File>getUpload(){return upload;}publicvoidsetUpload(List<File> upload){this.upload = upload;}public List<String>getUploadContentType(){return uploadContentType;}publicvoidsetUploadContentType(List<String> uploadContentType){this.uploadContentType = uploadContentType;}public List<String>getUploadFileName(){return uploadFileName;}publicvoidsetUploadFileName(List<String> uploadFileName){this.uploadFileName = uploadFileName;}public List<String>getUploadedFileNames(){return uploadedFileNames;}public StringdoUpload(){for (int i =0; i < uploadFileName.size(); i++) {uploadedFileNames.add(uploadFileName.get(i));}return SUCCESS;}}

UploadAction对应单文件上传的场景

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
package com.struts2;import com.opensymphony.xwork2.ActionSupport;import java.io.*;import java.util.ArrayList;import java.util.List;publicclassUploadActionextendsActionSupport{privatestaticfinallong serialVersionUID =1L;private File upload;private String uploadContentType;private String uploadFileName;public FilegetUpload(){return upload;}publicvoidsetUpload(File upload){this.upload = upload;}public StringgetUploadContentType(){return uploadContentType;}publicvoidsetUploadContentType(String uploadContentType){this.uploadContentType = uploadContentType;}public StringgetUploadFileName(){return uploadFileName;}publicvoidsetUploadFileName(String uploadFileName){this.uploadFileName = uploadFileName;}public StringdoUpload(){return SUCCESS;}}

struts.xml

12345678910111213141516
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPEstrutsPUBLIC"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN""http://struts.apache.org/dtds/struts-2.0.dtd"><struts><packagename="upload"extends="struts-default"><actionname="upload"class="com.struts2.UploadAction"method="doUpload"><resultname="success"type="">/file.jsp</result></action></package><packagename="uploads"extends="struts-default"><actionname="uploads"class="com.struts2.UploadsAction"method="doUpload"><resultname="success"type="">/files.jsp</result></action></package></struts>

file.jsp

123
<%@page contentType="text/html; charset=UTF-8" language="java" %><%@ taglib prefix="y4tacker" uri="/struts-tags"%>上传的文件名是:<y4tacker:property value="uploadFileName" />

files.jsp

1234567891011
<%@page contentType="text/html; charset=UTF-8" language="java" %><%@ taglib prefix="y4tacker" uri="/struts-tags"%><y4tacker:if test="uploadedFileNames.size() > 0">文件上传成功:<y4tacker:iterator value="uploadedFileNames"><li><y4tacker:property /></li></y4tacker:iterator></y4tacker:if><y4tacker:else>no files.</y4tacker:else>

web.xml

12345678910111213141516171819202122232425262728293031323334
<?xml version="1.0" encoding="UTF-8"?><!--Licensed to the Apache Software Foundation (ASF) under one or morecontributor license agreements. See the NOTICE file distributed withthis work for additional information regarding copyright ownership.The ASF licenses this file to You under the Apache License, Version 2.0(the "License"); you may not use this file except in compliance withthe License. You may obtain a copy of the License athttp://www.apache.org/licenses/LICENSE-2.0Unless required by applicable law or agreed to in writing, softwaredistributed under the License is distributed on an "AS IS" BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the License for the specific language governing permissions andlimitations under the License.--><web-appxmlns="http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaeehttp://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"version="4.0"metadata-complete="true"><filter><filter-name>struts2</filter-name><filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class></filter><filter-mapping><filter-name>struts2</filter-name><url-pattern>*.action</url-pattern></filter-mapping></web-app>

目录结构如下

Apache Struts2 文件上传逻辑绕过(CVE-2024-53677)(S2-067)

前置知识

由于是S2-066的绕过,所以需要对上一个漏洞的原理有所了解,在我上一篇文章中Apache Struts2 文件上传分析(S2-066)对此有详细的介绍,这里就不详细描述了,对于上一个漏洞,官方的修复也很暴力,在FileUploadInterceptor中设置参数时,忽略大小写遍历删除同名参数再做添加

123456789101112131415161718
public HttpParametersappendAll(Map<String, Parameter> newParams){this.remove(newParams.keySet());this.parameters.putAll(newParams);returnthis;}public HttpParametersremove(Set<String> paramsToRemove){Iterator var2 = paramsToRemove.iterator();while(var2.hasNext()) {String paramName = (String)var2.next();this.parameters.entrySet().removeIf((p) -> {return ((String)p.getKey()).equalsIgnoreCase(paramName);});}returnthis;}

S2-067,不同于以往的漏洞分析,这一次不能通过官方的commits对比快速定位漏洞原因

Apache Struts2 文件上传逻辑绕过(CVE-2024-53677)(S2-067)

原因是官方直接使用了一个新的类,在官方文档中,告诉我们在处理上传时推荐使用新的拦截器org.apache.struts2.interceptor.ActionFileUploadInterceptor

简单分析不难看到,其与之前的org.apache.struts2.interceptor.FileUploadInterceptor最大的区别在于,这一次并没有参数存储的过程,因此也不存在变量覆盖的问题

Apache Struts2 文件上传逻辑绕过(CVE-2024-53677)(S2-067)

失败的尝试

在一开始,没有其他背景知识的情况下,我的第一个思路是java.lang.String#equalsIgnoreCase是否安全?

查看Java的实现可以看到,在regionMatches中对于每个字符的比较过程中都是同时转小写以及大写做比较

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
publicbooleanequalsIgnoreCase(String anotherString){return (this == anotherString) ?true: (anotherString !=null)&& (anotherString.value.length == value.length)&& regionMatches(true,0, anotherString,0, value.length);}publicbooleanregionMatches(boolean ignoreCase,int toffset,String other,int ooffset,int len){char ta[] = value;int to = toffset;char pa[] = other.value;int po = ooffset;// Note: toffset, ooffset, or len might be near -1>>>1.if ((ooffset <0) || (toffset <0)|| (toffset > (long)value.length - len)|| (ooffset > (long)other.value.length - len)) {returnfalse;}while (len-- >0) {char c1 = ta[to++];char c2 = pa[po++];if (c1 == c2) {continue;}if (ignoreCase) {// If characters don't match but case may be ignored,// try converting both characters to uppercase.// If the results match, then the comparison scan should// continue.char u1 = Character.toUpperCase(c1);char u2 = Character.toUpperCase(c2);if (u1 == u2) {continue;}// Unfortunately, conversion to uppercase does not work properly// for the Georgian alphabet, which has strange rules about case// conversion. So we need to make one last check before// exiting.if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) {continue;}}returnfalse;}returntrue;}

在这个时候,突然想到phithon曾写过一篇关于:Fuzz中的javascript大小写特性的文章

同样的,有个天马行空的思路就是,有没有可能存在一些字符它的大写等于另一个字符的小写呢?如果存在这种情况,在后面参数绑定过程中ognl.OgnlRuntime#capitalizeBeanPropertyName做参数处理时又通过对其转大写还原成正常的字母

Apache Struts2 文件上传逻辑绕过(CVE-2024-53677)(S2-067)

很可惜,跑了很久的代码并没有发现存在这样的情况🤪那么

(Ps: 当然这其中不止失败了一次,期间也想过很多不同的思路,当然都是以失败告终🥱)

Struts2的参数绑定

在上文中提到了,新版的Struts2文件上传拦截器没有参数存储的过程,那么很容易联想到漏洞的利用还是与参数相关,Struts2中对于参数绑定通过Ognl表达式实现,具体实现在com.opensymphony.xwork2.interceptor.ParametersInterceptor拦截器中

Apache Struts2 文件上传逻辑绕过(CVE-2024-53677)(S2-067)

简单发一个上传的包Debug做验证

Apache Struts2 文件上传逻辑绕过(CVE-2024-53677)(S2-067)

com.opensymphony.xwork2.interceptor.ParametersInterceptor#setParameters中,有着对参数字符的限制函数,只有isAcceptableParameter条件为true才能做接下来的参数绑定

Apache Struts2 文件上传逻辑绕过(CVE-2024-53677)(S2-067)

这部分限制还是满死的,毕竟历史上Struts2被爆出无数RCE漏洞,其中修修补补无数(没学过的自己去补补课),因此想要绕过各种个样限制直接完成RCE是极为困难的。另外在这里,我也不会把所有的参数限制条件列举出来,哪里卡住绕哪里即可,这里就浪费时间讲解一些不重要的过程,当然有兴趣也可以具体看看各个限制条件。

12345678910111213141516
// com.opensymphony.xwork2.interceptor.ParametersInterceptor#isAcceptableParameterprotectedbooleanisAcceptableParameter(String name, Object action){ParameterNameAware parameterNameAware = actioninstanceof ParameterNameAware ? (ParameterNameAware)action :null;returnthis.acceptableName(name) && (parameterNameAware ==null || parameterNameAware.acceptableParameterName(name));}// com.opensymphony.xwork2.interceptor.ParametersInterceptor#isAcceptableParameterValueprotectedbooleanisAcceptableParameterValue(Parameter param, Object action){ParameterValueAware parameterValueAware = actioninstanceof ParameterValueAware ? (ParameterValueAware)action :null;boolean acceptableParamValue = parameterValueAware ==null || parameterValueAware.acceptableParameterValue(param.getValue());if (this.hasParamValuesToExclude() ||this.hasParamValuesToAccept()) {acceptableParamValue &=this.acceptableValue(param.getName(), param.getValue());}return acceptableParamValue;}

在这里,RCE的宏伟目标就暂不考虑了,我们只需要知道既然Struts2使用了Ognl做参数绑定的实现,那么便可以尝试通过参数绑定的过程去实现对上传文件名的修改,从而绕过系统对于目录穿越的限制

S2-067之多文件上传场景绕过

回到本身,简单整理下漏洞绕过的思路,用一句话来概括就是:

在参数名与文件上传参数不一致的前提下,能通过Ognl参数绑定过程对文件名做修改

在多文件上传情景下,为方便调试,首先简单构造一个上传多文件的数据包

12345678910111213141516171819
POST/uploads.actionHTTP/1.1Host:127.0.0.1:8080Connection:keep-aliveContent-Type:multipart/form-data; boundary=----WebKitFormBoundaryq0PW93h6lyBzjZNZUser-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36Content-Length:138------WebKitFormBoundaryq0PW93h6lyBzjZNZContent-Disposition: form-data;name="Upload";filename="1.txt"Content-Type:text/plainy4tacker------WebKitFormBoundaryq0PW93h6lyBzjZNZContent-Disposition: form-data;name="Upload";filename="2.txt"Content-Type:text/plain1------WebKitFormBoundaryq0PW93h6lyBzjZNZ--

在这个场景下如何使用简单的Ognl表达式对文件名做赋值呢?

由于在这里我们的uploadFileName是列表的格式

Apache Struts2 文件上传逻辑绕过(CVE-2024-53677)(S2-067)

我们很容易想到使用中括号写法uploadFileName[0]的形式对其中的文件名做修改,简单在控制台尝试,在这里成功对我们的文件名做了修改

Apache Struts2 文件上传逻辑绕过(CVE-2024-53677)(S2-067)

在这个场景下,很容易验证得到绕过的Poc,在自己尝试时同样别忘了参数保存是在TreeMap中,这是个有序列表,简单解释下尽管在FileUploadInterceptor中参数保存在无序的HashMap中了,但是在com.opensymphony.xwork2.interceptor.ParametersInterceptor#setParameters完成参数绑定的过程中重新用了有序的Treemap做包装

Apache Struts2 文件上传逻辑绕过(CVE-2024-53677)(S2-067)

因此错误的大小写以及参数名会影响其排列顺序,导致文件名无法覆盖(S2-066的时候也讲过)

Apache Struts2 文件上传逻辑绕过(CVE-2024-53677)(S2-067)

S2-067之单文件上传场景绕过

同样的Payload放在单文件上传的场景自然而然就失效了,毕竟我们的uploadFileName在这里只是一个String类型的变量

Apache Struts2 文件上传逻辑绕过(CVE-2024-53677)(S2-067)

同样的为了完成文件名的修改,我们依旧需要在参数名与文件上传参数不一致的前提下,通过Ognl参数绑定过程对文件名做修改

在讲解之前我们需要知道一个概念,在Ognl中有个重要的概念叫做值栈,值栈主要目的是为了让能方便的访问Action的属性

在Struts2中默认的实现为OgnlValueStack,Struts2在执行一次请求的过程中会把当前的Action对象自动存入值栈中,

因此我们只要能获取到这个对象就能完成对文件名的修改

为了方便调试Ognl语句,我们首先构造一个正常的Http流量包

1234567891011121314
POST/upload.actionHTTP/1.1Host:127.0.0.1:8080Connection:keep-aliveContent-Type:multipart/form-data; boundary=----WebKitFormBoundaryq0PW93h6lyBzjZNZUser-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36Content-Length:138------WebKitFormBoundaryq0PW93h6lyBzjZNZContent-Disposition: form-data;name="Upload";filename="1.txt"Content-Type:text/plainy4tacker------WebKitFormBoundaryq0PW93h6lyBzjZNZ--

在Struts2中我们可以使用[0]获取整个栈对象,为方便显示转换为String对象,调用其 toString()方法输出对象信息,可以看到栈顶元素即为我们的Action对象

Apache Struts2 文件上传逻辑绕过(CVE-2024-53677)(S2-067)

因此我们可以使用top关键词直接获取到栈顶的Action对象,从而获取到FileName参数

Apache Struts2 文件上传逻辑绕过(CVE-2024-53677)(S2-067)

因此我们可以尝试使用[0].top.UploadFilename来对文件名做修改,但显然从返回结果来看并没有成功

Apache Struts2 文件上传逻辑绕过(CVE-2024-53677)(S2-067)

经过调试发现,这里的isAcceptableParameter返回了false

Apache Struts2 文件上传逻辑绕过(CVE-2024-53677)(S2-067)

没通过的条件是com.opensymphony.xwork2.interceptor.ParametersInterceptor#isAccepted

对应的表达式为w+((.w+)|([d+])|((d+))|(['(w-?|[u4e00-u9fa5]-?)+'])|(('(w-?|[u4e00-u9fa5]-?)+')))*

Apache Struts2 文件上传逻辑绕过(CVE-2024-53677)(S2-067)

没通过的原因很简单[0]前面不能为空

Apache Struts2 文件上传逻辑绕过(CVE-2024-53677)(S2-067)

这个条件Bypass也很简单,在表达式中[0].top等价于top

Apache Struts2 文件上传逻辑绕过(CVE-2024-53677)(S2-067)

最终我们成功实现了在单文件上传场景下的绕过

Apache Struts2 文件上传逻辑绕过(CVE-2024-53677)(S2-067)

参考文章

https://developer.aliyun.com/article/330800

https://paper.seebug.org/794

https://cwiki.apache.org/confluence/display/WW/S2-067

https://y4tacker.github.io/2023/12/09/year/2023/12/Apache-Struts2-%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0%E5%88%86%E6%9E%90-S2-066/

关 注 有 礼

Apache Struts2 文件上传逻辑绕过(CVE-2024-53677)(S2-067)

本文内容来自网络,如有侵权请联系删除

原文始发于微信公众号(网络安全者):Apache Struts2 文件上传逻辑绕过(CVE-2024-53677)(S2-067)

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年12月20日09:51:43
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Apache Struts2 文件上传逻辑绕过(CVE-2024-53677)(S2-067)https://cn-sec.com/archives/3531148.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息