Insomni’hack finals – smtpwn writeup

This challenge was solved by several teams during the contest, however it seems that most teams didn’t have the intended solution, so here it is 😉
The source, binary and exploit for this challenge can be found on our github here!
smtpwn was a very simple local SMTP service. Basically you write a message to its stdin, and it’ll write a file to /tmp/ with the following content:

Message-ID: <urandom_hex_string>rn
From: "fromuser" <fromuser@hostname>rn
To: "touser" <touser@hostname>rn
Date: current_datetimern
X-Flag: the_flagrn
Subject: Read this message to get the flag!rn
rnuser_inputrn.rn

Where you control or known every variable except the urandom_hex_string used as a message-id, and the flag.
Now the program works as follows:

  1. retrieve all variables used in the message in the following order: the message on stdin, the username, hostname, current date, the flag, and the random message-id.
  2. create the output file, which name is /tmp/mail_<time>_<message-id>, and write the message to it.
  3. print the message id and the output file checksum
  4. destroy the output file

The new file can only be read by its owner, which is the setuid one. So you cannot just make stdout blocking, read the message-id and read the file before unlink is called.

The random string is problematic, because you can’t guess it nor brute-force it. The trick here lies in the way error handling was performed: the message “An error occured” is printed but the program doesn’t exit.

if (/* ... snip ...*/
    /* read flag */
    ((flag_fd = open(FLAG_PATH, O_RDONLY)) == -1) ||
    (read(flag_fd, msg->flag, FLAG_SIZE) < 0) ||

    /* generate random Message-ID */
    ((urandom_fd = open(RANDOM_PATH, O_RDONLY)) == -1) ||
    (read(urandom_fd, random, sizeof(random)) != sizeof(random)) ||
    (hexdigest(msg->id, random, sizeof(random)))
) {
    fail();
}

close(flag_fd);
close(urandom_fd);

Because the flag is opened before /dev/urandom and not closed yet, you can cause a file descriptor exhaustion bug to happen, which would leave the random message-id initialized with null bytes.
That can be trivially done using ulimit (or setting RLIMIT_NOFILE to 4 with setrlimit()).

Many teams just stopped there and then performed a race condition by creating a hardlink so they can read the flag file. Which I didn’t really think of (fail). However there’s a “cleaner” solution, which involves ulimit / setrlimit again.

Because we now know the message-id (null bytes), the only thing left that we don’t know is the flag itself. Remember that the checksum of the file written to disk is provided. We can use that information to leak the flag byte by byte, proceeding as follows:

  1. set RLIMIT_NOFILE to 4 to bypass /dev/urandom
  2. set RLIMIT_FSIZE so that the program cannot write more than X+1 bytes, X being the number of bytes before the flag
  3. launch smtpwn and check out the checksum
  4. bruteforce the last byte (255 possibilities) until we match the checksum
  5. repeat with RLIMIT_FSIZE += 1

The exploit can be found on github here.

This allows to retrieve the flag quickly (and reliably):

[+] I
[+] IN
[+] INS
[+] INS{
[+] INS{M
[+] INS{M4
* snip *
[+] INS{M4yb3 0th3r p30pl3 w1ll try t0 l1m1t m3 but 1 d0n't l1m1t mys3l
[+] INS{M4yb3 0th3r p30pl3 w1ll try t0 l1m1t m3 but 1 d0n't l1m1t mys3lf
[+] INS{M4yb3 0th3r p30pl3 w1ll try t0 l1m1t m3 but 1 d0n't l1m1t mys3lf}
[+] INS{M4yb3 0th3r p30pl3 w1ll try t0 l1m1t m3 but 1 d0n't l1m1t mys3lf}

Final flag: INS{M4yb3 0th3r p30pl3 w1ll try t0 l1m1t m3 but 1 d0n't l1m1t mys3lf}

Insomni’hack finals – CTF results

Here is the final scoreboard for Insomni’hack 2015!
Congratz to Dragon Sector for winning again this year!

1 Dragon Sector 6035
2 StratumAuhuur 5725
3 int3pids 4800
4 KITCTF 4135
5 0x8F 4105
6 dcua 3255
7 penthackon 3135
8 mushdoom 2660
9 BullShitsecurity 2350
10 RGB 2070
11 13NRV 2060
12 Porc Scanner 2020
13 sec-cured 1780
14 OWE 1645
15 FIXME 1590
16 N05L33P 1515
17 pycured 1470
18 HacKazaar 1265
19 UndefinedBehavior 1265
20 Samurai 1265
21 SeBC 1235
22 Old legends 1230
23 pinkBull 1230
24 waspo 1205
25 Barbah4ck3R2D2 1165
26 H314 1105
27 /dev/null 1055
28 pilons-de-poulet 1030
29 cr4zyg04t0verfl0w 920
30 pic0wn 870
31 NoPwnNoCookie 860
32 […] 845
33 /null/uppercase 810
34 EpsiH4ck 790
35 Soft qui peut 785
36 /null/lowercase 635
37 hard 615
38 C8H10N4O2 480
39 sh0tnb33r 470
40 BlackFox 430
41 KAOS 430
42 unlockedwheel 365
43 vuk 365
44 Epic Hack Battelle 350
45 whoaim 350
46 0x90 310
47 /dev/lowercase 190
48 V3sth4cks153 190
49 eint0 175
50 morb{H}ack 175
51 The_iNeXplication 175
52 theciso 55
53 test 55
54 seultout 55
55 SnakeFeet 55
56  <h1>si 55

The scores have been sent to CTFtime.
Below are some quick stats on the number of solves for each task:

Exploit:
mastermind 9
smtpwn 5
Sql inject flow 2
The Firm(ware) 0
Jurassic Sparc 0
SH1TTY 0
Forensic:
ZoomIn 43
Lost In Memories 26
Elysium ropchain analysis 1
Hardware:
1-2-3-4 3
Mobile:
iBadMovie Season 1 40
iBadMovie Season 2 20
InsomniDroid Phase 1 0
InsomniDroid Phase 2 0
Network:
TimeToLeak 10
Hollywood network 0
Reversing:
Swordfish 43
Swordfish_passwd 12
Shellcoding:
blue pill 8
tldr 6
Web:
n00bs gonna win! 56
Smell of the lamp 32
Hacker News 30
Serial Hackers 19
Smelly lamp got makeup 4
Hacker Idol 2
Jack the clicker 1
Hack like it’s 1999! 0

As you can see a few tasks were not solved during the CTF, so we will try to publish some writeups to explain our solutions, stay tuned!