ARTEX 无扫描器资产收集方法

3172 字
16 分钟
ARTEX 无扫描器资产收集方法

资产是”副产品”,不是”前置阶段”#

传统渗透的资产收集是一个独立的前置阶段:

subfinder -d target.com → httpx → naabu → nmap → 得到资产清单 → 开始打

ARTEX 完全反过来:

planner 派一条意图("摸清某子系统")
→ worker 打的过程中顺手发现资产
→ insert_assets 记进图
→ enrich 异步补全(DNS/HTTP)
→ 图变大 → planner 看到新节点 → 派新意图

资产收集不是一个阶段,是探索过程的沉淀物。 这解释了为什么它不需要 subfinder —— subfinder 是”一次性把子域全捞出来”的工具,而 ARTEX 是”打到哪收到哪,边收边扩”。

这个循环靠的是图的反馈:insert_assets 写资产 → SetOwnerNode(intent.ID) 记下哪条意图发现的 → enrich 补全 → planner 的 graph_overview 里资产数变多 → 下一轮规划自然看到新空间。


1. 五条资产管道#

管道一:外部平台同步(ScopeSentry)#

server/sync_scopesentry.go

ScopeSentry 是一个 ASM(攻击面管理)平台。ARTEX 通过它的 MCP 接口按「项目」或「任务」两个维度拉数据:

// 数据源本身是一个名为 "ScopeSentry" 的 http 传输 MCP 行
// (url + X-API-Key 头保存在 mcp_servers)

拉回来的东西映射成 6 种资产类型:域 / 子域 / IP / 端口 / 站点 / 端点。

关键设计:这里用 mcphttp.Client.Call 直接调用,不触发 AskUser 权限弹窗 —— 因为这是后台批量同步,不该一条条弹窗确认。

还有个保护:syncMaxPerType = 5000,单目标单类型最多入 5000 条,防止误配把库撑爆。

这是”不装 subfinder”的第一个答案:让别人替你扫,你只消费结果。 ScopeSentry 本身就是干资产测绘的,重复造轮子没意义。

管道二:在线公开数据源(agent 自己 curl API)#

这是最反直觉的一环 —— 没有 subfinder,但 agent 会直接 curl 打各种公开数据源的 API:

数据源次数能拿到什么
ICP 备案查询13按备案号反查同主体所有域名
crt.sh(证书透明度日志)11历史上所有签发过证书的子域
subcenter / subdomain.center3在线子域查询
web.archive.org3历史快照里的旧域名/旧接口
hackertarget2在线子域/反查
urlscan.io2别人扫过的页面里的资源域名
bing / google2site: 语法搜子域
rapiddns1被动 DNS 数据集
alienvault OTX1威胁情报里的关联域名
github 搜索1代码里泄露的内部域名

crt.sh 的真实用法:

Terminal window
curl -s --max-time 30 "https://crt.sh/?q=%25.<domain>&output=json" -o crtsh.json
wc -c crtsh.json

%25 是 % 的 URL 编码,%.domain 就是通配符查询 —— 一次拿到该域所有历史证书里的子域名。

从文件命名就能看出手法:

subs_crtsh.txt ← crt.sh 拿的
subs_ht.txt ← hackertarget 拿的
subs_subcenter.txt ← subcenter 拿的
subs_cs.txt ← 另一个源
ct_subs.txt ← 证书透明度
linked_subs.txt ← 关联域名推导
all_subs.txt ← 合并去重后的总表

这是 subfinder 的替代方案,而且更灵活:subfinder 内置几十个源的 SDK,规则固定;agent 直接 curl 每个源的 API,返回什么格式就现场解析什么格式。用 11 次 crt.sh、13 次 ICP 查询,覆盖了 subfinder 最核心的两个源。

管道三:主动 DNS 枚举#

258 条命令带 for 循环 + dig/host/getent,典型形态:

