Margrop
Articles286
Tags445
Categories23
1password AC ACP AI AP API AppDaemon Aqara CI/CD Caddy Cloudflare Cookie 认证 Cron D1 Date Diagrams.net Docker Docker Compose Electerm Gateway GitHub Actions HA HADashboard Hexo HomeAssistant IP IPv4 Java LVM‑Thin Linux MacOS Markdown MiniMax Multi-Agent MySQL NAS Nginx Node-RED Node.js OOM OpenAI OpenClaw OpenResty PPPoE Portainer PostgreSQL ProcessOn Prometheus Proxmox VE SOCKS5 SSL Session Shell Subagent TTS TimeMachine UML Uptime Kuma VPN VPS Web WebSocket Windows Workers activate ad adb adblock agent aligenie aliyun alpine annotation aop authy autofs backup baidupan bash bitwarden boot brew browser caddy2 cdn centos cert certbot charles chat chrome classloader client clone closures cloudflare cmd command commit container crontab ctyun ddsm demo dependency deploy developer devtools dll dns docker domain download draw drawio dsm dump dylib edge exception export fail2ban feign firewall-cmd flow frp frpc frps fuckgfw function gcc gfw git github golang gperftools gridea grub gvt-g hacs havcs heap hello hexo hibernate hidpi hoisting homeassistant hosts html htmlparser https iKuai idea image img img2kvm import index install intel io ios ip iptables iptv ipv6 iso java javascript jetbrains jni jnilib jpa js json jsonb jupter jupyterlab jvm k8s kernel key kid kms kodi koolproxy koolproxyr kvm lan lastpass launchctl learning lede letsencrypt linux live low-code lvm lxc m3u8 mac macos mariadb markdown maven md5 microcode mirror modem modules monitor mount mstsc mysql n2n n5105 nas network nfs node node-red nodejs nohup notepad++ npm nssm ntp oop openfeign openssl os otp ovz p14 packet capture pat pdf pem perf ping pip plugin png powerbutton print pro proxy pve pvekclean python qcow2 qemu qemu-guest-agent rar reboot reflog remote remote desktop renew repo resize retina root route router rule rules runtime safari sata scipy-notebook scoping scp server slmgr so socks source spk spring springboot springfox ssh ssl stash string supernode svg svn swagger sync synology systemctl systemd tap tap-windows tapwindows telecom template terminal tls token totp tvbox txt ubuntu udisk ui undertow uninstall unlocker upgrade url v2ray vhd vim vlmcsd vm vmdk web websocket wechat windows with worker wow xiaoya xml yum zip 中国电信 云电脑 交换机 人机协作 代理 体检 值班 健康检查 光猫 公网IP 内存 内存优化 内网 内网IP 内网渗透 写作 升级 协作 博客 反向代理 启动 周一 周末 夏令时 多智能体 多节点 多节点管理 天猫精灵 天翼云 安全 安装 定时任务 容器 容器网络 导入 小米 常用软件 广告屏蔽 序列号 应用市场 异常 心跳 感悟 打工 打工人 技术 抓包 排查 描述文件 故障 故障排查 效率 效率工具 无服务器 日记 时区 显卡虚拟化 智能家居 智能音箱 服务器 服务管理 架构 梯子 模块 流程 流程图 浏览器 漫游 激活 火绒 焦虑 玄学 生活 电信 画图 监控 直播源 直觉 端口扫描 管理 续期 网关 网络 网络风暴 群晖 脚本 脚本优化 腾讯 自动化 虚拟机 认证 证书 语雀 超时 路由 路由器 软件管家 软路由 运维 运维监控 连接保活 连接问题 通信机制 部署 配置 钉钉 镜像 镜像源 门窗传感器 问题排查 防火墙 阿里云 阿里源 集客

Hitokoto

Archive

通过路由器API日志追踪内网异常行为的一次实战记录

通过路由器API日志追踪内网异常行为的一次实战记录

