从环境中正在运行的内容开始。我列出所有活动容器:``` docker ps-a

**受害者仅暴露了一个端口:`9001`**。
端口 9001 并非标准的 Web 应用。查阅**端口数据库**可知,该端口可能与 **Supervisord**(根据 IANA 的命名,即 ETL 服务管理器)、Tor 代理或其他某些内部服务相关联。然而,我们不能仅凭端口号就得出结论。
⇒ 我直接使用 curl 读取响应,并访问 Web GUI 以收集更多信息```
curl -i http://192.168.3.137:9001/


响应分析:
Server: Medusa/1.12 以及标题 Supervisor Status→ 确认这是 Supervisord,而非 Tor 或任何其他服务。
REFRESH、RESTART ALL、STOP ALLSupervisord 是 Linux 上的进程管理器。如果 port 9001 在未设置密码的情况下暴露到网络中,这是一种危险的配置。攻击者可以查看服务、重启/停止进程,并且在某些配置下,如果拥有修改或控制受管程序的权限,还能利用其执行命令。
分析结论:我们可以确认目标将 Supervisord 管理界面通过网络暴露在 9001 端口。这不是标准的 Web 服务,而是用于监控和控制进程的管理界面。无需认证即可访问此界面的能力,会造成攻击者可以查看受管服务状态或与受管服务交互的风险。
然而,我们必须区分可见的 UI 与底层的控制机制。像 REFRESH、RESTART ALL、STOP ALL 这样的按钮并不会在前端独立处理请求;相反,它们必须调用 Supervisord 的接口/后端来获取状态或发送进程控制命令。因此,在确认 Web UI 已暴露之后,下一步分析是确定 Web UI 背后是否还存在底层的控制接口,以及该接口是否需要认证。
⇒ 思考:需要检查 Web UI 背后的控制接口是否存在,以及它是否需要认证。