Terminal window
for h in <sub1>.<target> <sub2>.<target> portal-service.<sub>.<target> uniapp.<sub>.<target>; do
a=$(dig +short A "$h" | grep -E '^[0-9]' | tr '\n' ',')
c=$(dig +short CNAME "$h" | grep -E '\.' | tr '\n' ',')
echo "$h -> A=[$a] CNAME=[$c]"
done

注意四个细节:

  1. A 和 CNAME 分开取。CNAME 指向 CDN 时,A 记录是 CDN 的 IP(无价值),但 CNAME 能暴露真实源站(比如指向 xxx.oss-cn-hangzhou.aliyuncs.com 就说明用的是阿里云 OSS)。
  2. grep -E '^[0-9]' 过滤。dig +short 有时返回空行或注释,不过滤会污染结果。
  3. tr '\n' ',' 压成一行。多 IP 合成 1.2.3.4,5.6.7.8,方便后续处理。
  4. 通配符检测先行(关键):
Terminal window
for n in randomxyzabc123.<domain> thisdoesnotexist98765.<domain>; do
dig +short A "$n"
done

如果随机字符串也解析出 IP,说明配了泛解析 —— 此时所有”子域存在”的结论都不可信,必须靠响应内容差异(title、大小)而不是 DNS 是否解析来判断。

还有指定 resolver 的进阶用法:

Terminal window
dig +short A "$d" @<ip>

用目标自己的 DNS 服务器查,能看到外部视角看不到的内网记录。

管道四:被动提取(从已有材料里抠)#

这是产出最多的一条,全部次数:

手法次数说明
从 HTML/JS 提取域名261页面里的外链、API 地址、跳转
favicon 抓取108抓 /favicon.ico 做系统指纹
从 JS bundle 找接口96SPA 的真实 API 藏在 bundle 里
提取 API 路径100/api/... 正则批量抠
证书 SAN52一张证书常绑多个域名

favicon 指纹是最巧的一招。agent 会自己写 Python 算 mmh3:


Tips — 为什么要用favicon 这个文件虽然小,但往往具有“出厂默认、长期不变、可被哈希”的特性。很多软件(如 CMS、网络设备管理界面、Web 服务器)会自带一个固定的 favicon.ico 文件,只要计算它的哈希值并与已知数据库比对,就能推测目标运行的是什么系统。


import hashlib, struct, base64, mmh3
data = open('well.ico','rb').read()
print("mmh3:", mmh3.hash(data))
b = base64.encodebytes(data) # 很多平台的 favicon hash 是对 base64 算的

为什么有用:同一套系统(同一套 OA、同一个厂商的网关)在不同域名下 favicon 完全相同,mmh3 hash 也相同。所以拿到一个 hash 就能反向关联「哪些域名跑的是同一套系统」,还能去 FOFA/Shodan 用这个 hash 找同类目标。

DB 里 assets.favicon_mmh3 字段就是存这个 —— 但ARTEX 自己不计算它,是 agent 算好填进去的,或者从 ScopeSentry 同步时拿过来(FaviconMMH3: it.Icon)。

JS bundle 提取是另一条主力。SPA 用 curl 只能拿到空壳 <div id="app">,真实接口全在 bundle 里:

