ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

java-本地命令执行

2022-09-06 17:35:26  阅读:202  来源: 互联网

标签:lang java String exec 命令 本地 import Runtime


RunTime类进行exec的流程

源码:Runtime类

public class Runtime,它是一个公有类

构造函数:private Runtime() {},构造函数为私有,说明不能直接实例化该对象

private static Runtime currentRuntime = new Runtime();,这句话可以看出来currentRuntime这个属性本身是一个Runtime的类

继续看,那么应该就知道了,这个Runtime类是遵循单例模式的,每次运行的对象就只有一个,所以在java程序中不同线程通过调用Runtime.getRuntime()获得的是同一个对象实例,也就是说一个java进程中只有一个Runtime实例

Runtime有个exec的方法,并且对应的重载方法有多个,并且执行完之后返回的是Process类的实例,是一个进程类的实例对象

根据传入的参数大致可以分为两类,exec方法支持传入的命令可以是一个字符串类型,也可以是一个字符串数组类型

//字符串
public Process exec(String command);
public Process exec(String command, String[] envp);
public Process exec(String command, String[] envp, File dir);
//数组
public Process exec(String cmdarray[]); 
public Process exec(String[] cmdarray, String[] envp); 
public Process exec(String[] cmdarray, String[] envp, File dir); 

这里得细说exec这个方法,该exec返回的是一个Process类的实例,那么这里就可以知道了它实则是创建了一个进程,让进程来执行我们传入的command命令

这里得跟下exec方法的大概流程,这里执行命令执行测试类,如下:

public class command_cmd {
    public static void main(String[] args) throws IOException {
        String command = "whoami";
        Process p = Runtime.getRuntime().exec(command);
        InputStream in = p.getInputStream();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] b = new byte[1024];
        int a = -1;
        while ((a = in.read(b)) != -1) {
            baos.write(b, 0, a);
        }
        System.out.println(baos.toString());
    }
}

对命令执行语句进行debug操作

 Process p = Runtime.getRuntime().exec(command);

首先跟进exec的方法,发现还是一个exec方法

当传入的参数类型是 String ,会到 Process exec(String command) 这个构造方法进行处理,最后返回了 exec(command, null, null);

它继续跟进exec方法中,继续跟进另一个exec方法

java.lang.Runtime#exec(java.lang.String, java.lang.String[], java.io.File)

第三个exec方法中,如下图所示,可以看到又会继续生成一个ProcessBuilder的实例对象,并且执行start方法

java.lang.Runtime#exec(java.lang.String[], java.lang.String[], java.io.File)

经过处理之后,最后实例化了 ProcessBuilder 来处理传入的 cmdarray。可以证实 Runtime.getRuntime.exec() 的底层实际上也是 ProcessBuilder。

跟进 ProcessBuilder 中的 start 方法

java.lang.ProcessBuilder#start

注意:这里将cmdarray的第一个值赋值给prod参数,其实StringTokenizer对command进行分割后,第一个参数当成系统命令执行

String prog = cmdarray[0];

开始执行ProcessImpl的start方法如下:

ProcessBuilder.start 内部又调用了 ProcessImpl.start

这里标记的for循环,就是将cmdarry[1:])作为命令执行的参数转换成 byte 数组 argBlock。

在 ProcessImpl.start 中 将 cmdarry 第一个参数 (cmdarry[0]) 当作要执行的命令,把后面的部分 (cmdarry[1:]) 作为命令执行的参数转换成 byte 数组 argBlock。

最后将处理好的参数传给 UNIXProcess

java.lang.UNIXProcess#UNIXProcess

我们看到当前断点的 pid 是 89827 , 这里确实启动了一个 whoami 进程

RunTime类exec执行流程总结:

  1. Runtime.exec(xxx)
  2. java.lang.ProcessBuilder.start()
  3. new java.lang.UNIXProcess(xxx)
  4. UNIXProcess构造方法中调用了forkAndExec(xxx) native方法。
  5. forkAndExec调用操作系统级别fork->exec(*nix)/CreateProcess(Windows)执行命令并返回fork/CreateProcessPID

StringTokenizer 用法

StringTokenizer是一个用来分隔String的应用类,相当于String的split方法。

构造函数

public StringTokenizer(String str)
public StringTokenizer(String str, String delim)
public StringTokenizer(String str, String delim, boolean returnDelims)
  • 第一个参数就是要分隔的String
  • 第二个是分隔字符集合,如果不指定分隔字符,默认的是:”\t\n\r\f
  • 第三个参数表示分隔符号是否作为标记返回

StringTokenizer(String str):构造一个用来解析str的StringTokenizer对象。java默认的分隔符是“空格”、“制表符(‘\t’)”、“换行符(‘\n’)”、“回车符(‘\r’)”。

我们本地执行命令,会讲2条命令都显示出来

ifconfig en0;whoami

Java 代码审计当中,关于命令执行,我们主要关注的是函数 Runtime.getRuntime().exec(command) && new ProcessBuilder(command).start()

在参数 command 可控的情况下,一般就会存在命令执行的问题,但是也会存在这种问题,有时候明明参数可控,但是无法成功执行命令,以及复杂的shell 命令,例如带有 |<>$ 等符号的命令没办法正常执行。

