前言

JNDI注入在高版本下显得比较鸡肋,不过基于本地Factory的利用方式还是值得学习的。


Tomcat-BeanFactory

比较出名的可利用Factory,而且Tomcat使用范围比较广。

该类中的getObjectInstance函数可以调用一个对象的函数,要求满足三个条件。

  • 要有public的无参构造函数:
Object bean = beanClass.getConstructor().newInstance();
  • 要调用的函数为public:
Method method = forced.get(propName);
...
method.invoke(bean, valueArray);
  • 要调用的函数有且只有一个String类型的参数
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = String.class;
...
forced.put(param, beanClass.getMethod(setterName, paramTypes));
  • 其实还有一些隐含的条件,比如它的bean在通过无参构造函数实例化后,到调用该函数之间没有其他操作,也就是说可控输入就只剩下参数了(或许可以通过多次调用提前控制一些成员)。又比如它无法进行链式调用,所以反射链难以实现,要找到利用方式基本只有那种输入命令->执行,或者ClassLoader的奇妙玩法了。

存在漏洞或危险操作的依赖

通过ELProcessor、GroovyShell、Yaml、MVEL、Xstream、bsh等存在漏洞的工具的玩法。

Mlet

MLet是一个ClassLoader,继承自URLClassLoader,其public函数addURL可以通过一个字符串URL定义远程资源:

public void addURL(String url) throws ServiceNotFoundException {
    try {
        URL ur = new URL(url);
        if (!Arrays.asList(getURLs()).contains(ur))
            super.addURL(ur);
    } catch (MalformedURLException e) {
       ...
    }
}

远程类需要加载并初始化才能执行其中的静态代码块,问题在于我们通过BeanFactory实例化并调用的Mlet是一个与环境隔离的ClassLoader,我们无法通过这里之外的加载/实例化点完成利用,因为环境中使用的ClassLoader不是这个Mlet。

而在Mlet类中,可以调用的loadClass函数来自父类的父类ClassLoader,而loadClass在加载失败时会调用findClass从资源中定义类:

public Class<?> loadClass(String name) throws ClassNotFoundException {
    return loadClass(name, false);
}


protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } 
            ...

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    ...
                }
        }
        ...
    }
}

// Mlet
protected Class<?> findClass(String name) throws ClassNotFoundException {
    return findClass(name, currentClr);
}

Class<?> findClass(String name, ClassLoaderRepository clr)
    throws ClassNotFoundException {
    Class<?> c = null;
    MLET_LOGGER.logp(Level.FINER, MLet.class.getName(), "findClass", name);
    // Try looking in the JAR:
    try {
        c = super.findClass(name);
        ...
    }
}

// URLClassLoader
protected Class<?> findClass(final String name)
    throws ClassNotFoundException
{
    final Class<?> result;
    try {
        result = AccessController.doPrivileged(
            new PrivilegedExceptionAction<Class<?>>() {
                public Class<?> run() throws ClassNotFoundException {
                    String path = name.replace('.', '/').concat(".class");
                    Resource res = ucp.getResource(path, false);
                    if (res != null) {
                        try {
                            return defineClass(name, res);
                        } 
                        ...
                    } else {
                        return null;
                    }
                }
            }, acc);
    } 
    ...
}

然而loadClass的第二个参数固定为false,也就是说仅仅是加载也无法执行静态代码块,必须要走到实例化的步骤才行。而ClassLoader中自然是不会有实例化类的操作的,所以这种方式也就走不通了。

NashornScriptEngineFactory

有无参构造函数:

public NashornScriptEngineFactory() {
}

有危险函数:

public ScriptEngine getScriptEngine(String... args) {
    return this.newEngine((String[])Objects.requireNonNull(args), getAppClassLoader(), (ClassFilter)null);
}

可以链式利用:

new NashornScriptEngineFactory().getScriptEngine("js").eval("java.lang.Runtime.getRuntime().exec('calc')");

然而BeanFactory似乎实现不了链式调用,寄。

Mvel

一种表达式,需要依赖:

<!-- https://mvnrepository.com/artifact/org.mvel/mvel2 -->
<dependency>
    <groupId>org.mvel</groupId>
    <artifactId>mvel2</artifactId>
    <version>2.4.14.Final</version>
</dependency>

MVEL类没有无参构造函数,但是有很多静态public函数,比如eval、evalToString和evalToBoolean,可以在mvel2包里面其他public类中找一找有没有调用这些静态函数的地方。

单纯用文本搜索做起来会比较麻烦,因为调用了eval的地方实在有点多,看起来很麻烦,如果用codeQL等工具污点分析就会方便很多,详见参考文章。

ShellSession类有一个无参构造函数:

public ShellSession() {
    ...
}

其exec函数只有一个String类型参数:

public void exec(String command) {
    for (String c : command.split("\n")) {
        inBuffer.append(c);
        _exec();
    }
}

其_exec函数会调用内部定义好的Command们的execute函数:

try {
    commands.get(inTokens[0]).execute(this, passParameters);
}

PushContext的execute函数中会调用MVEL类的eval函数:

public Object execute(ShellSession session, String[] args) {
    session.setCtxObject(MVEL.eval(args[0], session.getCtxObject(), session.getVariables()));
    return "Changed Context";
}

MemoryUserDatabaseFactory

继承了ObjectFactory接口,其getObjectInstance函数中会实例化一个MemoryUserDatabase对象,该对象可以设置几个属性:

MemoryUserDatabase database = new MemoryUserDatabase(name.toString());
RefAddr ra = null;

ra = ref.get("pathname");
if (ra != null) {
    database.setPathname(ra.getContent().toString());
}

ra = ref.get("readonly");
if (ra != null) {
    database.setReadonly(Boolean.parseBoolean(ra.getContent().toString()));
}

ra = ref.get("watchSource");
if (ra != null) {
    database.setWatchSource(Boolean.parseBoolean(ra.getContent().toString()));
}

然后调用其open和save函数,分别可以触发xxe和tomcat下的rce漏洞。

JDBC

详见参考文章。


参考

https://xz.aliyun.com/t/10829

https://tttang.com/archive/1405/


Web Java JNDI

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

论文研读(1)
Java反序列化中的RMI、JRMP、JNDI、LDAP(2)