CVE-2021-2394 Gadgets 分析
1 2 3 4 5 6 7 8 9 10 11 12 13
| com.tangosol.coherence.servlet.AttributeHolder.readExternal com.tangosol.util.aggregator.TopNAggregator$PartialResult.readExternal com.tangosol.util.aggregator.TopNAggregator$PartialResult.add com.tangosol.util.SortedBag.add java.util.TreeMap.put java.util.TreeMap.compare com.tangosol.util.SortedBag$WrapperComparator.compare com.tangosol.util.extractor.AbstractExtractor.compare oracle.eclipselink.coherence.integrated.internal.querying.FilterExtractor.extract org.eclipse.persistence.internal.descriptors.MethodAttributeAccessor.getAttributeValueFromObject java.lang.reflect.Method.invoke com.sun.rowset.JdbcRowSetImpl.connect javax.naming.InitialContext.lookup
|
从入口到FilterExtractor.extract
调用不再说了,跟之前CVE-2021-2135类似,通过InitialContext.lookup
到ldap执行命令过程可以看下之前的分析。这里主要分析下FilterExtractor.extract
到Method.invoke
及POC构造问题。
FilterExtractor.extract
中调用了attributeAccessor属性的initializeAttributes和getAttributeValueFromObject方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public Object extract(Object obj) { if (obj instanceof Wrapper) { obj = ((Wrapper)obj).unwrap(); }
if (!this.attributeAccessor.isInitialized()) { this.attributeAccessor.initializeAttributes(obj.getClass()); }
try { return this.attributeAccessor.getAttributeValueFromObject(obj); } catch (Exception var3) { return new FilterExtractor.InvalidObject(); } }
|
如果让attributeAccessor属性是MethodAttributeAccessor实例,那么就可以调用MethodAttributeAccessor.getAttributeValueFromObject
,当不满足PrivilegedAccessHelper.shouldUsePrivilegedAccess()
能够进一步调用invoke方法,这里注意getAttributeValueFromObject(anObject, (Object[])null)
传入的参数是null,那么invoke调用的就是无参方法。当传入的anObject是JdbcRowSetImpl实例,this.getMethod是connect方法,则可以调用JdbcRowSetImpl.connect
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public Object getAttributeValueFromObject(Object anObject) throws DescriptorException { return this.getAttributeValueFromObject(anObject, (Object[])null); }
protected Object getAttributeValueFromObject(Object anObject, Object[] parameters) throws DescriptorException { try { if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) { ...... } else { return this.getMethod.invoke(anObject, parameters); } } catch (IllegalArgumentException var6) { ...... } }
|
getMethod属性本身是不可以序列化的,但是在反序列化时可以调用到MethodAttributeAccessor.initializeAttributes
来完成赋值工作。initializeAttributes中使用Helper.getDeclaredMethod和getGetMethodName属性获取到Method对象并调用setGetMethod赋值给getMethod属性,而getMethodName属性是可以序列化的,在构造POC时可以设置为connect。
另外有一点值得注意:this.isWriteOnly()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public void initializeAttributes(Class theJavaClass) throws DescriptorException { this.initializeAttributes(theJavaClass, (Class[])null); }
protected void initializeAttributes(Class theJavaClass, Class[] getParameterTypes) throws DescriptorException { if (this.getAttributeName() == null) { throw DescriptorException.attributeNameNotSpecified(); } else { DescriptorException descriptorException; try { this.setGetMethod(Helper.getDeclaredMethod(theJavaClass, this.getGetMethodName(), getParameterTypes)); if (!this.isWriteOnly()) { this.setSetMethod(Helper.getDeclaredMethod(theJavaClass, this.getSetMethodName(), this.getSetMethodParameterTypes())); }
} catch (NoSuchMethodException var5) { ...... } } }
|
这里需要注意,当isWriteOnly属性是false时会调用setSetMethod。在CVE-2020-14841中思路是将该属性直接设置为false,从而避免调用setSetMethod:
1 2 3 4
| MethodAttributeAccessor methodAttributeAccessor = new MethodAttributeAccessor(); methodAttributeAccessor.setGetMethodName("getDatabaseMetaData"); methodAttributeAccessor.setIsWriteOnly(true); methodAttributeAccessor.setAttributeName("xxx");
|
但是CVE-2021-2934不能直接这样,在360cert中说明了这一点:因为MethodAttributeAccessor被加入了黑名单(笔者未验证),所以思路是可以调用setSetMethod方法,我们在构造POC时需要设置setMethodName。
如何设置setMethodName?在initializeAttributes方法中,将this.getSetMethodParameterTypes()
返回值作为方法的参数,最终会获取到this.getMethod
代表的方法的返回值参数列表,因此我们需要关注下JdbcRowSetImpl.connect
的返回值,connect会返回一个Connection对象,setConnection刚好以connection为参数,所以我们就可以设置setMethodName为setConnection。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| protected Class[] getSetMethodParameterTypes() { return new Class[]{this.getGetMethodReturnType()}; }
public Class getGetMethodReturnType() throws DescriptorException { if (this.getGetMethod() == null && this.getGetMethodName() != null && this.getGetMethodName().indexOf("_persistence_") > -1) { AbstractSessionLog.getLog().log(1, "no_weaved_vh_method_found_verify_weaving_and_module_order", this.getGetMethodName(), (Object)null, this); throw DescriptorException.nullPointerWhileGettingValueThruMethodAccessorCausedByWeavingNotOccurringBecauseOfModuleOrder(this.getGetMethodName(), "", (Throwable)null); } else if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) { try { return (Class)AccessController.doPrivileged(new PrivilegedGetMethodReturnType(this.getGetMethod())); } catch (PrivilegedActionException var1) { return null; } } else { return PrivilegedAccessHelper.getMethodReturnType(this.getGetMethod()); } }
|
当然,除了connect和setConnection还可以有prepare和setPreparedStatement:
1 2
| methodAttributeAccessor.setGetMethodName("prepare"); methodAttributeAccessor.setSetMethodName("setPreparedStatement");
|
既然上面提到了CVE-2020-14841,也顺便分析下。
CVE-2020-14841
CVE-2020-14841的Gadget:
1 2 3 4 5 6 7 8 9 10
| java.util.PriorityQueue.readObject java.util.PriorityQueue.heapify java.util.PriorityQueue.siftDown java.util.PriorityQueue.siftDownUsingComparator com.tangosol.util.comparator.ExtractorComparator.compare oracle.eclipselink.coherence.integrated.internal.cache.LockVersionExtractor.extract org.eclipse.persistence.internal.descriptors.MethodAttributeAccessor.getAttributeValueFromObject java.lang.reflect.Method.invoke com.sun.rowset.JdbcRowSetImpl.connect javax.naming.InitialContext.lookup
|
MethodAttributeAccessor.getAttributeValueFromObject
到javax.naming.InitialContext.lookup
上面已经分析过了,不再赘述,主要分析下PriorityQueue.readObject
到``LockVersionExtractor.extract`。
PriorityQueue是一个优先队列,本质是数组,在其内部定义了readObject,可自己处理序列化和反序列化。PriorityQueue反序列化时特点是先创建一个数组queue,然后再调用PriorityQueue.heapify进行排序。当内部属性Comparator有被赋值,处理排序时会通过PriorityQueue.heapify调用PriorityQueue.siftDownUsingComparator,节点之间用比较器Comparator的compare方法进行比较。所以当Comparator被赋值为ExtractorComparator,反序列化时会调用ExtractorComparator.compare。
ExtractorComparator.compare如下,内部调用了extract方法,当参数不是Entry实例,执行this.m_extractor.extract,构造ExtractorComparator的m_extractor属性为LockVersionExtractor实例,就能调用LockVersionExtractor.extract。LockVersionExtractor实现了ValueExtractor和ExternalizableLite,刚好满足序列化要求。
1 2 3 4 5 6 7 8 9 10
| public int compare(T o1, T o2) { Comparable a1 = o1 instanceof Entry ? (Comparable)((Entry)o1).extract(this.m_extractor) : (Comparable)this.m_extractor.extract(o1); Comparable a2 = o2 instanceof Entry ? (Comparable)((Entry)o2).extract(this.m_extractor) : (Comparable)this.m_extractor.extract(o2); if (a1 == null) { return a2 == null ? 0 : -1; } else { return a2 == null ? 1 : a1.compareTo(a2); } }
|
LockVersionExtractor.extract方法如下,和FilterExtractor.extract一样,当arg0参数是JdbcRowSetImpl实例,最终可以调用JdbcRowSetImpl.connect。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public Object extract(Object arg0) { if (arg0 == null) { return null; } else { if (arg0 instanceof Wrapper) { arg0 = ((Wrapper)arg0).unwrap(); }
if (!this.accessor.isInitialized()) { this.accessor.initializeAttributes(arg0.getClass()); }
return this.accessor.getAttributeValueFromObject(arg0); } }
|
看了下CVE-2020-14654,其实和CVE-2020-14841一样的,只不过LockVersionExtractor变成了UniversalExtractor,UniversalExtractor.extract中满足条件可调用invoke,类似的还有CVE-2020-2555。
参考链接:
https://cert.360.cn/report/detail?id=2d3b4207433c18ddf6d9607a57f92960
https://mp.weixin.qq.com/s/wFHhWvnCLm1xcWZIbv6O3A
https://blog.csdn.net/kobejayandy/article/details/46832797