Terminal window
grep -rhoaE '.{0,120}("/api[^"]+").{0,200}' OUTDIR/js/*.js | head -20

技能 api-recon 为此专门写了 harvest_static.py,并且明令禁止直接 Read 大 bundle:

100KB 的 index-*.js 禁止 Read/grep 进上下文;用 OUTDIR 脚本批处理

管道五:写入 + 异步补全(enrich)#

前四条管道产出的原始线索,通过 insert_assets 工具写进资产表:

// 一次可混合多种类型
"各类型必填字段:root_domain→domain;ip→ip;subdomain→domain;
app→app_name;service(HTTP)→url;service(非HTTP)→service_name+port;
endpoint→url+method"

写入时 task_id 不暴露给模型 —— 由程序经 SetTaskID 权威赋值。防止模型伪造归属。

写入后触发 enrich(SetEnrich(enr))异步补全:

// DNS 无门禁;HTTP 探测受 RoE 约束(§5.2)
func (e *Engine) doDNS(id int64, host string) {
data, _ := e.resolv.QueryMultiple(host) // dnsx 库
for _, ip := range data.A { UpsertIP(...) } // 建 IP 节点
for _, a := range data.A { UpsertSubdomain(...) } // 建 A 记录
for _, aaaa := range data.AAAA { ... } // AAAA
for _, cname := range data.CNAME { ... } // CNAME
}
func (e *Engine) doHTTP(id int64, rawURL string) {
resp, _ := e.client.Do(req) // UA: artex-enrich/1.0
body, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) // 硬上限 1 MiB
UpsertHTTPService(URL, StatusCode, ContentLength, PageTitle)
}

三个设计细节:

  • LimitReader 1 MiB:探测只读前 1MB,防止撞上大文件把内存打爆
  • 冷却窗口:同一 (kind, id) 在 cooldown 内不重复跑,防止 planner 反复唤醒导致重复探测
  • HTTP 探测受 RoE 门禁:DNS 解析无害所以无门禁,但 HTTP 请求会打到目标,必须受授权范围约束

enrich 只做补全,不做发现。它不会枚举子域,只对已经写进库的资产做解析和探测。发现靠前四条管道。


2. 数据模型:去重靠 DB,不靠代码#

6 种资产类型,每类都有唯一索引(db/schema.sql):

类型唯一索引含义
root_domainuq_av2_root_domain (domain)根域
ipuq_av2_ip (ip)IP
subdomainuq_av2_subdomain (domain, record_type)子域(按记录类型分)
appuq_av2_app_bundle (bundle_id) / uq_av2_app_name应用
serviceuq_av2_service_http (url) / uq_av2_service_other (domain,ip,port,service_name)服务
endpointuq_av2_endpoint (url, method)接口

靠唯一索引 + ON CONFLICT DO UPDATE 做幂等,不在 Go 代码里写”查一下存不存在”的逻辑。这样并发写也不会重复。

ASSETS 表有 21 个业务字段,几个值得注意的:

  • c_segment CIDR + GIST 索引 → 支持网段归属查询
  • open_ports JSONB[] → 一个 IP 的多个端口
  • bound_domains TEXT[] → IP 反查绑定的域名
  • technologies TEXT[] + GIN 索引 → 按技术栈搜资产
  • icp → 按备案号关联同主体资产

运行时实际数据:

type数量
endpoint160
subdomain116
ip54
service41
app15
root_domain5

3. 归属与范围:三套机制#

机制一:company_scope(企业资产范围)#

支持 5 种规则(db/company_scope.go):

kind匹配方式
domain注册域(用 publicsuffix 精确取 registrable domain)
ip单 IP(归一成 /32 或 /128)
cidr网段(inet_ops GIST 匹配)
icp备案号(去所有 Unicode 空白 + 折叠大小写,不做模糊匹配)
keyword关键词

ICP 这条最聪明:一个单位名下可能几十个域名,全都共用一个备案号。按备案号匹配,等于一次锁定该主体的所有资产,比一个个加域名省事得多。

company_source 字段区分两种归属来源:

  • explicit —— 人手动指定的企业
  • scope —— 由范围规则自动推导

这个区分很重要:重建 scope 时不能把历史手工关联抹掉,所以旧数据保守地全部标成 explicit。

机制二:task_scope(任务测试范围)#

task_scope 表 129 条记录,表示「这个任务允许测哪些资产」。它是覆盖度的分母:

  • add_task_scope 工具:planner 主动把整根域/子公司/子域/IP 纳入范围
  • list_untested_assets 工具:查「范围内还没测的资产」
  • worker 处理意图时,自动把意图锚定的资产加入 scope(AddAutoScope,ON CONFLICT DO NOTHING 保证幂等)

这形成了覆盖度的闭环:范围是分母,已测是分子,planner 看得到缺口,据此派新意图。

机制三:资产 DSL 查询#

db/asset_dsl.go 实现了一个类 Google 语法的查询语言:

domain=example.com AND status_code=200
technologies~=nginx OR page_title=后台

支持字段:domain / root_domain / ip / url / page_title / icp / service_name / app_name / bundle_id 等,运算符有 = == != > >= < <=,逻辑组合 AND / OR。

planner 用 list_assets 工具按条件检索整库,比如「所有 status_code=200 且 technologies 含 nginx 的站点」。


4. 实测缺口#

缺口一:IP 一个都没归属#

ip | 无归属 | 54 ← 全部

理由是 company_scope 只有 3 条规则,全是 domain,没有 cidr/ip 规则。于是:

  • 子域靠域名后缀匹配上了(116/116 归属)
  • IP 没有任何规则能匹配,全部悬空

影响:list_untested_assets 按企业维度统计覆盖度时,54 个 IP 不在任何企业的分母里 —— 端口扫描结果实际上没有进入覆盖度考核。

修法:给两家公司各加一条 cidr 规则(把已知 IP 段纳入),或者让 enrich 在建 IP 节点时,从 bound_domains 反推归属企业。

缺口二:root_domain 3/5 无归属#

5 个根域里 3 个没归属,同样是 scope 规则覆盖不全。

缺口三:资产”来源”信息不足#

assets 表没有”发现来源”字段(是 crt.sh 来的?JS 里抠的?还是 ScopeSentry 同步的?)。只有 task_ids 和 company_source。

影响:无法回答「哪条管道产出的资产质量最高」,也就无法优化 recon 策略。


5. 为什么这套方法能work#

5.1 用在线数据源替代本地工具#

subfinder 做的事ARTEX 的替代
聚合 30+ 被动源curl 打 crt.sh / ICP / hackertarget / OTX / urlscan
统一输出每源单独解析,落 subs_<源>.txt
内置去重sort -u + DB 唯一索引

优势:源可以随时换、随时加,不用等工具更新。某个源挂了,换一个就是。而且 agent 能根据返回内容做语义判断(“这个子域叫 test- 开头,可能是测试环境,优先打”),工具做不到。

5.2 用图驱动替代清单驱动#

subfinder 的输出是一份静态清单,扫完就完了。

ARTEX 的资产在图里,和 intent/fact/finding 连在一起。所以:

  • 发现一个新子域 → 可能触发 planner 派新意图
  • 某个意图打到一组接口 → insert_assets 记下来 → 覆盖度分母变大
  • 覆盖度有缺口 → list_untested_assets 提示 → planner 补测

资产不是终点,是探索空间的度量。

5.3 用 DB 约束替代去重代码#

7 个唯一索引 + ON CONFLICT DO UPDATE,让多条管道并发写同一个资产也不会重复。这比在每个管道里写”先查再插”可靠得多。

5.4 用异步补全替代批量探测#

enrich 是异步 worker 池,一个资产写完立即返回,解析和探测在后台做。

好处:worker 不用等 DNS 解析完才能继续;而且同一资产被多次写入时,冷却窗口能挡住重复探测。


总结#

  • 资产来源:外部平台同步(ScopeSentry)+ 在线数据源 curl(crt.sh/ICP/hackertarget)+ 主动 dig 枚举 + 从页面/JS/证书/favicon 里抠
  • 资产落点:Postgres 的 assets 表,6 类 + 21 字段 + 7 个唯一索引
  • 资产补全:enrich 异步跑 DNS(dnsx)和 HTTP(限 1MB)
  • 资产用途:作覆盖度分母,驱动 planner 下一步派什么意图

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Profile Image of the Author
LYH
LYH,安全方向的在校学生。方向集中在 Web 安全和密码学——Web 这边从 webshell 识别、文件包含到注入类题目都在练,密码学在啃 RSA 和格密码相关的内容,逆向和网络也有涉猎。日常工具链是 Mac + Docker。欢迎交流学习!
公告
欢迎来到我的博客!这里展示我的学习过程与结果。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
站点统计
文章
52
分类
20
标签
32
总字数
311,598
运行时长
0 天
最后活动
0 天前

文章目录