基于Nezha面板的僵尸网络工具分析

· 2026-11-03 09:03 · 4 阅读

原创 花十一一 2026-11-03 09:03 北京

摘要:一台托管于 Contabo 法兰克福节点的服务器,以 Python SimpleHTTPServer 方式对外开放了文件列表,页面攻击脚本可被任意下载。目录本身不是零散脚本的堆积,而是覆盖扫描、验真、利用、植入与远控等攻击工具,sqlite.db 中仍有会话写入,痕迹未及清理,说明这套武器库当时处在使用状态。

协议层的成熟度高于常见的脚本拼装。RDB 报文级投毒并自研 Redis Module、ECDSA 签名伪造、nsenter 容器逃逸三项均在隔离环境中复现成功,其中 Portainer 私钥可直接产出合法签名头,绕过原有身份校验。编排层则相反:13 处工程缺陷中,cron 表达式语义错误是最重的一处,47 台主机的每日持久化任务从未执行,五条入侵路径的自动化在此断链。

C2 由原版 Nezha 面板与官方 agent 组成,二进制与官方发布版本字节一致,因此基于文件哈希的检测规则全部失效。有效检测须下沉到配置路径 /tmp/nz.yml、进程名 /tmp/na、systemd unit 与外联域名。

综合能力结构、代码习惯与运营纪律,该体系更接近个人或小团队自建的武器库。

一、样本概况

样本托管于Contabo法兰克福节点(144.91.103.172:8000),Python SimpleHTTPServer默认目录页,无任何鉴权。61个文件按功能分为五类:

类别

数量

关键文件

Python漏洞利用脚本脚本

18

rogue.py

rce_drive.py

es13_rce.py

pin_batch.py

Shell调度脚本

9

scanline.sh

harvest_loop.sh

ct_guard.sh

Redis RCE模块

3

exp.so(Debian 12编译)

exp.so.bak(RHEL 8编译)

密钥

2对

Portainer ECDSA P-256私钥、SSH密钥对

C2面板数据库

1

sqlite.db,49台主机记录+2697条登录会话