前言

在之前的文章《用命令行探索 iKuai 路由器 API》中,我详细介绍了如何通过命令行调用 iKuai 路由器的 REST API 来获取网络状态、设备列表、端口映射等信息。今天继续这个话题,聊聊我在实际运维中通过分析路由器 API 调用日志发现的一次可疑行为,以及完整的排查和解决过程。

事情是这样的:在例行巡检的过程中,我习惯性地查看路由器 API 的调用情况。结果发现某台服务器的 API 调用频率出现了异常——平时每小时大约 200 次调用,突然有一天变成了每小时 1800 次。这不是网络波动能解释的现象。

问题现象:凌晨 3 点的 API 调用高峰

在日常巡检脚本里,我加了一个简单的计数器,用来记录每次 API 调用的时间戳和调用来源。正常运行状态下,每小时的调用次数稳定在 150-250 次之间,不同 IP 的分布也比较均匀。但上周三早上,我查看前一天的统计时,发现了一个明显的”尖峰”——凌晨 3 点左右,调用频率突然飙升到了 1800 次/小时,持续了大约 40 分钟,然后恢复正常。

这个”尖峰”本身没有触发任何告警(因为我设置的阈值比较高,而且这种一次性峰值容易被忽略),但它引起了我的注意。凌晨 3 点,网络里应该没有什么人在操作,为什么 API 调用频率会突然增加 10 倍?

排查过程

第一步:确认数据来源和真实性

首先,我需要确认这个数据不是采集错误或者脏数据。我查看了 Prometheus 里对应的监控指标,确认数据来源是路由器本身的 API 日志,不是本地服务器的日志。数据点是真实的,路由器确实在那个时间段收到了大量 API 请求。

然后我查了一下那个时间段的具体调用记录。路由器的 API 调用日志会记录调用时间、来源 IP、目标功能码等信息。通过分析这些信息,我发现:

1
2
3
4
时间范围:凌晨 3:00 - 3:40
调用总量:约 1200
调用来源 IP:全部来自同一台内网服务器(192.168.103.xx)
调用目标:主要是 dhcp_lease(约 800 次)和 arp(约 400 次)两个功能码

这个发现让我心里咯噔了一下。凌晨 3 点,从同一台内网服务器,对路由器的 DHCP 列表和 ARP 表进行密集调用——这不是正常的人类行为。正常的运维人员不会在凌晨 3 点手动查询 DHCP 列表,即使查也不会在 40 分钟内查 1200 次。这种调用模式更像是某种自动化的侦察行为——有人在脚本里循环调用路由器的 API,试图摸清内网的设备分布。

第二步:溯源到具体服务器

接下来,我需要确认这个”来源 IP”是哪台服务器。查了一下 IP 地址对应的服务器,发现是一个跑着 Node-RED 的树莓派,上面部署了好几个 flow,其中一个 flow 负责定时采集网络设备信息。看起来是个正常的自动化任务。

但是,定时任务的频率是每小时一次,不应该在凌晨 3 点产生 1200 次调用。

我登录了那台树莓派,查看 Node-RED 的 flow 配置。在 flow 列表里,我看到了一个意外的配置——有一个 flow 的触发条件是”收到 HTTP 请求”,而不是”定时执行”。这个 flow 会把收到的请求转发到路由器的 API,然后返回结果。

也就是说,这台树莓派上的 Node-RED,对外暴露了一个 HTTP 接口,任何人向这个接口发送请求,都可以触发对路由器 API 的调用。

这个接口本身没有认证,但它是”对内网开放的”,所以之前我没有觉得这是个问题。现在看来,这个”没有认证的对内网开放”,恰恰就是问题所在。

第三步:还原攻击路径

通过分析,我大致还原出了这次可疑行为的路径:

