前言:
终于来到最后一篇了,这个RMI确实拖了我很长时间,一直没时间写,还发现有很多新的东西需要写,但是自己又没有时间,,,,后面还有类加载、Fastjson、CC、CB,,,,,,,感觉自己还有好多东西要学,大脑有点不够用了(猪脑过载),还是慢慢积累,慢慢成长吧。噢,对了上次忘了每篇鸡汤,这次一并补上:
1 2 3 4
| 路漫漫其修远兮,吾将上下而求索 ——屈原 千磨万击还坚韧,任尔东南西北风 ——郑板桥
|
攻击客户端:
注册中心攻击客户端
通过图片我们可以看到注册中心与客户端之间通信是为了获取stub。
那么我们下断点跟一下看看里面究竟藏着什么猫腻。
直接来到RegistryImpl_Stub.lookup
:
进来之后一眼看到三部分1,3部分很简单就是先将传入的字符序列化传给注册中心,最后再把注册中心返回的数据进行反序列化。
这个反序列化的点就是我们可以利用的点,假如注册中心返回一个恶意类,客户端反序列化之后会直接执行。所以这就是我们攻击客户端的第一种方法。
还有比较有意思的第2部分:
可以看到它是UnicastRef.invoke(var2)具体的值也可以看到,我们再次进行跟进:
再次跟进:
可以看到这里捕获2异常,如果异常为2,则会对流进行反序列化,这里的本意可能是想通过反序列化来获取更详细的错误信息,但是这里实际上也是利用点,且这个利用点更加隐蔽、且范围更广。为什么这么说?
因为只要是调用的这个invoke的函数都会触发反序列化,但是在处理网络请求的时候,会大量的调用这个invoke,所以这里也更危险。
服务端攻击客户端
跟进客户端调取服务端远程对象:
直接来到了invoke
方法并调用invokeRemoteMethod(proxy, method, args);
可以很清楚的看到堆栈信息和具体传入的参数。我们再次跟进:
可以看到这里又调用了UnicastRef.invoke()
但是这里的invoke
是重载过的,和我们上面的UnicastRef.invoke()
有点不太一样,我们再次跟进:
可以看到这里的marshalValue()
其实是将我们输入进行序列化,通过参数信息也可以看到,这里将我们调用的远程方法hello
也传入进去。
完了之后又调用了executeCall()
也就是说所有客户端的请求都会调用这个executeCall()
方法。我们再往后看:
这里会获取到服务端的返回值传入unmarshalValue()
而在这个unmarshalValue()
中其实是将返回值进行反序列化:
所以看到这我们也明白了,客户端与服务端之间,客户端也是有两个反序列化的点。
首先第一个,细心的同学可以看到上图我在executeCall()
打了断点但是没跟进去,原因是因为他和之前(客户端与注册中心)的executeCall()
是一样的。他之中也有反序列化的点:
第二个反序列化的点就是我们分析的unmarshalValue()
中将服务端返回值反序列化。
攻击注册中心
客户端攻击注册中心
当客户端在请求注册中心时会来到Transport.serviceCall()
:
可以看到这里先是获取Target,而这个Target也就是RegistryImpl_Stub,后面会获取分发器,而里面的内容就是skel。后面会调用dispatch()
:
这个dispatch也就是UnicastServerRef
我们再次进行跟进:
这里首先是获取输入,在检查skel字段是否为空,不为空则调用oldDispatch()
,那我们再跟进:
这里也就终于走到了skel.dispatch()
,这个dispatch()
也就是我们上一篇讲到的那几种方法对应的case,他会对不同的方法执行不同的readObject。(因为,截不下。我贴一下,这样大家就不用回去看了)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
| public void dispatch(Remote var1, RemoteCall var2, int var3, long var4) throws Exception { if (var4 != 4905912898345647071L) { throw new SkeletonMismatchException("interface hash mismatch"); } else { RegistryImpl var6 = (RegistryImpl)var1; String var7; ObjectInput var8; ObjectInput var9; Remote var80; switch (var3) { case 0: RegistryImpl.checkAccess("Registry.bind");
try { var9 = var2.getInputStream(); var7 = (String)var9.readObject(); var80 = (Remote)var9.readObject(); } catch (ClassNotFoundException | IOException var77) { throw new UnmarshalException("error unmarshalling arguments", var77); } finally { var2.releaseInputStream(); }
var6.bind(var7, var80);
try { var2.getResultStream(true); break; } catch (IOException var76) { throw new MarshalException("error marshalling return", var76); } case 1: var2.releaseInputStream(); String[] var79 = var6.list();
try { ObjectOutput var81 = var2.getResultStream(true); var81.writeObject(var79); break; } catch (IOException var75) { throw new MarshalException("error marshalling return", var75); } case 2: try { var8 = var2.getInputStream(); var7 = (String)var8.readObject(); } catch (ClassNotFoundException | IOException var73) { throw new UnmarshalException("error unmarshalling arguments", var73); } finally { var2.releaseInputStream(); }
var80 = var6.lookup(var7);
try { ObjectOutput var82 = var2.getResultStream(true); var82.writeObject(var80); break; } catch (IOException var72) { throw new MarshalException("error marshalling return", var72); } case 3: RegistryImpl.checkAccess("Registry.rebind");
try { var9 = var2.getInputStream(); var7 = (String)var9.readObject(); var80 = (Remote)var9.readObject(); } catch (ClassNotFoundException | IOException var70) { throw new UnmarshalException("error unmarshalling arguments", var70); } finally { var2.releaseInputStream(); }
var6.rebind(var7, var80);
try { var2.getResultStream(true); break; } catch (IOException var69) { throw new MarshalException("error marshalling return", var69); } case 4: RegistryImpl.checkAccess("Registry.unbind");
try { var8 = var2.getInputStream(); var7 = (String)var8.readObject(); } catch (ClassNotFoundException | IOException var67) { throw new UnmarshalException("error unmarshalling arguments", var67); } finally { var2.releaseInputStream(); }
var6.unbind(var7);
try { var2.getResultStream(true); break; } catch (IOException var66) { throw new MarshalException("error marshalling return", var66); } default: throw new UnmarshalException("invalid method number"); }
} }
|
服务端攻击注册中心
这里就不跟了,流程基本上大差不差。只不过服务端会调用bind,case的点不一样。
攻击服务端
客户端攻击服务端
之后我们要确保获取到的Target是动态代理,因为这样才是服务端在处理请求。
也是会来到Transport.serviceCall()
,调用dispatch()
:
跟进:
我们发现之前注册中心在处理的时候,因为接收到的skel不为空所以走的是上面的if,但是服务端在处理的时候会直接走到下面。
这里会调用这个unmarshalValue()
方法,其实这个方法就是将客户端的数据进行反序列化:
最后在进行真正的调用:
看到这利用点也就出来了,就是我们上面的readObject了。
注册中心攻击服务端
同样的套路,我们直接略过。。。。。
至此,我们的RMI也就草草结束了,,其实我只是很浅显的写了一下,包括他还有通过JRMP去攻击客户端、回收机制、高版本绕过、等等。我觉得先浅浅的了解一点,等后面用得着再深入的学这样是好的。然后后面的主线就是去分析一些类似于RMI的这种在java安全中有着一席之地的东西,支线的话偶尔会穿插一些漏洞分析文章。我尽量自律,保持稳定的产出速度(如果没有什么意外发生)。