Java远程代码执行学习(1)

前言:最近在学习Java安全方面的内容,记录一下Java中的几个命令执行函数,使用Javasec靶场进行演示

第一个命令执行函数 RuntimeExec

漏洞描述

远程命令执行漏洞,用户通过浏览器提交执行命令,由于服务器端没有针对执行函数做过滤,导致在没有指定绝对路径的情况下就执行命令

可能会允许攻击者通过改变 $PATH 或程序执行环境的其他方面来执行一个恶意构造的代码。

getRuntime()常用于执行本地命令,使用频率较高。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class RuntimeExec {
@RequestMapping("/runtime")
public String RuntimeExec(String cmd, Model model) {
StringBuilder sb = new StringBuilder();
String line;

try {
Process proc = Runtime.getRuntime().exec(cmd);

InputStream fis = proc.getInputStream();
InputStreamReader isr = new InputStreamReader(fis, "GBK");
BufferedReader br = new BufferedReader(isr);
while ((line = br.readLine()) != null) {
sb.append(line).append(System.lineSeparator());
}

} catch (IOException e) {
e.printStackTrace();
sb.append(e);
}
model.addAttribute("results", sb.toString());
return "basevul/rce/runtime";
}
}

这段代码是一个基于Spring框架的Java程序,其中包含了一个处理HTTP请求的方法。该方法接受一个名为cmd的参数,并将其作为命令传递给操作系统运行。然后,程序获取命令的输出,并将输出内容保存到一个StringBuilder对象中,最后将输出结果作为模型属性返回给视图。

这段代码实现了一个”Runtime Exec”功能,可以执行操作系统命令。

image-20230721100201126

第二个命令执行函数 ScriptEngineManager

该漏洞又叫脚本引擎代码注入

漏洞描述

在Java 8之后ScriptEngineManager的eval函数就没有了

windows:

var a = mainOutput(); function mainOutput() { var x=java.lang.Runtime.getRuntime().exec(“calc”)};

Payload绕过:

var a = mainOutput(); function mainOutput() { var x=java.lang.//Runtime.getRuntime().exec(“calc”);}

恶意代码(java)

1
2
3
4
5
6
7
8
9
10
// 通过加载远程js文件来执行代码,如果加载了恶意js则会造成任意命令执行
// 远程恶意js: var a = mainOutput(); function mainOutput() { var x=java.lang.Runtime.getRuntime().exec("open -a Calculator");}
// ⚠️ 在Java 8之后移除了ScriptEngineManager的eval

public void jsEngine(String url) throws Exception {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
String payload = String.format("load('%s')", url);
engine.eval(payload, bindings);
}

js代码

1
2
3
4
var a = mainOutput();
function mainOutput() {
var x = java.lang.Runtime.getRuntime().exec("calc")
};

引用这一个js文件,就可以触发该漏洞

image-20230721101714944

第三个 Groovy执行命令

1
2
3
4
5
6
7
8
// 不安全的使用Groovy调用命令

import groovy.lang.GroovyShell;
@GetMapping("/groovy")
public void groovy(String cmd) {
GroovyShell shell = new GroovyShell();
shell.evaluate(cmd);
}

* windows: “calc”.execute()

* macos: “open -a Calculator”.execute()

image-20230721102406073

就是一个传参,后端有该代码的时候直接传恶意代码就好

第四个 ProcessBuilder

漏洞描述

Process类是一个抽象类(所有的方法均是抽象的),封装了一个进程(即一个执行程序)。

Process 类提供了执行从进程输入、执行输出到进程、等待进程完成、检查进程的退出状态以及销毁(杀掉)进程的方法。

ProcessBuilder.start() 和 Runtime.exec 方法创建一个本机进程,并返回 Process 子类的一个实例,该实例可用来控制进程并获取相关信息。

创建的子进程没有自己的终端或控制台。它的所有标准 io(即 stdin,stdout,stderr)操作都将通过三个流 (getOutputStream(),getInputStream(),getErrorStream()) 重定向到父进程,通过流的形式进行读取。

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
public String ProcessBuilderExec(String ip, String safe, Model model) {
if (safe != null) {
if (Security.checkOs(ip)) {
model.addAttribute("results", "检测到非法命令注入");
return "basevul/rce/processbuilder";
}
}
// String[] cmdList = {"sh", "-c", "ping -c 1 " + ip};
String[] cmdList = {"cmd", "/c", "ping -n 1 " + ip};
StringBuilder sb = new StringBuilder();
String line;
String results;

// 利用指定的操作系统程序和参数构造一个进程生成器
ProcessBuilder pb = new ProcessBuilder(cmdList);
pb.redirectErrorStream(true);

// 使用此进程生成器的属性启动一个新进程
Process process = null;
try {
process = pb.start();
// 取得命令结果的输出流
InputStream fis = process.getInputStream();
// 用一个读输出流类去读
InputStreamReader isr = new InputStreamReader(fis, "GBK");
// 用缓存器读行
BufferedReader br = new BufferedReader(isr);
//直到读完为止
while ((line = br.readLine()) != null) {
sb.append(line).append(System.lineSeparator());
}
results = sb.toString();
} catch (IOException e) {
e.printStackTrace();
results = e.toString();
}
model.addAttribute("results", results);
return "basevul/rce/processbuilder";
}

image-20230721102848515

第五个 ProcessImpl

漏洞描述

对于ProcessImpl类不能直接调用,但可以通过反射来间接调用ProcessImpl来达到执行命令的目的

该类非Public修饰,所以在不同包下只能通过反射的方式去调用执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public String processImplExec(String cmd, Model model) {
CharArrayWriter infoStream = null;
try {
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);
Process e = (Process) method.invoke(null, new String[]{cmd}, null, null, null, false);

char[] bs = new char[2048];
int readSize = 0;
infoStream = new CharArrayWriter();
InputStream inputStream = e.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "GBK");
while ((readSize = inputStreamReader.read(bs)) > 0) {
infoStream.write(bs, 0, readSize);
}
model.addAttribute("results", infoStream.toString());
} catch (Exception ex) {
ex.printStackTrace();
model.addAttribute("results", ex.toString());
}
return "basevul/rce/processimpl";
}

image-20230721102947929

在白盒测试中,要挖rce漏洞,可以尝试全局搜索这五个函数并进行代码审计

黑盒测试的时候就只能靠运气了


Java远程代码执行学习(1)
https://zzhnohikari.github.io/2023/07/21/Java远程代码执行学习-1/
作者
John Doe
发布于
2023年7月21日
许可协议