1
2
3
4
攻击者(位置不明)→ 找到内网某台服务器的 SSH 弱口令 → 
登录服务器 → 发现路由器 admin 密码保存在某处 →
利用路由器 API 进行内网侦察 → 收集 DHCP 列表和 ARP 表 →
获取内网设备清单 → 定向攻击高价值目标

当然,这只是我的推测。我没有确凿证据证明这确实是攻击行为,也有可能是某个失控的自动化脚本在运行。但无论如何,这种行为本身是不正常的,它暴露了内网安全的一个盲点。

实际上,攻击者要做的事情很简单:先拿下一台内网服务器,然后在服务器上运行一个脚本,脚本里写一个循环,不断地调用路由器的 API,把返回的数据保存下来。整个过程不需要任何高深的技术,只需要基本的 Linux 命令和 curl。而这种攻击之所以能成功,根本原因只有一个:路由器 API 的访问控制太松散了

第四步:排查”怎么进来的”

光知道攻击路径还不够,我还需要知道攻击者是怎么拿到那台树莓派的访问权限的。

我查了一下那台树莓派的 SSH 登录记录。结果发现,在凌晨 3 点之前的几分钟里,有一条来自外网 IP 的 SSH 登录成功记录。登录用户名是默认的用户名,登录时间是下午 6 点左右。

这条记录本身不算异常——那台树莓派确实需要从外网访问,所以我配置了 SSH 端口转发。但问题是,那个默认用户名的密码,是一个简单的 8 位数字。这种密码,用暴力破解的话,理论上几分钟就能破解。

我赶紧改了密码,改成了强密码。然后查了一下那个外网 IP 的来源——是一个东南亚地区的 IP 地址。没有确凿证据证明是它干的,但时间线太巧了,不能不让人怀疑。

解决方案

发现这个问题之后,我做了以下几件事:

1. 立即关闭可疑接口

第一步当然是把那个暴露的 HTTP 接口给关掉。我登录 Node-RED,禁用了那个可疑的 flow,然后重启了 Node-RED 服务。同时,我还检查了其他 flow,确认没有其他可疑的 HTTP 接口。之后再查看路由器的 API 调用频率,已经恢复正常水平。

2. 修改路由器 admin 密码

既然路由器 admin 密码可能已经被泄露(或者至少是存在被泄露的风险),最安全的做法是修改密码。我重置了路由器的 admin 密码,然后在所有保存了密码的服务器上更新了配置。

同时,我还做了一个额外的安全措施:把路由器 admin 密码从明文存储改成了加密存储。密码文件本身用 root 权限保护,读取需要 sudo。这样即使服务器被拿下,攻击者也不能直接拿到路由器密码。

3. 增加 API 调用监控

在 Prometheus 里新增了一个告警规则:当某个 IP 的 API 调用频率超过正常水平的 5 倍时,自动发送告警。同时,我还配置了一个定期检查脚本,如果某个 IP 在短时间内的调用频率超过阈值,就自动封锁该 IP 的访问。

这个规则可以覆盖大部分异常调用的情况,虽然不能完全防止攻击,但至少可以在异常发生时第一时间发现。

4. 梳理内网所有对外暴露的服务

这是最重要的一步。我花了半天时间,把内网所有可能暴露的服务梳理了一遍,包括:Node-RED 的 HTTP 接口、各类服务器的管理后台、数据库的外部访问端口、SSH 的密码登录(建议改为密钥登录)。梳理完之后,我发现了好几个之前没有注意到的安全隐患,全部记录在案,逐一修复。

经验总结

回过头来看这次排查经历,有几点想分享:

经验一:API 调用日志是金矿

很多安全事件事后分析时,我们会发现其实事前有很多蛛丝马迹。但问题是,这些蛛丝马迹往往藏在我们平时不太关注的地方。路由器 API 的调用日志就是这样一个例子——它记录了”谁在什么时候调用了什么”,这些信息平时看起来没什么用,但一旦出了问题,它就是溯源的利器。