根据 Supervisor 的文档,[inet_http_server] 是一个监听 TCP 套接字的 HTTP 服务器。该接口默认不启用,只应在受信任的环境中使用,不支持加密,并且除非配置了 username/password,否则默认没有认证。
该文档还指出,[inet_http_server] 的端口用于接收 HTTP/XML-RPC 请求;supervisorctl 通过该端口使用 XML-RPC 与 supervisord 通信。这与我们在实验中的观察一致:容器暴露了 0.0.0.0:9001->9001/tcp,Web UI 无需认证即可访问,显示的版本是 Supervisor 3.3.2。
因此,在确认 9001 端口上的 Web UI 之后,下一步是检查 XML-RPC 端点 /RPC2。基于 Supervisord 的官方机制,我们需要验证以下目标:
/RPC2 是否存在。supervisor.getState 或 system.listMethods 的非破坏性方法。检查端点是否存活:```
curl -i -s -X POST -H "Content-Type: text/xml"
-d 'supervisor.getState'
http://192.168.3.137:9001/RPC2

结果返回的是 `HTTP/1.1 200 OK`,而不是 `401 Unauthorized` 或 `403 Forbidden`,这表明服务器在没有凭据的情况下接受了该请求。响应采用 XML-RPC 的 `<methodResponse>` 格式,并包含 `statename=RUNNING` 和 `statecode=1`,证明 `/RPC2` 端点处于活动状态,且 `supervisor.getState` 方法已成功执行。
**⇒ 思考:** 攻击面不再局限于 Web UI,而是扩展到了 XML-RPC API,该 API 负责处理守护进程/进程控制命令。从这里开始,下一步的分析方向是**检查 Supervisor** 如何处理 **XML-RPC** 中的 `methodName`,以判断当前目标是否**表现出 CVE-2017-11610 的行为**,该漏洞存在于该方法的调度/查找机制中。我们需要验证这一点,以最终判断它是否就是 **CVE-2017-11610**。
### **分析 XML-RPC 中的方法名处理**
在上一步中,我们通过 `/RPC2` 端点成功调用了 `supervisor.getState` 方法。这就引出了下一个问题:当收到一个基于字符串的 `methodName` 时,Supervisord 如何将该字符串映射到内部的 Python 函数?
在 XML-RPC 中,方法通常使用命名空间,例如:
- `supervisor.getState`
- `supervisor.stopProcess`
- `supervisor.getAllProcessInfo`
从逻辑上讲,服务器接收 `methodName` 字符串,按点 `.` 进行拆分,然后在已注册的处理器中查找对应的对象/函数。
伪代码可以理解如下:```python
# Pseudo-code simulating the dispatch method concept in XML-RPC
def dispatch(method_name, params):
parts = method_name.split(".") # ["supervisor", "getState"]
obj = registered_handlers[parts[0]] # get namespace "supervisor"
for attr in parts[1:]:
obj = getattr(obj, attr) # lookup the next attribute
return obj(*params) # call the final function
对于标准方法(如 supervisor.getState),该机制正常工作:服务器会取出 supervisor 处理器,然后调用 getState 函数。 然而,CVE-2017-11610 的核心问题在于,这种查找机制并未充分限制允许访问的属性。如果攻击者能够控制 methodName,那么他们不仅可以调用 getState 等公开方法,还可以沿 supervisor 处理器向下深入遍历其内部可达的对象/模块。
换句话说,methodName 中的点 . 不仅用于调用合法方法,还可能被滥用为遍历对象属性。
这就构成了我们的利用路径:
supervisor → supervisord → options → warnings → linecache → os → system
其思路是:从 supervisor 处理器出发,沿属性追踪到守护进程的内部对象,然后借助预先导入的 Python 模块最终抵达 os.system。如果能够调用 os.system,攻击者就能以 supervisord 进程的权限执行系统命令。
因此,攻击链遵循以下逻辑:
/RPC2 接受未认证的方法调用 → 研究 XML-RPC 如何分发 methodName → 发现 methodName 可以遍历对象属性 → 最终导致调用 os.system。
首先,我需要检查服务器是否真的允许遍历内部属性。 我会尝试调用一个比平时更长的 method name。如果服务器返回“method not found”错误,说明存在过滤器;如果返回的是其他错误(或调用成功),则说明遍历有效。
思考: 我已经知道 supervisor.getState 可以工作。如果我尝试 supervisor.supervisord——这相当于多向下一层——而服务器没有返回 unknown method 错误,那就意味着它确实在使用递归 getattr,且没有白名单限制。
我们知道 XML-RPC 是一种基于 HTTP 的远程过程调用协议,数据以 XML 编码传输。每个请求仅由3 个固定组件组成:```
FUNCTION_NAME VALUE ``` 简单的结构——只需替换 `` 和 ``。如果函数不需要参数,将 `` 留空。如果函数需要字符串,将其包裹在 `...` 中。这并非秘密知识——阅读 XML-RPC RFC 即可了解详情。⇒ 应用:尝试调用比平时更长的 methodName,以检查命名空间遍历:```
curl -s -X POST -H "Content-Type: text/xml"
-d 'supervisor.supervisord.options'
http://192.168.3.137:9001/RPC2

返回的是 `HTTP 500 Internal Server Error`,而不是标准的 `unknown method` 错误。这表明服务器并未在有效的命名空间级别拦截 `methodName`,而是在调度过程中继续处理了 `supervisor.supervisord.options` 链。换句话说,请求深入遍历了属性查找机制;错误发生在后续步骤,因为解析出的对象无法作为方法调用。这是命名空间通过 `methodName` 进行遍历已生效的明确迹象。
### **查找命令执行函数的路径**
遍历已生效。下一步是**查找一个以可执行系统命令的可调用函数结尾的属性链。**在 Python 中,最容易验证的目标是 `os.system()`。然而,我们**在目标上没有 shell**,并且**无法直接读取容器中的源代码/运行时对象。**因此,我们必须**根据 Python 的导入机制进行推断**,并**先在本地验证依赖关系。**
需要检查的链为:
`supervisor` → `supervisord` → `options` → `warnings` → `linecache` → `os` → `system`
- Supervisord 是用 Python 编写的,因此 `options` 等内部对象是带有属性的 Python 对象。
- 如果某个模块通过 `import X` 导入另一个模块,那么 `X` 将存在于该模块的命名空间中。
- 在 Python 标准库中,`warnings` 模块导入 `linecache` 以便在显示警告时获取上下文。
- `linecache` 模块导入 `os` 用于路径/文件操作。
- `os` 模块提供 `system()` 函数,该函数是可执行 shell 命令的可调用对象。
在对目标执行之前,先在本地确认此依赖关系:```bash
python3 -c "import warnings; print('linecache' in dir(warnings))"
# True
python3 -c "import linecache; print('os' in dir(linecache))"
# True
python3 -c "import os; print(callable(os.system))"
# True
⇒ 思考: 依赖链 warnings → linecache → os 是 CPython 标准库中的真实依赖关系;并且 system 确实是 os 模块中的一个可调用函数。结合 XML-RPC 中的命名空间遍历缺陷,如果我们能从 supervisor 处理程序访问到 supervisord.options.warnings,我们就可以继续遍历到 linecache.os.system 来调用系统命令。
在识别出通往 os.system 的遍历链之后,下一步是构造 XML-RPC 请求来调用该函数。在 Python 中,os.system() 接受一个字符串参数,代表要执行的 shell 命令,并返回命令的退出码。该函数不会直接将 stdout 返回给 XML-RPC 响应,因此为了证明命令已执行,我们必须将输出重定向到文件中。
⇒ 思考: 响应中没有直接输出,因此将结果写入 /tmp。在 Linux 上,/tmp 目录通常所有用户都可写。一个安全的验证载荷是:
id > /tmp/rce_proof.txt
将上述分析的 XML-RPC 模板应用过来,把 <methodName> 替换为指向 os.system 的遍历链,并在 <string> 中传入 shell 命令:```bash
curl -s -X POST -H "Content-Type: text/xml" -d 'supervisor.supervisord.options.warnings.linecache.os.systemid > /tmp/rce_proof.txt' http://192.168.3.137:9001/RPC2

`<int>0</int>` 是 `os.system()` 的退出码,而不是命令的标准输出。退出码 `0` 表示 shell 命令执行成功。我们可以通过读取容器内的文件来验证这一点:```
docker exec project1-lab03-1 cat /tmp/rce_proof.txt
