政府网站 API 逆向分析实战:噪声数据采集
一句话总结
从目标网站前端 JS 中提取硬编码的第三方 API 地址和认证凭据,直接调用外部数据平台接口获取结构化数据。
逆向流程
步骤 1:访问目标网站
页面正常返回 200,HTML 中包含多个 <script> 标签。
步骤 2:定位核心 JS 文件
在 HTML 中找到关键脚本引用:
html
<script src="../../images/zsjs*_sthjt_jkjs.js"></script>
步骤 3:下载并分析 JS
从目标网站下载对应的 JS 文件,发现是未混淆的明文代码。
关键发现 1:API 配置(硬编码)
javascript
var API_CONFIG = {
baseUrl: "https://*.jlzslw.com:50003", // 第三方数据平台
username: "InterfaceCall",
secretKey: "******", // 硬编码密钥
sysCode: "NOISE"
};
关键发现 2:3 个 API 接口
搜索 JS 中的 ajax / Get 关键词,找到 3 个接口路径:
| 接口路径 | 功能 |
|---|---|
/GetExternalApiToken |
获取 Token |
/GetBSDStationListAsync |
获取站点列表 |
/GetDATStationHourDisplayListAsync |
获取噪声小时数据 |
步骤 4:分析 getToken 函数
javascript
function getToken() {
$.ajax({
type: "GET",
url: API_CONFIG.baseUrl + "/api/noiseproduct/AirCityBaseCommon/GetExternalApiToken",
data: { UserName: API_CONFIG.username, SecretKey: API_CONFIG.secretKey },
dataType: "json",
success: function (response) {
if (response && response.result) {
RESULT_DATA.token = response.result;
关键点:
- 请求方式:
GET - 参数名大小写敏感:
UserName、SecretKey(首字母大写) - Token 位置:
response.result
步骤 5:分析站点列表接口
javascript
$.ajax({
type: "GET",
url: API_CONFIG.baseUrl + "/api/noiseproduct/AirCityProductBase/GetBSDStationListAsync",
headers: { SysCode: API_CONFIG.sysCode, Authorization: "Bearer " + RESULT_DATA.token },
关键点:
- 请求方式:
GET - 必须携带 Headers:
SysCode和Authorization: Bearer {token}
步骤 6:分析噪声数据接口
javascript
$.ajax({
type: "GET",
url: API_CONFIG.baseUrl + "/api/noiseproduct/airdata/DATStationHour/GetDATStationHourDisplayListAsync",
headers: { SysCode: API_CONFIG.sysCode, Authorization: "Bearer " + RESULT_DATA.token },
data: $.param({
codes: cityData.map(function (item) { return item.stationCode; }),
dataType: 0,
timePoint: dayjs().format("YYYY-MM-DD HH:00:00")
}, true),
关键点:
codes:站点编码列表dataType: 0:固定值timePoint:必需字段 ,格式YYYY-MM-DD HH:00:00
步骤 7:Python 验证
python
import requests
session = requests.Session()
session.verify = False
# 1. 获取 Token
token = session.get(
API_BASE + "/api/noiseproduct/AirCityBaseCommon/GetExternalApiToken",
params={"UserName": "InterfaceCall", "SecretKey": "******"}
).json()["result"]
# 2. 获取站点列表
stations = session.get(
API_BASE + "/api/noiseproduct/AirCityProductBase/GetBSDStationListAsync",
headers={"SysCode": "NOISE", "Authorization": f"Bearer {token}"}
).json()["result"]
# 3. 获取噪声数据
data = session.get(
API_BASE + "/api/noiseproduct/airdata/DATStationHour/GetDATStationHourDisplayListAsync",
headers={"SysCode": "NOISE", "Authorization": f"Bearer {token}"},
params={"codes": "1001A", "dataType": 0, "timePoint": "2026-05-22 12:00:00"}
).json()["result"]
数据流转图
─────────────────────────────────────────────────────────────┐
│ 1. GET /GetExternalApiToken │
│ → Token: "a24dbae..." │
─────────────────────────────────────────────────────────────┤
│ 2. GET /GetBSDStationListAsync │
│ Headers: SysCode=NOISE, Authorization=Bearer {token} │
│ → 154 个站点 [{stationCode:"1001A", ...}, ...] │
├─────────────────────────────────────────────────────────────┤
│ 3. GET /GetDATStationHourDisplayListAsync │
│ ?codes=1001A&dataType=0&timePoint=2026-05-22 12:00:00 │
│ → 噪声数据 [{code:"1001A", leq:"48.4", ...}] │
└─────────────────────────────────────────────────────────────
API 接口详情
1. 获取 Token
| 项目 | 值 |
|---|---|
| URL | https://*.jlzslw.com:50003/api/noiseproduct/AirCityBaseCommon/GetExternalApiToken |
| 方法 | GET |
| 参数 | UserName={硬编码用户名}&SecretKey={硬编码密钥} |
| 返回 | {"success":true, "result":"a24dbae..."} |
2. 获取站点列表
| 项目 | 值 |
|---|---|
| URL | https://*.jlzslw.com:50003/api/noiseproduct/AirCityProductBase/GetBSDStationListAsync |
| 方法 | GET |
| Headers | SysCode: NOISE Authorization: Bearer {token} |
| 返回 | {"result": [{id, positionName, stationCode, cityName, ...}]} |
3. 获取噪声数据
| 项目 | 值 |
|---|---|
| URL | https://*.jlzslw.com:50003/api/noiseproduct/airdata/DATStationHour/GetDATStationHourDisplayListAsync |
| 方法 | GET |
| Headers | 同上 |
| 参数 | codes={站点编码}&dataType=0&timePoint={YYYY-MM-DD HH:00:00} |
| 返回 | {"result": [{code, name, leq, l5, l10, ...}]} |
噪声数据字段说明
| 字段 | 说明 | 示例 |
|---|---|---|
code |
站点编码 | 1001A |
name |
站点名称 | 某某小区 |
timePoint |
监测时间 | 2026-05-22T12:00:00 |
leq |
等效声级 dB(A) | 48.4 |
l5 |
L5 | 52.9 |
l10 |
L10 | 51.4 |
l50 |
L50 | 46.1 |
l90 |
L90 | 42.3 |
lMax |
最大声级 | 64.1 |
temperature |
温度 ℃ | 25.4 |
humidity |
湿度 % | 43 |
windSpeed |
风速 m/s | 2.3 |
isOverStandard |
是否超标 | false |
与高难度站点的对比
| 对比项 | 高难度站点 | 本案站点 |
|---|---|---|
| 逆向难度 | 极难(VM 字节码混淆) | 简单(JS 明文) |
| Token 来源 | 动态生成(解密函数) | 固定凭据调用接口 |
| API 地址 | 自有系统 | 第三方数据平台 |
| 数据托管 | 自有 | 托管至第三方平台 |
认证凭据(已脱敏)
baseUrl: https://*.jlzslw.com:50003
username: InterfaceCall
secretKey: ******
sysCode: NOISE
注意:这些凭据硬编码在前端 JS 中,无需注册即可使用。
免责声明
本文仅用于技术学习和研究目的,展示的是如何通过前端逆向分析理解数据接口的工作原理。实际使用时请遵守相关法律法规和网站服务条款。