逆向真实字段,批量填写 Web 后台表单
核心原则(每条都对应一次真实翻车)
1. 接口返回 code:200 不等于写进去了。
一次写多个对象时,必须每个对象单独回查。实测遇到过:五个对象一把写,
返回 200 很漂亮,回查发现其中两个仍是 null。
2. 枚举值一律从前端代码抽字典,不要按字面猜。
实测:cwZyTransType 的字典是 {1:"网络医院", 2:"其它医疗机构"} —— 与直觉完全相反。
3. 同一个表单的字段可能分属不同接口。
先用一个字段做写入探针,确认每个对象归哪个接口,再批量写。
4. 探测用假主键,绝不拿真实数据当试验品。
用 visitId="TEST_PROBE_001" 这类不存在的键试写,回查验证后再动真实数据。
测试完记得还原被你改动的真实字段。
5. 先查已有数据,再决定填什么。
"这个患者没填过"和"这个患者填了一半"是两回事。空壳记录要全填,有值记录只能补空缺,
否则会覆盖掉别人已有的手环号之类。
与 site-clone-static 的区别
两者都会逆向 Web 后台,但目的相反:
smart-form-filler(本技能) |
site-clone-static |
|
|---|---|---|
| 目标 | 往真实系统写数据 | 复刻出本地副本 |
| 产出 | 数据落库 | 离线可跑的静态站点 |
| 写入 | 会真的改生产/业务数据 | 只写本地 mock |
| 风险 | 高,必须先备份 + 回查 | 低,不碰原站 |
| 鉴权 | 常需要账号密码 | 需要账号密码才能抓数据 |
判断依据:要录数据 → 本技能;要做离线副本 → 那个。
五步流程
Step 1 — 探活与入口定位
curl -s -o /dev/null -w "HTTP:%{http_code} SIZE:%{size_download}\n" "<入口URL>"
拿到 HTML 后确认:框架(Vue/React)、路由 hash 模式、静态资源真实路径。
坑:HTML 里的相对路径可能与实际目录差一层
(如写 ./static/app.js 实际在 /static/static/app.js)。以 404 为准逐层试。
Step 2 — 扒构建产物
下载入口 HTML 里引用的全部 js(含异步 chunk)。异步 chunk 的映射表在入口的
webpack runtime 里,形如:
{"chunk-136a6f32":"bdf4adf2", ...}
// → static/js/chunk-136a6f32.bdf4adf2.js
先试 xxx.js.map 能不能下(200 说明部署了 source map,能拿到原始源码,含注释与
业务键名;404 就只能按压缩代码逆向)。
定位目标页面:从路由表找到 hash 路由 → 对应 chunk → 组件模块 ID。
Step 3 — 量出表单(核心)
用 scripts/extract-form.js 从目标 chunk 批量抽取:
node scripts/extract-form.js <chunk.js> [对象名]
它会输出:字段名、对应 prop、中文标签、枚举字典、字段归属前缀。
必须量的四样东西:
| 量什么 | 怎么量 | 为什么 |
|---|---|---|
| 字段名 | 扫 form.<对象>.<字段> |
拼错一个就静默丢数据 |
| 中文标签 | 扫 label:"..." 配 prop |
核对字段含义,防张冠李戴 |
| 枚举字典 | 扫 XXX:[{label,value}] |
猜必错 |
| 校验规则 | 扫 rules:{字段:[{required...}]} |
知道哪些必填 |
四种枚举来源都要查:
1. data 里的字典数组 {label,value}
2. 硬编码 el-radio({attrs:{label:"1"}},[文本])—— 最容易被漏掉
3. 枚举映射表 {id:"xxx"} 形式
4. 后端下发的字典接口
Step 4 — 摸清写入链路
用假主键做写入探针,一次只试一个对象:
# 先查已存数据(很多系统读接口免鉴权)
POST <读接口> {"<主键>":"TEST_PROBE_001"}
# 再试写
POST <写接口> {"<主键>":"TEST_PROBE_001","<对象>":{"字段":"值"}}
# 回查确认
POST <读接口> {"<主键>":"TEST_PROBE_001"}
三个必须分清的概念:
| 返回码 | 含义 | 别误读成 |
|---|---|---|
200 |
调用成功 | 数据写对了 |
202 参数有误 |
参数校验失败(在鉴权之前) | 登录失效 |
300 登录失效 |
真的需要鉴权 | 参数问题 |
202 经常被误读成"要登录",其实只是 body 不全。带完整合法 body 再测一次。
Step 5 — 批量写入 + 回查
用 scripts/fill-batch.js,它内置四类校验:
- 枚举合法性 —— 值必须在字典内
- 字段联动 —— 如"有=1 则某时间字段必填"
- 时间链一致性 —— 见下
- 逐字段回查 —— 写完立刻读,逐字段比对
时间链校验(医疗/工单类表单必做)
这类表单常有几十个时间点,必须校验单调性:
事件A ≤ 事件B ≤ 事件C
例:发病 ≤ 首份检查 ≤ 检查报告 ≤ 开始治疗 ≤ 结束治疗 ≤ 复查
任何一项倒序直接拦下。 造模拟数据时尤其重要 —— 手填的时间几乎必然出现倒序。
校验时注意 空值归一化:
const norm = v => (v === null || v === undefined || v === '' ? '' : String(v));
// 库里 null 与发 "" 语义相同,String(null)="null" ≠ String("")="" 会误报
鉴权摸底清单
同一系统的接口鉴权经常不一致,逐个测:
# 空 body 探测(注意 202 的含义)
curl -X POST -H "Content-Type: application/json" -d '{}' "<接口>"
常见形态:
| 形态 | 特征 |
|---|---|
| 免鉴权 | 直接 200 返回业务数据 |
| Cookie 会话 | 登录响应带 Set-Cookie |
| 固定请求头 | 前端 axios 拦截器里写死的值 |
| 动态 token | 登录后从响应里取,需持久化 |
找固定请求头:在前端代码里搜 interceptors.request.use,
看它往 header 塞了什么。常见是 X-Token、Authorization。
不要把固定请求头当 token —— 有的系统前端就写死一个字符串,不是服务端下发的。
安全与可逆性
动手前必须做的:
- 拉一份当前数据做备份,存成带时间戳的 json
- 确认哪些字段已有值 —— 已有的一律照抄,不要用空值覆盖
(实测:统一写sn:''会抹掉患者原有手环号) - 用假主键试写,确认字段编码正确再动真实数据
改过的真实字段要还原。探针测试若改到了真实数据,立即改回并回查确认。
脏数据清不掉的情况:很多系统只有 add / update,没有 delete,
且 deleteFlag 字段后端不接收。此时只能从数据库层面清理 ——
测试前就想清楚能不能造脏数据。
造模拟数据的规矩
| 项 | 规则 |
|---|---|
| 主键类(身份证/统一社会信用代码) | 必须过校验位算法,否则批量写脏数据 |
| 派生字段(年龄) | 必须与日期字段自洽 |
| 枚举字段 | 只能取字典内的值 |
| 日期时间 | 同一条链上必须单调递增 |
| 手机/电话 | 与地区字段一致(别写北京号配福建地址) |
校验位算法(GB 11643 身份证):
权重 [7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2],
校验码表 ['1','0','X','9','8','7','6','5','4','3','2']。
第一版编的号常常全错,必须写脚本自检。
常见坑速查
| 现象 | 真因 |
|---|---|
| 传姓名查不到数据 | 接口只认主键,其他字段被静默忽略并返回空壳 |
| 返回 200 但数据没进去 | 该对象归别的接口;或字段名拼错被忽略 |
202 参数有误 |
参数不全(不是登录问题) |
| 字典里有 id 字段但下拉不显示 | 模板里 :value="e.value" 没用 id |
| 保存后组件数据还在 | 该组件在 destroyed 时才提交,切页签才存 |
| 数值回查多了几位小数 | DB numeric 精度损失,非错误 |
| 401/403 | token 过期或权限不足 |
脚本
| 文件 | 用途 |
|---|---|
scripts/extract-form.js |
从 chunk 抽字段/标签/字典/校验 |
scripts/fill-batch.js |
带四类校验的批量写入与回查模板 |
references/probing-playbook.md |
探针测试与鉴权摸底详解 |
两个脚本都不含任何目标站点信息,属通用工具,改 BASE_URL 和数据文件即可复用。