1. 前言
官方公告:
https://solr.apache.org/security.html#cve-2024-45216-apache-solr-authentication-bypass-possible-using-a-fake-url-path-ending
https://issues.apache.org/jira/browse/SOLR-17417
漏洞描述:
Apache Solr 中存在不正确的身份验证漏洞。使用 PKIAuthenticationPlugin 的 Solr 实例(在使用 Solr 身份验证时默认启用)容易受到身份验证绕过的影响。任何 Solr API URL 路径末尾的假结尾将允许请求跳过身份验证,同时保持与原始 URL 路径的 API 契约。这个假结尾看起来像一个不受保护的 API 路径,但它在身份验证之后但在 API 路由之前在内部被剥离。
影响版本:
Apache Solr 5.3.0 ~ 8.11.4 之前版本
Apache Solr 9.0.0 ~ 9.7.0 之前版本
2. 环境搭建
9.x.x 版本下载地址:https://archive.apache.org/dist/solr/solr/
8.x.x 及以前版本下载地址:https://archive.apache.org/dist/lucene/solr/官方教程文档:https://solr.apache.org/guide/8_11/
本次复现使用 Apahce solr 8.11.3,jdk 11.0.16。
在bin目录下运行以下命令即可启动或停止 solr,-c是以 SolrCloud 模式启动solr,-p指定启动端口:
solr -p 8983 #启动
solr stop -p 8983 #停止
solr -c -p 8983 #以 SolrCloud 模式启动
solr -c -p 8983 -a "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005" #启动并开启远程调试
浏览器访问 http://localhost:8983/solr/ ,solr 成功运行。
这个时候访问/solr/admin/info/properties,不需要登录,会显示配置信息。要还原漏洞环境,我们需要自己配置身份验证和授权。Solr 所有身份验证、授权和审计日志插件等相关配置(包括用户和权限规则)都存储在 security.json 文件中,在 SolrCloud 模式中需要将 security.json 上传至 ZooKeeper。如果本地没有搭建 ZooKeeper 服务,可以使用 Solr 自带的嵌入式 ZooKeeper,默认端口为 9983。
自定义一个 security.json ,这里直接使用官方提供的配置。
authentication 为认证部分:
- blockUnknown 为 true,未经认证的请求不允许通过;
- 指定 solr.BasicAuthPlugin 插件类为solr提供身份验证;
- 定义用户名为 solr 的用户,密码通过加密存储。authorization 为授权部分:
- 指定 solr.RuleBasedAuthorizationPlugin 插件类为 solr 提供授权;
- permissions 定义操作权限,权限名称为 security-edit,表示允许编辑安全配置。拥有该权限的角色是 admin;
- 定义用户与角色的映射关系,表示 solr 用户拥有 admin角色的权限。{
"authentication":{
"blockUnknown": true,
"class":"solr.BasicAuthPlugin",
"credentials":{"solr":"IV0EHq1OnNrj6gvRCwvFwTrZ1+z1oBbnQdiVC3otuq0= Ndd7LKvVBAaZIF0QAVi1ekCfAJXr1GGfLtRUXhgrF8c="},
},
"authorization":{
"class":"solr.RuleBasedAuthorizationPlugin",
"permissions":[{"name":"security-edit",
"role":"admin"}],
"user-role":{"solr":"admin"}
}
}
先以 SolrCloud 模式启动 Solr,执行下面的命令将自定义的 security.json 文件上传到 ZooKeeper。
solr zk cp -z localhost:9983 file:security.json zk:/security.json
上传成功后重新启动 Solr,身份验证生效,访问主页和 /solr/admin/info/properties 就需要登录了。这样漏洞环境就搭好了。
3. 漏洞复现
在未登录的情况下访问/solr/admin/info/properties会返回401。
在 url 后面添加:/admin/info/key,向请求头中添加SolrAuth头,即可绕过身份验证,成功返回配置信息。4. 漏洞分析
先看看未登录情况下的身份认证流程。
在 SolrDispatchFilter.doFilter() 断点,调用 authenticateRequest() 进行身份认证,如果返回 true,则说明认证通过。
跟进 authenticateRequest(),获取到认证插件为 security.json 中指定的 BasicAuthPlugin。
下面对请求路径进行判断,如果路径匹配/admin/info/key,则直接返回 true 。
如果请求头中含有 SolrAuth 或 SolrAuthV2,则采用 PKIAuthenticationPlugin 认证插件。
这些条件都不满足,调用当前 BasicAuthPlugin 插件的 authenticate() 方法进行认证,如果认证通过,则将 isAuthenticated 设为 true,返回认证成功的结果。跟进到 BasicAuthPlugin.doAuthenticate(),判断了请求头中的 Authorization 字段,由于我们并没有登录信息,所以认证不通过,返回 false。
再来看看绕过后的认证流程。
同样进入到 SolrDispatchFilter.authenticateRequest(),这时请求头中含有 SolrAuth 字段,所以认证插件采用 PKIAuthenticationPlugin。
跟进到 PKIAuthenticationPlugin.doAuthenticate() 进行认证,这里直接通过判断 URI 只要以/admin/info/key结尾就返回 true,所以成功通过 PKIAuthenticationPlugin 插件的认证。SolrDispatchFilter.authenticateRequest() 也返回 true。
继续跟进到 HttpSolrCall.init(),这里判断了路径中是否含有:号,有的话就只保留:号前的部分,于是 path 就变成了/admin/info/properties,这样就能获取到其对应的处理器。
处理器不为 null,就给 HttpSolrCall 对象的 requestType 赋值为 RequestType.ADMIN,给 action 赋值为 Action.ADMIN,表示授予 admin 角色权限。
接着进入 HttpSolrCall.authorize(),调用 RuleBasedAuthorizationPluginBase.authorize() 进行授权,因为上面将 requestType 赋值为了 RequestType.ADMIN,所以这里进入 if 分支。mapping 是 security.json 中授予 admin 角色 security-edit 允许编辑安全配置的权限。跟进 checkCollPerm(),mapping 中没有找到/admin/info/properties的授权规则,继续跟进 checkPathPerm(),返回 NO_PERMISSIONS_FOUND 标识。
接着获取了默认权限列表{"name":"security-edit","role":"admin"},继续检查权限列表是否适用与当前请求。遍历权限列表,到 predefinedPermissionAppliesToRequest(),context.getHandler() 不是 PermissionNameProvider 类型,不适用于当前权限,返回 false。
最后还是返回 NO_PERMISSIONS_FOUND 标识,状态码为 200。
HttpSolrCall.authorize() 返回 null,表示授权成功,继续处理流程。5. 补丁分析
在 8.11.4 版本中,将 PKIAuthenticationPlugin.doAuthenticate() 中判断路径是否以/admin/info/key结尾的代码删掉了,所以当使用 PKIAuthenticationPlugin 插件进行身份认证时,这一步不会返回 true,也就无法再利用/admin/info/key结尾来绕过身份认证。
HttpSolrCall.init() 中截取:号前路径的代码也被删除了,URL路径假结尾无法被剥离,就无法获取到正确的处理器,不会进入下面的 if 分支,就无法被授予 ADMIN 角色权限。