The Mustache syntax is touted as being “logic-less templates”; however, when client-provided templates are rendered server side, there are some unexpected side effects.

This post explores the discovery of a (somewhat-limited) form of RCE within a Ruby web application where the end-user was able to control the Mustache template variables in use. This RCE led to the ability to read and delete all customer data, and could potentially be extended to execute shell commands.

It should be noted that this is not a vulnerability with the Mustache library or syntax - but instead a misuse of the library by the bounty target.

The bounty program in which this vulnerability was discovered did not allow for disclosure - however, given the nature of the bug, it is possible to provide a realistic comparison using some Ruby examples.

An Introduction to Mustache Templates

For this example, and for the rest of the examples, we will make use of the Ruby Mustache package - as this is where the vulnerability was found on the target. We will also make use of the Ruby ActiveSupport package, as this was present within the target’s environment, and allowed for Ruby object allocation.

The Mustache Template Syntax is a web template system with implementations in most of the popular programming languages. They lack flow-control systems, however there is room for looping and conditional evaluation.

irb(main):001:0> Mustache.render("Hello {{name}}!", name: "Rhys")
=> "Hello Rhys!"

You can also provide objects, and perform method chaining in order to access nested values:

irb(main):001:0> user = User.first
=> true
irb(main):002:0> Mustache.render("Hello {{user.name}}!", user: user)
=> "Hello Rhys!"

The Target

While investigating the target, I came across an integration where PagerDuty webhooks are directed to your service on the target, and any incident alerts raised by PagerDuty are transformed into customer-facing communications using Mustache templates.

I found early on that the endpoint that the target used to receive PagerDuty webhooks was unauthenticated, and I was quickly able to find a method in which I could impersonate the PagerDuty Service. This allowed me to act as PagerDuty, but using arbitrary content that would not normally be produced by the PagerDuty webhook system.

The Discovery

While playing around with the Mustache syntax, I quickly realised that there was no restriction to the tags that you were allowed to use.

I tried several different values and escapes, however I came up pretty empty. That is until I tried to render the value of context.

irb(main):001:0> Mustache.render("{{context}}")
=> "#<Mustache::Context:0x00007ffa3b8ef638>"

This immediately struck me as interesting - what we are seeing here is the Ruby string representation of an allocated object. This means that we have access to what is essentially a Ruby variable; meaning that this library might not be so safe when user content is encountered.

Given that the templates are being rendered server-side, and given that I could view and act on the Ruby object, I was tempted to explore vectors for RCE. However, I quickly determined that Mustache variable names could only contain letters, numbers, and dots - meaning that I could not pass arguments.

This meant I could not try something like context.class.ancestors.new.instance_method_send(:eval).

Enter Arbitrary Objects

However, given that I was able to supply user input (via the PagerDuty webhook impersonation), and given that I could access objects (as seen with the context example), I decided to try my hand at using method chaining to methods on the object itself.

irb(main):001:0> Mustache.render("{{value.class}}", value: "Rhys")
=> "String"

I then remembered the Active Support #constantize method. This method, which is a utility provided by ActiveSupport, tries to find a declared constant with the name specified in the string. I figured that this may allow me to interact with arbitrary objects within the scope of the application. To test this, I supplied a class value of Time.

irb(main):001:0> Mustache.render("{{value.constantize.new}}", value: "Time")
=> "2019-07-31 12:31:54 -0700"

Perfect! This is exactly what was needed.

Exploiting ActiveRecord Models

For the PoC, I decided to demonstrate that it would be possible to use our own malicious Mustache template to a) read all customer data, and b) delete all customer data. This would be achieved by targetting the application’s ActiveRecord models using our ability to act on arbitrary Ruby classes with method chaining. ActiveRecord is the ORM that powers Rails applications, and provides access to databases using Ruby classes.

I began by created a PagerDuty incident with the name Incident, and updated my template to have the following content.

{{ incident.name.constantize.all.first }}

Broken down, this chain of methods does the following:

  1. incident.name: Obtains the value from the fake PagerDuty webhook. In this case, the value was Incident.
  2. constantize: Attempts to find a declared constant with the name from the preceding method. In this case, the prior value was Incident - the name of the ActiveRecord model we were wishing to target.
  3. all: Performs an SQL SELECT on all records for the Incident model.
  4. first: Limits the result to one record.

I then invoked my fake webhook, and upon refreshing the page on my public-facing site, was met with the following output.


Sample of Web Application demonstrating access to arbitrary data

(To keep the identity of the program under wraps, the full output has been obscured. However, what is shown indicates that we are able to obtain any record from the database)


Perfect! Given that this was someone else’s data, this example was all that was needed to demonstrate meaningful impact at the P1 level. It should also be noted that the typical ActiveRecord methods such as #delete_all were also available, meaning that we could remove all data with this bug.

Methods in which arguments needed to be supplied, such as #update were not available - as the Mustache validator would not allow the characters required to form valid syntax.

Conclusion

The root cause of this issue was leaning on a server-side templating library to render user-supplied templates on the main web app instance. Instead, the template should have been a) rendered in a sandbox - such as AWS Lambda, b) filtered for inappropriate variables, or c) implemented using the Javascript library, which does not allow exposure to Ruby dot notation.

Since reporting the bug, the program has introduced a safelist of template tags that are allowed to be used within the communication templates. I am yet to find a bypass.

Figured out a way I could have gotten to full RCE using this method? Hit me up on Twitter!

2019

RCE in Ruby using Mustache Templates

The Mustache syntax is touted as being “logic-less templates”; however, when client-provided templates are rendered server side, there are some unexpected si...

Back to Top ↑