Apache Solr 这几天又被安全圈重新拿来讨论,核心就是 CVE-2021-27905,一个在 ReplicationHandler 组件里埋着的任意文件读取漏洞。Solr 这个开源搜索中间件在不少公司里都是直接暴露在内网甚至公网的,所以这类问题一旦被盯上,后果往往不是"读一个配置文件"这么简单。这篇文章我会把漏洞从组件原理讲到修复验证,再聊一些我在实际排查中踩过的坑,希望能给正在维护 Solr 的同学一份可以直接拿去用的参考。
1. 漏洞全景:这个“文件读取”问题到底出在哪
1.1 核心概念梳理:Solr的复制机制与ReplicationHandler
Solr 做搜索集群的时候,会用到主从复制(Master-Slave Replication)来把索引从主节点同步到从节点。负责处理这个复制动作的,是 Solr 里一个内置的请求处理器,叫 ReplicationHandler。它默认挂在/replication路径之下,提供backup、restore、indexversion、filecontent这类命令,用来检查主从节点的索引版本是否一致,以及把索引分片文件从主节点拉到从节点去。
你可以把 ReplicationHandler 理解成一个"索引传输管家":主节点负责分发文件,从节点负责接收文件,两边靠 HTTP 请求来沟通。正常情况下,这套机制在外网不暴露、内网可信任的前提下是够用的。但问题恰恰出在,这个处理器从设计之初就支持用一个绝对路径甚至跨协议的地址去定位文件,它本身又没有默认强制的身份认证。一旦这个接口能随便访问,就相当于把一个能读取后端任意文件的入口直接交了出去。
很多维护者最初踩坑的地方在于:大家会默认 Solr 只是对内部开放,或者认为"管理接口不对外就行了"。但忽略了一个现实,一旦admin相关接口和replication接口同时处于可访问状态,或者 Solr 直接被映射到了某个公网端口上,那么原本用来同步索引的filecontent命令就会变成攻击者读取服务器文件的通道。这个漏洞影响 Apache Solr 7.0.0 至 8.8.1 版本,官方在 8.8.2 中修复,属于典型的"组件本身功能特性被滥用"型漏洞。
1.2 影响版本与漏洞本质解析
CVE-2021-27905 的具体表现是:攻击者向 Solr 某个集合的/replication接口发送一个带有command=filecontent和file=file:///etc/passwd的请求,在没有授权校验的情况下,服务端会返回对应文件的内容。
这个漏洞的影响程度,可以在 CVSS 体系里大致对照一下:自身的机密性影响是 Complete,也就是说读取范围不受目录限制,只要 Solr 服务进程权限能读到的文件,攻击者都有机会通过构造路径去读。放在实际环境里,这意味着:
- 读取
/etc/passwd、/etc/shadow(取决于进程权限)这样的系统文件; - 读取 Solr 的
solr.xml、solrconfig.xml等配置文件,分析业务数据结构; - 读取应用服务器上保存的数据库账号、缓存连接串、甚至私钥文件。
本质原因不在于 Solr 的加密、认证机制先不先进,而在于它的文件操作入口缺少了"输入白名单"和"身份校验"这两个最基本的约束。很多有过 Java 中间件维护经验的同学看到这里可能会比较有感觉:这是一个典型的"信外部输入、又不查调用者身份"的问题,和不少老牌中间件当年的漏洞成因非常相似。
2. 原理拆解:为什么一个“普通接口”能读走服务器文件
2.1 file参数与file://协议的处理逻辑
ReplicationHandler 在处理filecontent命令时,会读取file参数指定的文件,并返回其字节流。在 8.8.1 及之前的版本中,这个参数解析逻辑有两个关键点:
- 支持直接传入带
file://前缀的完整路径; - 只校验文件是否存在,不校验文件是否在索引目录之内。
这意味着,正常的主从复制流程中,file参数应该指向索引目录下某个段的文件,比如_0.fdt、_0.cfs;但从代码逻辑看,file=file:///etc/passwd与file=index/_0.fdt走的是同一条文件读取通道,没有任何机制告诉程序"你不能越界读取"。
我在复现和排查时,更关注的是触发条件:请求需要包含有效的集合名称,这让漏洞利用看起来需要一个前提,即攻击者能知道或者探测到 Solr 上存在的 collection 或 core 名称。但 Solr 的 API 里一般是允许通过http://ip:8983/solr/admin/cores?action=STATUS这类接口拿到集合列表的,所以这个"门槛"可高可低。如果运维侧把管理接口访问权限一起放开,那攻击者从知道目标端口到构造完整请求,基本就是一两条命令的事。
如果把这种问题类比到日常开发里,它很像我们在做文件导出功能时,只校验了"要下载的文件是否存在",却没有校验"这个文件是否真的属于当前用户可下载的范围"。一个请求参数一旦从"文件名"变成了"任意路径",漏洞的边界就从业务文件扩展到了服务器文件系统。
2.2 攻击路径与业务危害推演(授权测试视角)
从防御者视角看,我们可以把一次完整的利用过程拆成四条主链路来理解,这四条链路在排查和修复时也对应着不同的检查点:
第一,探测阶段。攻击者请求 Solr 根路径或者/solr/admin/cores,拿到集合核心列表。我们排查时可以关注访问日志里是否有大量到/admin/cores的请求。
第二,确认复制接口是否开放。攻击者会尝试访问/solr/{collection}/replication?command=details或indexversion,正常返回时说明复制处理器在监听。
第三,实际利用。拼接command=filecontent和file=file:///etc/passwd读取目标文件。这条请求在访问日志里会比较显眼,因为它同时包含filecontent和file=参数。
第四,横纵向扩展。读取应用配置文件、数据库配置、密钥文件,或者尝试继续利用其他 Solr 已知漏洞做进一步控制。多数情况下,文件读取只是第一步,它的主要价值在于"信息收集"。
危害程度和 Solr 部署方式强相关:如果 Solr 只是作为后台检索服务跑在一台独立服务器上,读取到的最多是/etc/passwd和 Solr 配置;但很多公司的 Solr 和应用服务共存在同一台机器上,甚至直接把 Solr 部署在堡垒机管理的前端节点上,那这时候读到的文件范围就很吓人了。我在一次授权测试里看到过,目标服务器上同时跑着 Solr 和业务进程,solr 用户权限没做隔离,结果通过这个漏洞直接摸到了业务数据库的明文连接串。所以,评估这个漏洞时不能只按 CVSS 分数来,要看 Solr 进程实际运行在哪个权限边界内。
3. 从防御者视角:如何判断Solr是否中招
3.1 自查三步走:不跑攻击代码也能确认接口状态
很多人看到"任意文件读取"第一反应是我要不要写个脚本去打一下。我的建议是,自查阶段完全不需要跑攻击请求,一个不越界的检查流程反而更安全,也不容易被安全审计系统误判。
第一步,确认版本。在 Solr 的安装目录下执行:
# 查看 Solr 版本 bin/solr -version或者直接访问http://ip:8983/solr/#/页面,在左侧菜单的 "Solr Logging" 页面能看到版本号。这里需要重点确认是否在 8.8.1 及以下,以及有没有官方修复补丁。
第二步,检查集合和复制接口的状态。发起一个没有任何file参数的请求,这里用 Python 的 requests 做示例:
import requests base_url = "http://your-solr-host:8983/solr" # 1. 获取集合列表 cores = requests.get(f"{base_url}/admin/cores?action=STATUS", timeout=10).json() core_names = list(cores.get("status", {}).keys()) print("发现的 core/collection:", core_names) # 2. 对每个 core 检查 replication 是否可访问(不传 file 参数,纯状态探测) for name in core_names: try: status = requests.get( f"{base_url}/{name}/replication", params={"command": "details"}, timeout=10 ) print(f"[{name}] replication HTTP status: {status.status_code}") except Exception as e: print(f"[{name}] replication check error: {e}")如果这一步返回 200,说明复制接口未受访问控制。此时不应当继续尝试读取文件,而应直接进入修复流程。
第三步,核对配置。打开 Solr 的solr.xml和对应集合的solrconfig.xml,看<requestHandler name="/replication">是否显式声明了认证插件。如果在 SolrCloud 模式下,还要检查 ZooKeeper 配置的权限策略。这两份配置是评估"被人拿到文件读取能力"的关键佐证。
3.2 日志排查实录:如何从访问日志里看出异常
如果是应急排查,最直接的部分是看访问日志。Solr 的访问日志一般位于server/logs/solr.log,不过更直观的是 Jetty 的request.log,路径通常在server/logs/下面。
我通常会关注这么几个特征:
- 出现大量针对
/replication的请求,且参数里同时带有command=filecontent和file=; file参数包含file://、/etc/、/proc/、/root/、/home/等敏感路径关键词;- 相同的来源 IP 在短时间内对一个 Solr 端口发起多次不同集合名称的探测;
/admin/cores被高频访问。
如果日志里出现了上述特征,我建议立刻把相关请求行完整备份下来,作为后续排查的记录。同时不要急着清日志,因为后续还可能需要在同类请求里确认是什么时候开始的、持续了多久。
为了避免日志占满磁盘导致系列故障,可以在 Solr 的日志配置里对/replication追加独立的滚动策略。对于规模不大的集群,这一步可以先人工轮转,等修复完成后再统一调整。
3.3 更快一点的排查:从进程和网络侧找线索
除了日志,服务器侧的排查也能提供重要信息。攻击者在读取/etc/passwd后,往往会尝试读取应用配置、密钥文件,这些操作不一定会留下 HTTP 日志之外的其他痕迹,但可以通过进程级审计来补全视角:
# 查看 Solr 进程运行用户 ps -eo user,pid,cmd | grep solr # 查看 Solr 进程当前打开的敏感文件(适用于排查调用链) ls -l /proc/$(pgrep -f 'solr' | head -1)/fd 2>/dev/null | wc -l注意,/proc/{pid}/fd里的文件描述符查看需要权限,如果 Solr 用户和你的排查账号不是同一个,需要用 root 或有权限的账号执行。这个命令的意义不在于读取文件内容,而在于确认 Solr 进程运行的权限范围,以及在后续评估"如果文件读取被利用,最坏能读到哪些东西"。
从网络侧看,如果 Solr 端口对公网开放,可以先用简单的端口测试工具验证是否真的是全 0.0.0.0 监听:
ss -tlnp | grep 8983如果监听地址是0.0.0.0:8983,说明任何能访问到这台机器网络的人都有机会触达 Solr 服务。这里一定要区分清楚:监听地址不等于公网映射,还取决于云安全组、防火墙等网络策略。但无论如何,这个命令能让你快速确认 Solr 服务的网络暴露面。
4. 修复与加固:应急响应后的实操清单
4.1 版本升级与验证
最彻底的修复方式是升级到 Apache Solr 8.8.2 或更高版本。官方在这个版本中修复了 ReplicationHandler 的路径处理逻辑,对file参数进行了更严格的过滤,不再允许随意使用file://协议读取任意文件。
升级的具体步骤我这里简化一下,但每一步都很关键:
- 备份现有 Solr 的
server/solr目录以及 ZooKeeper 元数据; - 在测试环境部署新版本,把原
solrconfig.xml、solr.xml迁移过去; - 通过
bin/solr start启动新版本,逐个集合执行reload,观察查询、索引写入、复制三个动作; - 确认无误后,在夜间低峰期替换生产实例,先升级从节点,再升级主节点;
- 升级后再次执行前面的"自查三步走",确认
/replication不再返回可用的filecontent响应。
升级过程中有个容易忽略的点:Solr 的配置目录在不同版本间存在结构变化,直接覆盖可能会把自定义的lib、conf目录弄丢。我的建议是保留一份旧配置文件快照,升级时用 diff 工具逐项对比,而不是整目录复制。
4.2 权限、认证与网络隔离
如果因为业务兼容性问题暂时无法升级,那么权限和网络隔离是必须做到位的两道防线。
先看认证。Solr 从 8.x 版本开始已经内置了基于规则的身份认证,可以通过bin/solr auth enable快速开启基础认证。需要注意的是,开启认证以后,不仅/replication需要认证,所有管理接口也需要认证,这可能会影响旧的调用方程序。所以在开启前要梳理好所有访问 Solr 的应用账号,避免一开认证就把业务打断了。
再看网络隔离。即使开了认证,我也建议在防火墙或安全组层面限制 8983 端口的来源 IP。最理想的状态是:只有应用服务器、运维跳板机的 IP 能访问 Solr 端口,其余来源一概拒绝。对于规模不大的集群,这比单纯依赖中间件认证更直接、更管用。
如果 Solr 部署在 Nginx 或网关之后,也可以通过 location 规则把/replication直接封禁:
location ~ ^/solr/[^/]+/replication { deny all; return 403; }这段配置适合公开服务的 Solr 场景,直接在反向代理层阻断复制接口,业务功能如果确实不需要主从复制,就用这种方式一刀切。
4.3 如果生产环境暂时不能重启怎么办
生产环境不能随意重启,是每一个维护 Solr 的人都会遇到的现实问题。这种情况下,有几个可以立刻生效的临时方案。
第一,优先用防火墙限制来源 IP。这是立刻生效且不需要重启 Solr 的加固方式,主要看网络团队的配合速度。
第二,如果 Solr 支持热加载配置,可以在solrconfig.xml中调整ReplicationHandler的配置项,把某些命令的权限收紧。不过这里要小心,solrconfig.xml的热加载能力有限,很多配置修改仍然需要重启集合。建议先做小规模验证再批量操作。
第三,部署 WAF 或请求过滤规则。在 Solr 入口层面对包含filecontent且file参数含file://的请求直接拦截。这种方案属于临时止血,主要目的是在补丁上不了的情况下争取时间。
补丁和升级一定要排优先级,这一点我没有别的建议:能用升级解决的漏洞,不要只靠访问控制来拖着。因为访问控制本身也是配置,配置就存在被改错、被遗漏的可能,而版本修复才是从根源上堵住了漏洞点。
5. 相关借鉴:从一次文件读取到整个应用安全
5.1 同类风险:容易和这个漏洞一起出现的Solr问题
Solr 的历史漏洞里,除了任意文件读取,还有一类和它高度相关的风险是 Velocity 模板注入,以及部分场景下的 XXE 问题。这两类问题虽然利用方式不同,但根源都在于 Solr 的默认配置对"来源不可信的输入"太宽容,同时缺少访问控制。
在实际排查中,如果发现 Solr 暴露并且存在任意文件读取,我建议顺手做三件事:
- 检查是否开启了 Velocity 模板引擎的远程渲染(
/solr/{collection}/select?qt=velocity); - 检查是否开启了调试组件,如
debug=df或相关参数,避免调试信息泄露内部配置; - 检查 Solr 的配置文件中是否有
dataImport处理器,数据库连接串的暴露面和文件读取漏洞的危害会被进一步放大。
这些检查不一定都涉及漏洞,但在这个场景下,它们共同决定了一个暴露的 Solr 服务最后会不会演变成全服务器沦陷。安全运维就是这样,单个漏洞单独看也许只是低危中危,组合起来就是一个完整攻击链。
5.2 排查思路复盘:五个动作帮你跑完一次应急响应
把上面所有内容收拢一下,我一般会按这个顺序处理 Solr 任意文件读取的应急排查:
- 确认版本和暴露面:看 Solr 版本号、监听端口、防火墙规则,快速判断风险等级;
- 梳理访问日志:搜索
/replication和filecontent关键词,确认是否已有利用痕迹; - 检查配置文件:看
solrconfig.xml、认证插件、ZooKeeper 权限,搞清楚"当前有没有防护、防护在哪一层"; - 修复并验证:升级版本或加认证、加访问控制,然后重新走一遍接口状态检查,确保
file=file:///etc/passwd这种请求不再生效; - 复盘暴露面:优化 Solr 的部署架构,从安全组、路由、认证、账号体系几个维度做长期加固。
这套流程不复杂,但对时间敏感的应急场景很有用。因为它每一环节都对应着明确的目标:先止损,再定位,再修复,最后避免复发。
我在处理 Solr 类问题时最大的感受是:不要高估攻击者的成本,也不要低估复制接口在整个集群里的默认信任地位。任何一个名为"管理"的接口,一旦网络层不可达,它的安全等级就大幅提升;而一旦暴露出去,它就必须按"可被直接攻击"的标准来配置。这是我在检查一些老项目时反复验证过的事,也是这篇文章最想提醒大家的一点。