手把手教你搭建PublicCMS审计环境:用Docker+IDEA动态调试追踪漏洞链

张开发
2026/4/9 9:37:07 15 分钟阅读

分享文章

手把手教你搭建PublicCMS审计环境:用Docker+IDEA动态调试追踪漏洞链
从零构建PublicCMS动态审计环境DockerIDEA全链路漏洞追踪实战环境准备与基础配置搭建一个可调试的PublicCMS研究环境需要系统性规划。我们选择Docker作为环境隔离方案配合IDEA实现代码级调试能力。这套组合能完美还原真实漏洞触发场景同时提供执行流追踪的灵活性。基础组件清单Docker Desktop 4.12需开启Linux容器模式IntelliJ IDEA Ultimate 2023.2社区版缺少Java EE支持PublicCMS V5.202302.e源码包OpenJDK 11与PublicCMS兼容性最佳先创建项目目录结构mkdir -p ~/publiccms-audit/{source,deploy,scripts} cd ~/publiccms-audit/source wget https://github.com/sanluan/PublicCMS/archive/refs/tags/V5.202302.e.zip unzip V5.202302.e.zip编写Docker编译配置文件Dockerfile.buildFROM maven:3.8.6-eclipse-temurin-11 AS builder WORKDIR /build COPY . . RUN mvn clean package -DskipTests构建基础镜像时建议添加调试参数docker build -f Dockerfile.build -t publiccms-builder . docker run -d --name pc-builder publiccms-builder docker cp pc-builder:/build/target/publiccms.war ~/publiccms-audit/deploy/IDEA动态调试环境搭建导入项目时选择Import as Maven Project确保所有依赖正确加载。关键配置步骤如下打开Run/Debug Configurations面板新增Remote JVM Debug配置设置调试参数-agentlib:jdwptransportdt_socket,servery,suspendn,address5005 -Dfile.encodingUTF-8 -Dcms.port8088 -Dcms.contextPath/publiccms启动调试会话前需要先用特殊参数运行PublicCMSjava -jar publiccms.war \ -Djava.security.egdfile:/dev/./urandom \ -Dspring.devtools.restart.enabledfalse \ -Dcms.filePath./data在IDEA中设置断点的几个技巧对org.apache.commons.fileupload包添加条件断点在freemarker.core.Environment类设置方法断点使用Evaluate Expression实时修改变量值漏洞追踪实战方法论1. SSRF漏洞动态分析以CVE-2024-40543为例演示如何追踪完整的漏洞链在UeditorAdminController.java的catchImage方法设断点发送恶意请求触发漏洞POST /publiccms/ueditor/admin/catchimage HTTP/1.1 Content-Type: application/x-www-form-urlencoded source[]http://internal-service:8080使用Frames面板查看调用栈|- catchImage(UeditorAdminController:187) |- execute(HttpClientBuilder:1023) |- createRequest(RequestBuilder:235)关键观察点HttpClient实例化时的连接池配置URIBuilder对特殊字符的处理逻辑响应头Content-Type验证缺失处2. 文件上传漏洞深度追踪针对CVE-2024-40546通过调试还原漏洞触发路径在CmsWebFileController.save()设置入口断点监控FileUtils.writeStringToFile的调用过程重点关注路径穿越检查的绕过点// 安全检测被绕过示例 String safePath getSafeFileName(userInput); // 但后续拼接时未做二次验证 File target new File(baseDir / safePath);调试时可使用Memory View监控文件系统操作watch -n 0.1 ls -la /tmp/publiccms/uploads3. 反序列化漏洞链复现对于CVE-2023-46990这类复杂漏洞需要分阶段调试阶段一载荷构造// 在IDEA的Scratch File中测试Payload TemplatesImpl templates new TemplatesImpl(); setFieldValue(templates, _bytecodes, evilBytecode); BadAttributeValueExpException payload new BadAttributeValueExpException(templates);阶段二Redis交互调试// 设置Redis断点 Jedis jedis new Jedis(127.0.0.1, 6379); jedis.set(exploit.getBytes(), serialize(payload));阶段三触发点监控在RedisCache.getObject()方法设置条件断点// 条件表达式 key.equals(exploit) value instanceof byte[]高效审计技巧汇编1. 智能代码搜索策略组合使用IDEA的以下功能Search Everywhere双击Shift跨项目检索Find Usages追踪方法调用链Structural Search模式匹配示例configuration pattern$Method$($HttpRequest$ $req$)/pattern constraints constraint expr-typetype within*Controller/ /constraints /configuration2. 运行时数据监控方案监控类型工具应用场景HTTP流量Burp Suite请求/响应篡改测试文件操作jNotify敏感文件写入监控数据库访问P6SpySQL注入点定位反序列化操作SerializationDumper恶意载荷分析3. 典型漏洞模式速查表// 1. SSRF风险模式 GetMapping(/proxy) public String proxy(RequestParam String url) { return restTemplate.getForObject(url, String.class); } // 2. 命令注入风险模式 Runtime.getRuntime().exec(ping userInput); // 3. 反射型XSS风险模式 model.addAttribute(message, request.getParameter(msg));进阶调试场景解析1. 多线程环境调试当审计涉及异步处理的漏洞时如定时任务相关需要特殊配置在Edit Configuration中勾选Allow parallel run对线程池代码添加断点条件Thread.currentThread().getName().contains(task-)使用Thread Dump分析竞争条件2. 第三方库代码追踪对于Freemarker模板注入等场景需要在Project Structure中添加库源码对freemarker.core.Environment类设置方法断点监控TemplateModel的处理过程3. 内存马注入分析检测潜在的内存马行为# 在调试时执行内存扫描 jmap -histo pid | grep org.apache.catalina jstack pid | grep execute防御方案与加固建议1. 安全配置基线# application-security.properties cms.security.filter.xsstrue cms.security.filter.sqlInjectiontrue cms.security.fileUpload.whiteList.jpg,.png2. 关键补丁对比分析漏洞类型修复前代码修复后代码SSRFnew URI(userInput)URIValidator.check(userInput)文件上传FileUtils.copy(file)SafeFileUtils.validate(file)反序列化ObjectInputStream.readObject()SafeObjectInputStream.filtered()3. 监控体系建设方案# 示例简易漏洞监控脚本 import requests from difflib import unified_diff def check_vuln_patch(url, version): res requests.get(f{url}/version) return version not in res.text

更多文章