0x01 前言

在学习反制ysoserial的过程途中,jar包的报错让我误以为这个思路不太行。后来发现了问题的所在,并扩展一下自己的思路。

0x02 正片

一、 ysoserial反制

registry.list和registry.bind,这两处就是调用的原生的RegistryImpl_Stub,会触发UnicastRef#invoke->StreamRemoteCall#executeCall导致反序列化。一路跟下来,就可以看到这个方法里面有反序列化点了。因为都是原生的方法,也没存在任何的过滤。

一开始我对反制存疑,可以看到这里存在了java沙箱的机值,会限制命令的执行。:

后来验证了一下,还真得到了这样的结果:

可以看到确实在右边的最底下可以看到存在自定义的SercurityManager去限制了命令的执行,可以看到箭头上面所指的地方,这里是在exploit这个方法中执行并捕捉到了命令执行的问题,我们去看源代码:

可以看到第二个箭头所指向的地方,在exploit执行的时候,确实是将整段代码放在沙箱内去执行的。但是我们可以看到箭头1所指向的位置,理应在这里就会执行反序列化而导致命令的执行。

二、 调试

因为报错信息里面没有关于list方法的报错,先推断一下,我们再去跟着源代码调试一下。

既然list方法并没有执行成功,那应该在这个方法内部会抛出异常。

可以看到list函数抛出了两个异常,分别是AccessException和RemoteException,后面throw的异常都是这两个大类的子类,应该是会捕获的。上图的try,catch块是捕获ConnectIOException,是RemoteException的子类,应该捕获不了,希望知道的师傅可以解答一下。

后来当我把jar包拖入idea的时候我傻眼了。我以为用的是最新版本,结果用的是历史版本。

可以看到2018.4.23次的提交支持了sslrmi,在之前用list方法测试了一下,导致了反制的问题。也就是在这个版本之前的是不会在这个点被反制利用的。

三、 ExecCheckingSecurityManager

那么我们正好来看一下,能否反反制。可以看到ysoserial内置了两个自定义的SecurityManager。而exploit方法内使用的是第二个sm。让我们来看看这个sm。

类成员和方法在左边,挑一些说。可以看到checkExec继承父类的方法,这里的cmd参数表示系统特定的命令,在本次调试中就是cmd.exe。后面可以看到抛出了异常[在ExexCheckingSecurityManager对象调用构造方法的时候,将throwException参数设成了true]。这个ExecException类的getMessage方法可以在上下文中看到。

接下来看第二个callWrapped方法。

可以看到,首先将系统的sm通过局部变量先保存起来,最后会在finally中的语句返回。再将系统的sm设置成当前的ExecCheckingSecurityManager对象。try块里面调用了callable(可以当作是Runnable接口的增强版)的call方法启动了新的线程,并将结果返回。因为我们前面提到过,在ysoserial.secmgr.ExecCheckingSecurityManager对象生成的时候将throwException参数设成了true,所以只要进行了命令执行的方法,sm一定会进入checkExec方法,那么一定会直接抛出错误。所以在这里将命令执行终止。可以看到ysoserial在处理命令执行这块是非常的暴力。

后续又看了一下绕过沙盒的办法,感觉不适用于此处在进入checkExec方法后直接抛出异常的问题。

四 、 反反制

我们来研究一下这样能否做到反反制,SecurityManager调用链如下图所示:

SecurityManager.checkExec()
    |
    V
·······
    |
    V
SecurityManager.checkPermission()
    | 在默认的Security Manager实现中
    V
AccessController.checkPermission()

我们知道,AccessController还提供了doPrivilege方法,当这个方法被调用时,AccessController亦会自顶向下遍历当前栈,不过只会遍历到调用doPrivileged方法的栈帧就会停止。但是整个的调用栈仍然存在于ExecCheckingSecurityManager.checkExec方法中,当这个方法返回之前,仍然会抛出异常。

ExecCheckingSecurityManager.checkExec() 
    |
    V
SecurityManager.checkExec()

上面exploit方法没有执行成功的原因就是把代码放在了沙箱内去执行,所以反反制的方法也非常简单,我们可以学习ysoserial的方法,在try块的list方法外裹上ysoserial自定义的沙盒。

idea报错了,这里是因为内部类中使用但未声明的任何局部变量必须在内部类的正文之前明确分配。而且Java匿名内部类的方法中用到的局部变量都必须定义为final。

所以我声明了一下,并且在内部方法内就要捕获这个异常。但是为了照顾到下面的exploit方法,我们必须要将call方法的参数值进行一个返回,并用一个新的变量接收沙箱内执行的结果。

final Registry sandboxRegistry = registry; //将上面getRegistry获取到的registry放到final变量,方便沙盒内的匿名方法使用
        Registry receivedRegistry = new ExecCheckingSecurityManager().callWrapped(new Callable<Registry>(){public Registry call() throws RemoteException {
            try {
                sandboxRegistry.list();
            } catch (ConnectIOException ex) {
                ex.printStackTrace();
                return LocateRegistry.getRegistry(host, port, new RMISSLClientSocketFactory());//处理证书的问题
            }
            return null;
        }});
        if (registry3 != null){ //对返回的参数进行判断
            exploit(receivedRegistry, payloadClass, command);
        }else {
            exploit(registry, payloadClass, command);
        }

本来的情况:

修改之后的情况:

那么其他工具类似ysomap也可以类推,在这些危险方法的外部加上沙盒。

如果有什么错误欢迎各位师傅指正。

0x03 参考:

白白白师傅的文章。

https://www.anquanke.com/post/id/151398 关于沙盒绕过。

点击收藏 | 1 关注 | 2
登录 后跟帖