Dojo challenge #41 - Ruby treasure winners & writeup

The Dojo challenge, Ruby treasure, challenged the participants to bypass regex filter and exploit a command injection in Ruby's IO.read function to capture the flag.

💡 Want to create your own monthly Dojo challenge? Send us a message on X!

The winners

Congrats to khimluck4, P0pR0cK5 and duplicatesucks for the best write-ups 🥳

The swag is on its way! 🎁

Subscribe to our X and/or LinkedIn feeds to be notified of upcoming challenges.

Read on to find out how one of the winners managed to solve the challenge.

OVERALL BEST WRITE-UP

We would like to thank everyone who participated and reported their solution for this challenge to the Dojo program. There were many great reports for this challenge and you can find the best write-up below:

————– START OF khimluck4‘s REPORT —————

Description

The Ruby Treasure application is a web-based e-commerce platform built using Ruby. A critical vulnerability has been identified in the web application that allows an attacker to execute arbitrary system commands on the server. The vulnerability exists because the application improperly validates filenames using a regex that checks input line by line, allows special characters like pipe (|) which can be used for command execution, fails to sanitize user input before using it in file operations, and uses the ERB templating system in an unsafe manner.

The exploitation of this vulnerability allows attackers to read any accessible file on the system, including sensitive configuration files and user data. Most critically, it allows complete command execution on the underlying server, potentially leading to full system compromise.

Exploitation

The discovery of this vulnerability required a thorough code analysis during the initial reconnaissance phase. This involved examining the underlying Ruby technology stack, understanding how the application processes user input, and identifying potentially unsafe utility methods. By analyzing the application's validation mechanisms, file handling operations, and template rendering process, it became clear that a combination of Ruby's regex behavior with multiline strings and its special handling of pipe characters in IO operations could be leveraged to bypass security controls. This reconnaissance phase was crucial for developing a successful exploitation strategy that could overcome the input validation while achieving command execution.

Code Analysis

The vulnerability begins at the application entry point where user input is processed. The application is designed to serve ERB templates based on user input:

require 'cgi'
require 'erb'

Dir.chdir("/tmp/app/views")

The application first changes its working directory to "/tmp/app/views", which sets the context for all subsequent file operations. This is important because it establishes the base directory from which any path traversal would need to escape to access sensitive files like the flag stored at "/tmp/flag_*.txt".

Next, the application defines a validation function intended to restrict the filenames that can be accessed:

# Validate the given filename
# We use a super strict regex, so it can't be bypassed, right?
def validateFile(filename)
    return !!/^[a-zA-Z0-9_-]+$/.match(filename) || filename.length == 0
end

This validation function contains a critical flaw. While it appears to limit filenames to alphanumeric characters, underscores, and hyphens (as per the regex pattern /^[a-zA-Z0-9_-]+$/), Ruby's regex engine treats the ^ and $ anchors differently when processing multiline strings. In Ruby, when a string contains newlines, the regex engine evaluates the pattern against each line separately. The ^ anchor matches the beginning of any line and the $ anchor matches the end of any line, not just the beginning and end of the entire string.

The key vulnerability is that the match method in Ruby will return a match if any line in a multiline string matches the pattern. This means that if any single line of a multiline input satisfies the regex pattern, the entire input will pass validation, even if other lines contain malicious characters.

The application then processes the user input:

filename = CGI.unescape("")
content = ""

if validateFile(filename)
    begin
        content = IO.read("#{filename}.erb")
    rescue
        content = IO.read("collection.erb")
    end
end

Here, the application takes user input (which comes from the input field on the challenge page), URL-decodes it using CGI.unescape(), validates it using the vulnerable validateFile() function, and then attempts to read a file with that name plus the ".erb" extension. If this file reading operation fails, it falls back to reading "collection.erb".

Finally, the application renders the template:

# Render the given page for our web application
puts ERB.new(IO.read("index.erb")).result_with_hash({page: content})

This is where the command execution vulnerability comes into play. The application reads the template file "index.erb", creates an ERB object, and passes the content read earlier as a parameter named "page". The ERB template engine evaluates any Ruby code embedded within <%= %> tags in the template.

Conclusion From Code Analysis

The vulnerability can be exploited through a two-stage attack. First, the attacker creates a multiline input where one line (in this case, the second line) satisfies the regex validation requirement. This allows the entire multiline input to pass validation despite containing malicious characters in other lines. Second, the attacker includes command injection in the first line utilizing Ruby's IO special character handling.

The specific vulnerability exists because of several factors working together. Unlike JavaScript, Ruby's regex implementation treats ^ and $ anchors as matching the beginning and end of any line in a multiline string, not just the entire string. This means that the validation function only needs one line in a multiline input to match the pattern for the entire input to be considered valid. Additionally, Ruby's IO operations interpret the pipe character (|) at the beginning of a filename as a command to execute, reading the output of that command instead of reading from a file. This creates a perfect storm where an attacker can craft an input that both passes validation and executes arbitrary commands.

The weird regex validation in Ruby was confirmed from the following site, where even when an invalid input is given on first line, the input on second line would give a match.

This was also confirmed from the ruby compiler:

So a payload needed to be crafted for executing the commands on the application and also to bypass the regex validation. On using the pipe (|) on the first line, we were able to get command execution when we had a payload that would pass the regex validation on the second line. On using |ls on the first line and x on the second line, we were able to list all the files in the current directory.

Now all that was needed was to traverse to the directory with a flag and read it, hence the following payload was used to traverse back and read the .txt file with a wildcard. It was confirmed that it was the only .txt file with |ls ../../ earlier.

On using the payload below

|cat ../../*.txt
x

gave the You've Pwned it modal and the Flag.

Proof of Concept

The PoC involved putting the following payload on the input field.

|cat ../../*.txt
x

The input contains two lines separated by a newline. The first line is |cat ../../*.txt and the second line is simply x. When this input is passed to the validateFile() function, the regex checks each line separately. The first line fails the validation because it contains characters that aren't allowed by the regex. However, the second line x is a simple alphanumeric character that passes the regex validation. Because at least one line in the input satisfies the regex pattern, the entire input passes validation.

After passing validation, the application tries to read a file with the name. In Ruby's IO operations, when a filename begins with the pipe character |, Ruby treats everything after it as a command to execute and reads the output of that command instead of reading from a file. In this case, the command being executed is cat ../../*.txt. This command traverses up two directories (from /tmp/app/views to /tmp) and reads .txt files, including the flag file that contains the sensitive information we're targeting.

The output of this command is then passed to the ERB template engine and displayed to the user, revealing the contents of the flag file and completing the exploit. This combination of regex validation bypass and command injection through Ruby's IO special character handling creates a powerful vulnerability that allows attackers to execute arbitrary commands and read sensitive files.

The Flag as obtained from the application interface is

FLAG{Ruby_C4n_B3_W31rd_R1gh7?!}

Risk

The impact of this vulnerability is critical. It allows attackers to execute arbitrary commands on the server, which can lead to complete compromise of the application and potentially the entire server. Attackers can access sensitive system files, steal confidential data, install malware, and potentially pivot to other systems in the network.


————– END OF khimluck4‘s REPORT —————