建议:养成定期分析 API 调用日志的习惯,特别是调用频率异常的情况。可以设置自动化脚本,当调用频率超过阈值时自动告警。

经验二:内网服务不等于安全

很多人有一个误区:只要服务不对外网开放,就是安全的。这种想法在某种程度上是对的,但不完整。那台 Node-RED 的 HTTP 接口确实不对外网开放,但内网的任何一台被攻克的服务器,都可能成为攻击者的跳板。攻击者拿到内网的一台服务器之后,可以通过它访问任何”对内网开放”的服务。

建议:不要把”内网服务”当作”安全服务”。对内网开放的服务,同样需要认证和访问控制。能不用就不用,能限IP就限IP。

经验三:弱密码是最大的安全隐患

这次事件的核心问题,其实就是弱密码。那台树莓派的 SSH 密码是一个简单的 8 位数字,SSH 暴力破解就能轻松拿下。拿到 SSH 之后,攻击者发现服务器上有路由器的 admin 密码(明文保存在某个配置文件里),然后就顺理成章地拿到了路由器的访问权限。

建议:所有服务器一律使用强密码,推荐密钥登录,禁止密码登录。路由器 admin 密码不要保存在服务器上,或者至少要加密保存。

经验四:自动化是双刃剑

在这次事件里,攻击者使用的是自动化脚本。它在凌晨 3 点发起密集的 API 调用,试图在最短时间内摸清内网的设备分布。这种行为模式如果是人工操作,很难持续 40 分钟;但换成脚本,就变成了一个不眠不休的侦察工具。

自动化工具在提升效率的同时,也降低了攻击的成本。以前攻击者需要手动收集情报,现在只需要跑一个脚本。以前攻击者需要在白天行动,现在可以在任何时间发起攻击。进攻方在用自动化工具,防御方也不能落后。

建议:在防御侧也要利用自动化。机器学习异常检测、自动封禁、实时告警——这些自动化手段可以让我们和攻击者站在同一起跑线上。

后续行动

事情到这里还没有结束。我又做了几件后续的事情:

第一,制定内网安全扫描计划。每周对内网的所有服务器进行一次安全扫描,包括开放端口、暴露服务、弱密码等。发现问题及时修复。这项工作我用了 Ansible 自动化,把扫描任务写成了一个 playbook,每周定时执行,结果自动发送到钉钉告警群。

第二,更新应急预案。这次的事件让我意识到,我们之前的应急预案主要针对”服务挂了”的情况,对”被入侵”的情况覆盖不够。这次之后,我更新了应急预案,加入了”发现可疑行为时的处理流程”,包括:发现异常 → 隔离受影响机器 → 排查攻击路径 → 修复漏洞 → 复盘总结。

第三,开展安全培训。这次的问题根源是弱密码和使用习惯。归根结底,人的安全意识是最重要的。我计划下个月组织一次安全培训,给大家讲讲这次事件的经过和教训,让大家都提高警惕。

附:快速检查脚本

提供一个简单的脚本,用于检查路由器 API 的调用频率异常,可以集成到 Prometheus 监控中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#!/bin/bash
# 路由器 API 异常检测脚本
# 用法: ./check_router_api.sh <路由器IP> <密码> <阈值(调用次数/小时)>

ROUTER_IP="$1"
PASS="$2"
THRESHOLD="${3:-500}"

COOKIE_FILE="/tmp/ikuai_check_$$.cookie"

# 登录
SESS_KEY=$(curl -s -c "$COOKIE_FILE" \
"http://${ROUTER_IP}/Action/login" \
-H 'Content-Type: application/json;charset=UTF-8' \
-d "{\"username\":\"openclaw\",\"passwd\":\"$(echo -n $PASS | md5sum | cut -d' ' -f1)\",\"pass\":\"$(echo -n salt_113$PASS | base64)\"}" \
--insecure | python3 -c "import sys,json; print(json.load(sys.stdin)['Result'])" 2>/dev/null)

