网传nacos RCE 0day分析

admin 2024年7月16日13:17:15评论117 views字数 5988阅读19分57秒阅读模式

萌新第一次分析Java漏洞,大佬勿喷哦。
时间仓促(我要蹭热度+今天公司活多),就不写得很详细了,大概知道这个漏洞是怎么回事危害到底大不大(请跳到第三节)就行。

1 分析一下EXP

先看了下EXP:

网传nacos RCE 0day分析

config.py,确实是纯配置文件,没啥好说的。
service.py,建了一个flask,实际上是host了一个文件,也没什么问题:

网传nacos RCE 0day分析

jar包内容是啥我不关心,反正我没运行。
exploit.py,这里是重头戏:

网传nacos RCE 0day分析

可以看到是拼接了一个sql语句,然后构造一个随机字符串,反复循环访问removal_url,如果返回值中有message和data就执行derby_url传递sql语句(说明此POC需要爆破一个8位字符串),使用语句下载安装jar包再移除。命令执行毫无疑问是发生在安装jar包的过程中。
这里还暴露了一点,那就是RCE的条件与数据库类型有关,因为install_jar这个函数只在少数数据库如apache derby中有效。

2 简单瞅瞅源码

心里有数之后,转回去看对应版本的nacos源码(我真心不想搭环境)。
参照POC作者的描述选择2.3.2版本,找到derby语句的controller:
https://github.com/alibaba/nacos/blob/2.3.2/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigOpsController.java

网传nacos RCE 0day分析

对这个函数简单写了下注释:

@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 { #如果使用的数据源不是derby就返回
        if (!DatasourceConfiguration.isEmbeddedStorage()) {
            return RestResultUtils.failed("The current storage mode is not Derby");
        }
        LocalDataSourceServiceImpl dataSourceService = (LocalDataSourceServiceImpl) DynamicDataSource
                .getInstance().getDataSource();
        if (StringUtils.startsWithIgnoreCase(sql, selectSign)) { #如果以select开头
            if (!StringUtils.containsIgnoreCase(sql, limitSign)) { #如果不包含ROWS FETCH NEXT,就添加
                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"); #如果不以select开头就返回
    } catch (Exception e) {
        return RestResultUtils.failed(e.getMessage());
    }
}

回过来看POC,derby传入的语句倒确实符合这个规则(ROWS FETCH NEXT有点好笑不知道为啥):

select * from (select count(*) as b, S_EXAMPLE_{id}('{cmd}') as a from config_info) tmp /*ROWS FETCH NEXT*/

再看removal,同一个文件:

网传nacos RCE 0day分析
@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) -> { #使用body执行dataImport,需要跟入
            NotifyCenter.publishEvent(new DerbyImportEvent(true));
            if (Objects.nonNull(ex)) { #如果ex(看来是exception,能不能写全)不为空,返回失败提醒
                response.setResult(RestResultUtils.failed(ex.getMessage()));
                return;
            }
            response.setResult(result);
        });
    }, response);
    return response;
}

跟进DatabaseOperate,目前来看符合条件(这里用了抽象类)的类只有两个,由于没有详细看代码,这里又用了抽象类,我理解为两个类都是可能代入的(也许derby数据库只会用到其中一个类):

网传nacos RCE 0day分析

随便抽了一个类的dataImport看一下:

@Override #覆写自interface DatabaseOperate 
public CompletableFuture<RestResult<String>> dataImport(File file) {
    return CompletableFuture.supplyAsync(() -> {
        try (DiskUtils.LineIterator iterator = DiskUtils.lineIterator(file)) { #读取每一行
            int batchSize = 1000;
            List<String> batchUpdate = new ArrayList<>(batchSize);
            List<CompletableFuture<Void>> futures = new ArrayList<>();
            List<Boolean> results = new CopyOnWriteArrayList<>();
            while (iterator.hasNext()) {
                String sql = iterator.next(); #取出每一行到sql
                if (StringUtils.isNotBlank(sql)) {
                    batchUpdate.add(sql); #如果非空,加入batchUpdate
                }
                if (batchUpdate.size() == batchSize || !iterator.hasNext()) { #如果读到最大值 或者读完了
                    List<ModifyRequest> sqls = batchUpdate.stream().map(s -> { #应该是一种lambda函数?
                        ModifyRequest request = new ModifyRequest(); #处理每一个成员,setSQL
                        request.setSql(s); 
                        return request;
                    }).collect(Collectors.toList());
                    futures.add(CompletableFuture.runAsync(() -> results.add(doDataImport(jdbcTemplate, sqls)))); #这里单看函数名是执行语句去了
                    batchUpdate.clear();
                }
            }
            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); #下面懒得看,
            int code = 500;
            if (!CollectionUtils.isEmpty(results)) {
                code = (!results.stream().anyMatch(Boolean.FALSE::equals)) ? 200 : 500;
            }
            return RestResult.<String>builder().withCode(code).withData("").build();
        } catch (Throwable ex) {
            LOGGER.error("An exception occurred when external data was imported into Derby : ", ex);
            return RestResultUtils.failed(ex.getMessage());
        }
    });
}

然后看一下doDataImport:

default Boolean doDataImport(JdbcTemplate template, List<ModifyRequest> requests) { #两个类都继承自BaseDatabaseOperate
    final String[] sql = requests.stream().map(ModifyRequest::getSql).map(DerbyUtils::insertStatementCorrection) #有点像linq 我天哪太优雅辣^ ^
            .toArray(String[]::new);  #对MAP中的成员依次执行ModifyRequest::getSql DerbyUtils::insertStatementCorrection 然后转回成字符串
    int[] affect = template.batchUpdate(sql); #这里最后会执行语句
    return IntStream.of(affect).count() == requests.size();
}

跟到batchUpdate,这里终于执行了:

网传nacos RCE 0day分析

3 危害大不大

得从漏洞是否未授权和漏洞产生条件两个方面看。
首先derby数据库是nacos默认数据库,理论上看有漏洞的数据库应该比较多。
从授权条件来看这里是需要鉴权的(但是默认某些配置会让他未授权访问):

网传nacos RCE 0day分析

笔者个人认为这个漏洞最有可能用于提权,加上官方的态度(意思是不修)应该会是一个比较常驻的提权手段。

彩蛋

这个洞也是早有渊源。
https://github.com/alibaba/nacos/issues/10613

网传nacos RCE 0day分析

虽然他说的挺有道理的,但熟悉我的读者已经知道我要说什么了,feature!

网传nacos RCE 0day分析

原文始发于微信公众号(重生之成为赛博女保安):原来是老生常谈?网传nacos RCE 0day分析

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

发表评论

匿名网友 填写信息