粗略一看,存在命令执行函数,command 获取从外部传入的 cmd ,应该是存在命令注入漏洞的。但是并没有执行成功,并不存在命令执行漏洞。

我们跟进 Runtime.getRuntime().exec() 发现会依据传入的参数类型,而选用不同的函数。

通过StringTokenizer 对command处理,会根据 \t\n\r\f 把传入的 command 分割

空格前面的参数:

cmdarray[0]=ifocnfig

cmdarray[1]=en0;whoami

在 ProcessImpl.start 中 将 cmdarry 第一个参数 (cmdarry[0]) 当作要执行的命令,把后面的部分 (cmdarry[1:]) 作为命令执行的参数转换成 byte 数组 argBlock。

最后将处理好的参数传给 UNIXProcess

最后将处理好的参数传给 UNIXProcess

此时 prog 是要执行的命令 ifconfig , argBlock 都是传给 ifconfig 的参数 en0;whoami

经过 StringTokenizer 对字符串的处理,命令执行的语义发生了改变,并不是最初设定的想法。

Runtime.getRuntime().exec(String cmdarray[])

传入数组

public class command_cmd {
    public static void main(String[] aaa) throws IOException {
        String command = "ifconfig en0;whoami";

        Process p = Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", command});
        InputStream in = p.getInputStream();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] b = new byte[1024];
        int a = -1;
        while ((a = in.read(b)) != -1) {
            baos.write(b, 0, a);
        }
        System.out.println(baos.toString());

    }
}

我们传入数组,进行分析,因为直接传入的是数组,所以没有经过 StringTokenizer 对字符串的处理

java.lang.Runtime#exec(java.lang.String[])

java.lang.Runtime#exec(java.lang.String[], java.lang.String[], java.io.File)

java.lang.UNIXProcess#UNIXProcess

此时 prog 是要执行的命令 /bin/sh , argBlock 都是传给 /bin/sh 的参数 -c\x00"ifconfig en0;whoami"

同理base64编码也可执行

此时 prog 是要执行的命令 bash , argBlock 都是传给 bash 的参数 -c\x00"{echo,cGluZyAxMjcuMC4wLjE7ZWNobyAxID50ZXN0LnR4dA==}|{base64,-d}|{bash,-i}"

import java.io.IOException;

public class linux_cmd {
    public static void main(String[] args) throws IOException {
        String Command = "bash -c {echo,cGluZyAxMjcuMC4wLjE7ZWNobyAxID50ZXN0LnR4dA==}|{base64,-d}|{bash,-i}";
        Runtime.getRuntime().exec(Command);
    }
}

Runtime反射实现exec的执行

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.*;

public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

        String className = "java.lang.Runtime";
        Class  runtimeClass1 = Class.forName(className);

        Constructor runtimeConstructor =  runtimeClass1.getDeclaredConstructor();
        runtimeConstructor.setAccessible(true);
        Runtime runtime = (Runtime) runtimeConstructor.newInstance();
        Method ExecMethod = runtimeClass1.getMethod("exec", String.class);
        String cmd = "ifconfig en0";
        Process p = (Process) ExecMethod.invoke(runtime, cmd);

        InputStream in = p.getInputStream();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] b = new byte[1024];
        int a = -1;

        while ((a = in.read(b)) != -1) {
            baos.write(b, 0, a);
        }

        System.out.println(baos.toString());
    }
}

通过反射ProcessBuilder类来实现命令执行

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.List;

public class common_exec {
    public static void main(String args[]) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
        Class pClass = ProcessBuilder.class;
        List<String> list = new ArrayList<>();
        list.add("whoami");

        Constructor constructor = pClass.getDeclaredConstructor(new Class[]{List.class});

        Object obj = (ProcessBuilder)constructor.newInstance(list);
        Method startMethod = pClass.getMethod("start");
        Process p = (Process)startMethod.invoke(obj);

        InputStream in = p.getInputStream();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] b = new byte[1024];
        int a = -1;

        while ((a = in.read(b)) != -1) {
            baos.write(b, 0, a);
        }

        System.out.println(baos.toString());

    }
}

通过反射ProcessImpl类来实现命令执行

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class common_exec2 {
    public static void main(String args[]) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException {
        Class pClass = Class.forName("java.lang.ProcessImpl");
        //Constructor constructor = pClass.getDeclaredConstructor(); //这里是空的构造参数
        //constructor.setAccessible(true);
        //Object obj = constructor.newInstance();
        String[] cmdarray = new String[]{"whoami"};
        Method startMethod = pClass.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
        startMethod.setAccessible(true);
        Process p = (Process)startMethod.invoke(null,cmdarray, null, null, null, false); //这里需要的五个参数,第一个参数为null,因为调用的方法是这个类的静态方法

        // 下面的操作就是正常的读取数据的操作
        InputStream in = p.getInputStream();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] b = new byte[1024];
        int a = -1;

        while ((a = in.read(b)) != -1) {
            baos.write(b, 0, a);
        }

        System.out.println(baos.toString());

    }
}

标签:lang,java,String,exec,命令,本地,import,Runtime
来源: https://www.cnblogs.com/lalalaxiaoyuren/p/16662567.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有