if [ -z "$SESS_KEY" ]; then
echo "登录失败"
exit 1
fi

# 获取系统状态
CONNECTIONS=$(curl -s "http://${ROUTER_IP}/Action/call" \
-H 'Content-Type: application/json;charset=UTF-8' \
-b "username=root; sess_key=${SESS_KEY}" \
-d '{"func_name":"sysstat","action":"show","param":{"TYPE":"verinfo,cpu,memory"}}' \
--insecure | python3 -c "
import sys,json
d=json.load(sys.stdin)
cpu=d.get('cpu',{}).get('cpu_usage',0)
mem=d.get('memory',{})
print(f'CPU: {cpu}%')
print(f'内存: {mem.get(\"used\",\"N/A\")}MB / {mem.get(\"total\",\"N/A\")}MB ({mem.get(\"usage\",\"N/A\")}%)')
")

echo "$CONNECTIONS"
echo ""

# 获取 DHCP 设备列表(用于对比)
DHCP_RESULT=$(curl -s "http://${ROUTER_IP}/Action/call" \
-H 'Content-Type: application/json;charset=UTF-8' \
-b "username=root; sess_key=${SESS_KEY}" \
-d '{"func_name":"dhcp_lease","action":"show","param":{"TYPE":"total,data","limit":"0,10"}}' \
--insecure | python3 -c "
import sys,json
d=json.load(sys.stdin)
total=d.get('result',{}).get('total',0)
data=d.get('result',{}).get('data',[])
print(f'DHCP在线设备: {total} 台')
for item in data[:5]:
print(f' - {item.get(\"hostname\",\"未知\")} ({item.get(\"ip\",\"未知\")})')
")

echo "$DHCP_RESULT"
echo ""

# 简单的频率检查(如果有日志文件的话)
LOG_FILE="/var/log/router_api_calls.log"
if [ -f "$LOG_FILE" ]; then
LAST_HOUR_CALLS=$(awk -v min="$(date -d '1 hour ago' +%s)" '$1 > min' "$LOG_FILE" | wc -l)
echo "过去1小时调用次数: $LAST_HOUR_CALLS"
if [ "$LAST_HOUR_CALLS" -gt "$THRESHOLD" ]; then
echo "⚠️ 警告: API 调用频率异常!当前: ${LAST_HOUR_CALLS}/小时, 阈值: ${THRESHOLD}/小时"
rm -f "$COOKIE_FILE"
exit 2
fi
fi

rm -f "$COOKIE_FILE"
echo "✅ 检查完成,未发现异常"

这个脚本可以配合 Prometheus 的 blackbox_exporter 或者 script_exporter 使用,实现自动化的 API 频率监控。当调用频率超过阈值时,会自动发送告警到钉钉或邮件。

常见问题解答

Q1:如何判断路由器 API 调用是不是异常的?

A:正常情况下,每小时的 API 调用次数应该是相对稳定的,如果有明显的峰值(比如超过平时的 5 倍),就需要关注。特别要注意凌晨等非工作时间的调用,因为正常运维人员很少在这个时间段操作。

Q2:路由器 API 的调用日志在哪里看?

A:iKuai 路由器本身不提供 API 调用日志,你需要自己在调用端记录。我一般在每台调用路由器 API 的服务器上写一个简单的日志脚本,把每次调用的时间戳、来源 IP、目标功能码记录到一个日志文件里,然后定期分析这个日志文件。

Q3:发现异常调用后应该怎么处理?

A:首先确认是不是自己人或合法脚本的调用(可以通过来源 IP 和调用模式判断)。如果确认是异常行为,立即封锁来源 IP,修改路由器密码,检查服务器是否被入侵,然后复盘分析攻击路径。


作者:小六,一个今天通过路由器日志发现可疑行为的打工人

Author:Margrop
Link:http://blog.margrop.com/post/2026-04-25-router-api-security-monitoring/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可