这些脚本之间并非彼此孤立。scanline.sh 每 30 分钟拉起一轮收割循环,循环内调用 rce_drive.py 完成验真与利用;一旦拿到 RCE,rce_drive.py 自动把公钥写入目标机的 authorized_keys,同时把落地记录回写进 sqlite.db。pipeline2.py、pin_batch.py 等脚本共用同一份 masscan 输出池,sqlite.db 则由面板与收割脚本同时读写,既是受害者台账,也兼作运营审计底账。源码、私钥、台账三类文件一并明文挂在无鉴权目录,单从这种存放方式看,运营方更在意的是扩张速度。

    # scanline.sh
    #!/bin/bash
    # 产线1:持续 masscan 扫新段(每 2 小时一轮全段循环)
    # 5025 段 × 多端口 @300k pps
    P=/opt/harvest
    L=/tmp/scan_round.log
    echo "=== SCAN $(date -u +%m-%d\ %H:%M) ===" >> $L
    # 19 端口
    masscan -p6379,6380,6381,16379,26379,2379,8088,6066,27017,9200,8123,8086,11211,5672,8983,4222,15672 \
      --rate=300000 -iL /tmp/segs.txt --open-only --excludefile /tmp/excl.txt \
      -oL /tmp/scan_new.txt >> $L 2>&1
    echo "scan done: $(grep -c open /tmp/scan_new.txt) open" >> $L
    # 合并到主池(去重追加)
    cat /tmp/scan_new.txt | grep "^open" | sort -u >> /tmp/multi.txt 2>/dev/null
    sort -u /tmp/multi.txt > /tmp/multi_dedup.txt && mv /tmp/multi_dedup.txt /tmp/multi.txt
    echo "total pool: $(grep -c open /tmp/multi.txt)" >> $L

    二、五条入侵路径

    整套体系由 5 条独立入侵路径组成,按目标服务各建一条,共用同一个 masscan 扫描池(30 万 pps,17 个端口)和一套报告管道。每条路径都走同一个闭环:扫描,验真,利用,落地 agent,纳管面板。

    有利用脚本的(5 类)

    只收集、还没写利用脚本的(储备)

    Redis 6379/6380/6381/16379/26379、YARN 8088、Spark 6066、ES 9200、Portainer 9001(跳板机本地扫)

    etcd 2379、ClickHouse 8123、InfluxDB 8086、memcached 11211、RabbitMQ 5672/15672、Solr 8983、NATS 4222、MongoDB 27017

    右边那 12 个端口全是经典未授权目标——说明操作者在为扩大打击面做储备。Portainer 9001 不走主扫,而是 15 台跳板机各自本地 masscan 后 SSH 回传汇总。

    僵尸网络攻击链全景:五条产线从扫描到C2的完整路径

    如图masscan 先产出攻击目标,五条路径并行完成验真与利用,落地动作统一为 /tmp/na 加公钥注入,最终汇入一个原版哪吒面板,战报经 Telegram 播报。

    入侵路径

    端口

    手法

    技术要点

    Redis(主力)

    6379-6384

    主从复制投毒

    RDB报文级伪造+自研Module

    Elasticsearch

    9200

    MVEL脚本注入

    只对ES 1.x有效

    YARN

    8088

    假应用提交

    伪装成sysmon-update

    Spark

    6066

    假应用提交

    按响应JSON验真

    Portainer

    9001

    ECDSA签名伪造

    nsenter容器逃逸

    Redis 路径:两步验真与反蜜罐

    验真逻辑在pipeline2.py中实现,分两步:

      s.sendall(b"PING\r\n")
      s.sendall(b"CONFIG SET dir /tmp\r\n")

      第二步之所以必要,原因在于蜜罐通常只实现协议层应答:PING 与 CONFIG SET 都能回 +OK,而 CONFIG GET dir 不会返回数组格式,真实副作用无法伪造。存活池与可写池由此分离,后续的利用火力不会浪费在蜜罐上。

      投毒实现:RDB报文级载荷替换

      rogue.py在攻击机本地起rogue master,诱导受害Redis执行SLAVEOF拉取"RDB文件",实际传输的是Redis Module二进制exp.so。受害端MODULE LOAD后注册两条新命令:

        system.exec   // popen执行,有输出回显,用于验证与取数
        system.shell  // system()执行,仅返回退出码,用于隐蔽操作

        自研模块的动机并不复杂:现成工具通常只负责加载,加载之后便无法回读执行结果;自研版本一并提供有回显的 exec,RCE 之后可立即验证并取数。两个exp.so源码完全相同,分别以Debian 12和RHEL 8的GCC编译,对应不同glibc的受害环境,属于典型的预编译载荷矩阵。

        落地与反取证

        rce_drive.py共11步,最后两步将Redis的dbfilename和dir配置还原为原值。落地文件全部位于/tmp,agent为/tmp/na。事后仅审计Redis配置或日志,痕迹只剩一个/tmp/exp.so和一条MODULE LOAD。

        Portainer 路径:ECDSA 签名伪造

        五条路径中,这一条的技术复杂度最高。Portainer Agent与Server之间以ECDSA签名验证身份,签名内容为MD5("Portainer-App")。攻击者以本地私钥直接构造请求头:

          md5d = hashlib.md5(b'Portainer-App').digest()
          ds = priv.sign(md5d, ec.ECDSA(Prehashed(hashes.MD5())))

          验证通过后调用Portainer API定位特权容器,启动Privileged+挂载宿主根目录+共享PID命名空间的容器,随后nsenter -t 1进入宿主机PID 1的全部命名空间。经沙箱验证,目录中的ECDSA P-256私钥功能完整,可直接产出合法签名头。

          植入与回连:命令执行到C2心跳

          五条路径的利用手法各不相同,但漏洞利用之后的植入动作是标准化的。以 Redis 路径为例,链条分三级转换:未授权访问经CONFIG SET加SLAVEOF转为任意文件写,文件写配合MODULE LOAD转为受控命令执行,最后由rce_drive.py完成植入。落地是标准四件套:

          文件

          作用

          特点

          /tmp/exp.so

          Redis恶意模块

          一次性载荷,42行C源码,用完即弃

          /tmp/na

          agent本体(18.5MB官方nezha-agent)

          无扩展名非标准路径,规避关键词检测

          /tmp/nz.yml

          agent配置

          指向C2域名n.uau.pp.ua:443

          /etc/systemd/system/nezha-agent.service

          持久化

          Restart=always,进程被杀自动拉起

          agent本体并非现场生成,而是托管在攻击者自己的分发目录(即8000端口武器库),受害机获得命令执行后直接拉取落盘。武器库同时承担武器仓库与投递源两个角色,这也是整个仓库必须公网可访问的原因。

          植入细节有三处工程化设计:authorized_keys以grep -q ... || echo ...幂等写入,避免重复注入;systemd unit设置Restart=always实现进程级自愈;全部落地路径选/tmp,且RCE完成后还原Redis配置,只留最少痕迹。

          回连通道则完全伪装成合法监控流量:agent读取/tmp/nz.yml后,经gRPC over TLS连向标准443端口的n.uau.pp.ua,用硬编码的agent_secret注册入面板,自动生成UUID命名并在面板侧触发Telegram战报播报。从流量形态看,与一家公司用哪吒监控自己的服务器没有区别。

          回连侧信号比植入侧可靠,原因也在这里:agent 二进制是官方的,配置路径却不是——正常哪吒部署的配置位于标准安装目录,本案例却落在 /tmp/nz.yml、进程名为 /tmp/na。配合DNS+SNI关联封禁n.uau.pp.ua,整条回连链即可掐断,agent只会无限重连而永远收不到命令。

          三、战果统计:49台失陷

          阶段

          规模

          依据

          masscan扫描池

          73轮

          round_no.txt

          ES HTTP存活池

          4467台

          esline.sh

          五条路径攻击目标总数

          至少5000+

          各脚本产物

          失陷主机

          49台

          sqlite.db servers表

          受害机横跨 10+国家、涵盖阿里云/华为云/腾讯云/OCI/Contabo/Hetzner/OVH/Vultr/DO/Linode全部主流VPS——结合5025段的规模和300kpps的速率(全轮巡一遍需数天),扫描面是面向公网的广域扫描(大概率按云厂商/AS网段组织的清单),不是针对特定行业的定点打击。sqlite.db中有一台腾讯云失陷主机的备注原文完整记录了攻击链与验证方式。

          id

          224

          name

          腾讯云·中国·公网·81.70.x.x·4核4G·ubuntu

          note

          81.70.x.x·Redis 6379未授权→CONFIG SET→authorized_keys注入·2026-10-27拿下·system-audit共享钥直连root

          uuid

          502e40d0-8a46-46d3-ba98-00b606196e3a

          created_at

          2026-10-27 00:40:57(+08:00)

          updated_at

          2026-10-29 13:02:09(+08:00)

          user_id

          1(admin)

          四、C2:原版哪吒面板

          整个C2由原版哪吒(Nezha)监控面板v2.4.x与官方agent构成。将攻击者使用的agent与官方发布件逐项比对,结果如下:

          对比项

          攻击者的

          官方发布件

          nezha-agent大小

          18571426字节

          18571426字节

          SHA256

          130f212a...ab6

          130f212a...ab6

          特征字符串扫描

          0命中

          无

          两者大小一致,SHA256一致,特征串扫描零命中。我们最初怀疑样本被改过,比对后确认没有。恶意性并不来自二进制篡改,而是来自配置与编排行为。

          基于agent二进制哈希的检测规则,在本案例里基本抓不到东西。有效检测点在于配置文件路径(/tmp/nz.yml、/etc/nezha/config.yml指向异常域名)、新增systemd unit nezha-agent.service、以及外联域名。

          五、持久化缺陷与画像

          面板crons表中有3条远程下发的后门任务:

          ID

          功能

          调度表达式

          覆盖

          10

          keepalive,自愈公钥

          */5 * * * *(正确)

          2台

          11

          强制改回C2域名

          0 0 0 1 1 *(写错)

          47台

          12

          每日重植SSH公钥

          0 0 0 1 1 *(写错)

          47台

          0 0 0 1 1 *为6字段cron,第5位是月、第6位是星期,1 1即每年1月1日,并非攻击者预期的每日01:01。这两条"每日自愈"后门从未自动执行。

          实际生效的仅cron#10(每5分钟自愈公钥并放行22端口),但只覆盖2台。

          基于以上事实,处置顺序应为:先断C2(域名、IP、面板),再清理主机。cron后门虽失效,agent仍在实时上报,先掐线才能避免清理操作被攻击者实时观察。需要说明的是,我们无法确认攻击者是否在其他机器上另置了持久化方式,因此断链应先行。

          最让人意外的是conntrack自保:扫描进程一旦发现连接跟踪表占用超过40%,即向自身发送SIGSTOP暂停扫描。其余几项能力也值得记录:RDB报文级伪造并在EOF标记上做随机化以对抗检测,RCE完成后将Redis配置还原为原值的反取证操作,以及五条路径并行运行的增量收割模式。

          工程缺陷共13处,择要:

          缺陷

          后果

          cron表达式语义错误

          47台的每日后门从未生效

          X.java的uuid占位符未替换

          同一jar落地机器UUID全部相同

          esline.sh调用不存在的es_batch.py

          ES批量路径断链

          realtime.py指向已迁移的旧节点

          实时收割路径悬空

          auto_namer.py备份版参数不匹配

          实测抛TypeError

          面板jwt_secret、口令、Bot Token全明文随库

          整条链的凭据体系被完整缴获

          归因链:攻击者署名 YuUu

          用泄露的Bot Token调用Telegram只读接口getMe与getChat(仅查询身份,不读取消息),结果:收割战报Bot为"挖呀挖"(@wyw_yuu_bot),存活告警Bot为"探针"(@tz_yuu_bot),两者的共同接收端chat 7654417798是一个私人账号,first_name为YuUu。

          面板口令Yuu@N****2026中的Yuu、两个Bot用户名的_yuu_bot后缀、C2域名uau.pp.ua中的uau,加上Telegram账号的first_name,四处线索彼此独立,指向却一致。

          综合判断,该行为体属于个人或小团队运营的僵尸网络。

          七、检测与处置建议

          受害主机侧排查命令:

            ls -la /tmp/exp.so /tmp/na /tmp/nz.yml /etc/nezha/config.yml
            grep -l "INkbR1rkhU4" /root/.ssh/authorized_keys
            systemctl status nezha-agent.service
            grep -E "SLAVEOF|MODULE LOAD" /var/log/redis/*.log

            基础设施侧,出站封禁面板域jk.uau.pp.ua与agent上报域n.uau.pp.ua。Redis加固:6379-6384加requirepass、rename-command CONFIG、禁公网。

            Portainer加固:更换默认agent签名密钥,本案例中的私钥经实测真实可用。

            目录里那对SSH私钥属于攻击者自己的OCI主机,用于登录其自有基础设施;注入受害机的是system-audit公钥,对应的私钥并不在目录内。清理时只需要删掉authorized_keys里那把公钥。轮换主机自身的SSH密钥在这里没有意义,因为攻击者手里已经有一把可用的私钥。

            跳转微信打开

            本文转载自微信公众号「huasec」,查看原文。版权归原作者所有,如有侵权请联系删除。