背景
Log4j 漏洞已经过去一段时间,但在实际安全工作里,类似组件级高危漏洞并不会因为热点消退就自然消失。很多内网系统、历史服务、第三方交付包里仍然可能残留旧版本依赖。相比单纯讨论漏洞原理,我更关注排查链路是否完整:能不能找到资产、判断是否受影响、确认修复是否有效,并留下可复用的方法。
这篇文章整理一次偏防守视角的 Log4j 风险排查流程,重点放在资产识别、依赖定位、验证思路和修复注意点。文中不包含利用攻击目标的操作,也不提供外部攻击引导,适合用于内部自查、应急复盘和代码审计场景。
问题场景
排查对象是一批 Java Web 服务,包含自研系统、老旧管理后台、定时任务服务以及部分供应商提供的 jar 包。最开始的问题并不是“有没有漏洞”,而是更现实的几个点:
- 服务数量多,部署方式不统一,有 Tomcat、Spring Boot fat jar、Docker 镜像和传统脚本启动。
- 部分系统没有准确的依赖清单,代码仓库和线上部署包版本不完全一致。
- 开发侧只关注 Maven 依赖,但实际运行时可能包含 shaded jar、lib 目录旧包或供应商内嵌组件。
- 安全修复后缺少验证手段,容易出现“升级了 pom,但线上没更新”的情况。
因此排查不能只看代码仓库,也不能只依赖漏洞扫描器的结果。比较稳妥的方式是从资产、文件、依赖和运行时多个角度交叉确认。
技术分析
Log4j 相关风险的核心并不复杂:问题通常出现在特定版本的 log4j-core 组件中,而很多 Java 服务会通过直接依赖、传递依赖或打包内嵌的方式引入它。排查时需要重点区分以下几类情况:
- 仅存在 log4j-api:不等同于存在高风险组件,仍需确认是否存在 log4j-core。
- 存在 log4j-core 旧版本:需要根据版本和部署配置判断是否受影响,通常建议直接升级到安全版本。
- 组件被重新打包:例如 fat jar、Spring Boot 可执行 jar、供应商封装包,不能只在文件名层面搜索。
- 运行环境不一致:测试环境已修复,不代表生产环境部署包同步完成。
经验上,排查时不要只问“有没有 log4j”,而要回答这几个问题:
- 线上实际运行的包里是否包含 log4j-core?
- 版本号是多少,是否为安全版本?
- 是否存在多个版本同时出现的情况?
- 修复动作是替换依赖、升级框架,还是临时缓解?
- 修复后有没有对线上文件和运行进程做验证?
关键步骤
一、资产与部署包定位
第一步先把运行中的 Java 进程和部署路径梳理出来。很多漏报来自资产不完整,尤其是定时任务和后台管理服务。
ps -ef | grep java | grep -v grep如果服务由 systemd 管理,可以结合以下命令确认启动脚本和工作目录:
systemctl list-units --type=service | grep -i java
systemctl cat your-service-name对于容器环境,建议同时记录镜像名、容器 ID、挂载目录和启动命令:
docker ps --format 'table {{.ID}}\t{{.Image}}\t{{.Names}}\t{{.Command}}'
docker inspect <container_id> | grep -E 'Image|Mounts|Cmd|Entrypoint' -n这里的目标不是立即判断漏洞,而是建立一个“线上实际运行清单”。后续所有修复验证都应该回到这份清单上。
二、文件层面的组件搜索
在部署目录内先做文件名搜索,找出明显的 log4j 组件:
find /opt /app /data -type f \( -name '*log4j*.jar' -o -name '*.war' -o -name '*.ear' \) 2>/dev/null如果发现 jar 包,可以直接查看 MANIFEST 或 pom 信息。部分 jar 的版本号可以通过文件名判断,但更可靠的是查看包内元数据:
jar tf log4j-core-*.jar | head
unzip -p log4j-core-*.jar META-INF/MANIFEST.MF
unzip -p log4j-core-*.jar META-INF/maven/org.apache.logging.log4j/log4j-core/pom.properties对于 Spring Boot fat jar,不能只看外层文件名,需要查看内部 BOOT-INF/lib:
jar tf app.jar | grep 'BOOT-INF/lib/.*log4j.*\.jar'传统 war 包则重点检查 WEB-INF/lib:
jar tf app.war | grep 'WEB-INF/lib/.*log4j.*\.jar'如果供应商包做过二次封装,文件名不一定明显,可以搜索类名。以下命令用于内部自查,关注是否存在相关类文件:
find /opt /app /data -type f -name '*.jar' 2>/dev/null | while read f; do
if jar tf "$f" 2>/dev/null | grep -q 'org/apache/logging/log4j/core/lookup/JndiLookup.class'; then
echo "$f"
fi
done需要注意,类名存在不代表一定可被利用,但它是风险判断的重要线索。实际处置上,如果版本落在受影响范围,建议按组件升级处理,而不是依赖单点缓解。
三、代码仓库与依赖树检查
如果能拿到源码,Maven 项目可以查看依赖树:
mvn dependency:tree | grep -i log4jGradle 项目可以使用:
./gradlew dependencies | grep -i log4j这里要特别关注传递依赖。例如项目没有直接声明 log4j-core,但某个旧版本框架或内部公共包引入了它。建议把依赖树结果和线上部署包搜索结果做对比,不要只看一边。
一个常见问题是:开发修了 pom.xml,但构建缓存或部署流程仍然发布旧包。遇到这种情况,线上包内的 pom.properties 往往比仓库文件更能说明问题。
四、修复策略选择
对于仍包含受影响版本 log4j-core 的服务,优先建议升级到官方安全版本,并重新构建发布。临时缓解措施只能作为过渡,不能替代升级。
Maven 示例:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.2</version>
</dependency>如果项目通过父工程统一管理版本,建议在 dependencyManagement 中固定版本,避免不同模块出现版本漂移:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-bom</artifactId>
<version>2.17.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>对于 JDK 版本较老或框架兼容性复杂的系统,需要先在测试环境验证日志功能、启动流程和关键业务链路。安全升级不应只看服务能否启动,还要确认日志格式、异步日志、日志切分等配置没有异常。
五、修复后的验证
修复验证建议从三个层面做:
- 源码依赖:确认依赖树中不再出现受影响版本。
- 构建产物:确认 jar、war、镜像内的组件版本正确。
- 线上运行:确认当前进程实际加载的是新部署包。
检查构建产物时,可以直接对最终发布包做确认:
jar tf app.jar | grep -i log4j
unzip -p app.jar BOOT-INF/lib/log4j-core-*.jar META-INF/maven/org.apache.logging.log4j/log4j-core/pom.properties如果是 Docker 镜像,建议在镜像构建后直接进入镜像检查,而不是只看宿主机目录:
docker run --rm -it your-image:tag sh
find / -type f -name '*log4j*.jar' 2>/dev/null生产环境完成发布后,再对运行进程关联的文件路径做一次抽查。Linux 下可以结合进程启动参数和工作目录确认:
ps -ef | grep java | grep -v grep
readlink -f /proc/<pid>/cwd
tr '\0' ' ' < /proc/<pid>/cmdline注意点
- 不要只依赖文件名:有些包名被改过,或者组件被嵌入到更大的 jar 里,应该结合类名和元数据检查。
- 不要只看仓库:线上部署包可能不是当前代码分支构建出来的,尤其是历史系统。
- 注意多版本共存:同一服务内可能同时存在多个 log4j 版本,类加载顺序会影响实际运行结果。
- 避免长期依赖临时缓解:删除单个类或增加启动参数只适合作为短期措施,后续仍应完成组件升级。
- 保留排查记录:记录服务名、部署路径、组件版本、修复方式、验证结果,方便后续审计和复盘。
排查记录模板
实际工作中可以用简单表格记录,避免修复过程口头化。
| 字段 | 说明 |
| 系统名称 | 业务系统或服务名 |
| 部署位置 | 主机路径、容器镜像或发布包位置 |
| 组件情况 | 是否存在 log4j-core,版本是多少 |
| 风险判断 | 是否受影响,判断依据 |
| 修复方式 | 升级版本、替换依赖或其他处置 |
| 验证结果 | 源码、构建产物、线上进程验证情况 |
| 负责人 | 开发、运维或安全侧跟进人 |
总结
组件漏洞排查最怕两件事:资产不完整和验证不到位。Log4j 这类问题看似是单个依赖的升级,实际考验的是组织对 Java 服务、构建流程、发布链路和第三方包的掌握程度。
比较稳的做法是:先建立线上资产清单,再从文件、依赖树、构建产物和运行进程多点交叉确认;修复时优先升级组件版本,临时措施只作为过渡;修复后必须回到线上环境验证。这个流程不只适用于 Log4j,对后续 Fastjson、Spring、Jackson 等组件级漏洞排查也同样适用。
安全排查不要停留在“扫到了什么”,更重要的是能解释“为什么受影响、怎么修、修完是否真的生效”。这部分记录留下来,后面遇到类似事件会省很多时间。
推荐意见