During a recent webapplication testing I decided to perform some fuzzing of certain paths within the URI of a CMS and happened to find a potential SSTI (server side template injection) within one of the CMS’ plugins which I then was able to successfully exploit for information disclosure. In this post I want to share my approach on how I moved forward with the analysis.

Introduction

So here is the SSTI which I stumbled upon during my initial fuzzing. You can see that I was able to trigger some kind of math logic which is processed by the template engine on the server which is then reflected in the canonicalURL which is part of the Link header within the response:

First of all: what is it about? Basically SSTI is a scenario where user input is unsafely embedded into a template. Most of the time it is related to issues where user defined input is directly concatenated into the template and if this input contained a template expression that will be evaluated by the server.

However if this type of vulnerability is new to you, make sure to visit the Portswigger blog and read this article by James Kettle (@albinowax). You won’t regret it and it is a super addition to the topic of webapplication security. His research is also available as a printable whitepaper and was presented at BlackHat 2015 (paper/video). I already was aware of the basic concept behind this vulnerability but for me this was the first time that I actually encountered it out in the wild and so I decided to freshen up my knowledge and read through James’ paper regarding the topic.

After sharpening my understanding for this kind of weakness I got eager to perform a proper exploit and my first approach was to take the easy route by leveraging an automated exploitation tool called TPLMAP (https://github.com/epinna/tplmap). (Un)fortunately my braindead fire and forget methodology was not successful and I was forced to properly look into the subject which allowed me to actually learn a few new things:

Back to the drawing-board

Okay, we just hit the script kiddy wall. Let me take you on the journey on how to proceed from here. First of all: let’s step back, take a breathe and evaluate the situation by breaking down the informational facts.

We definitely know that we can inject arbitrary stuff which is processed by the template engine and based on the response given in the header we can assume the site is running on Craft CMS (https://craftcms.com):

Unfortunately it does not leak the exact version number but for beginners we can simply try to leverage the latest documentation and according to this documentation Craft is using Twig (which is one of the PHP template engines covered in James’ research).

So let’s have a comprehensive look at the documentation; this will actually help us a lot with our ambitions and sharpen our understanding of how the CMS and the template engine interact with each other. Also it is super useful for understanding the architecture and the actual objects, methods and syntax of both components:

Also it is always recommended to check older/known/existing vulnerabilities for your target. And if available evaluate the code to understand what was changed and fixed during the consecutive releases. Any of this might spark some ideas in your head. In my case there were a few known issues which I reviewed. For example:

As mentioned understanding older vulnerabilities can always be a good addition towards ones very own approach. Even though the issue is already fixed, the formerly present SSTI vulnerability listed above is a good example as you can find some interesting ideas by reading up how they created the PoC for this vulnerability.

And finally if this is an option: download the source code and have a look at the internals!

If you tried leveraging automatic exploitation and reconnaissance tools (TPLMAP) make sure to understand what they do and how they do it.

Fun fact: the utilization of Twig within Craft CMS also explained why my attempt with TPLMAP did not deliver any loot in the first place, as Twig is not supported by the tool:

Based upon our informational ramp up we can tell that according to James’ research Twig’s _self object and its env attribute are the way to go in order to get Remote-Code-Execution (RCE):

{{_self.env.registerUndefinedFilterCallback("exec")}} {{_self.env.getFilter("id")}} uid=1000(k) gid=1000(k) groups=1000(k),10(wheel) 1 2 3 4 { { _self . env . registerUndefinedFilterCallback ( "exec" ) } } { { _self . env . getFilter ( "id" ) } } uid = 1000 ( k ) gid = 1000 ( k ) groups = 1000 ( k ) , 10 ( wheel )

So let us try to call it via the injection point. In my case I found that _self is not an object but merely a string returning the template’s name:

According to the documentation and other statements, it turns out they changed that moving from Twig v1 towards Twig v2. So we are dealing with an updated Twig version that is not prone to the vulnerabilities disclosed in James’ research. At the current point in time it seems there is no publicly available exploit alternative for Twig v2 (which also should be the reason that TPLMAP currently does not support Twig exploitation).

Well, so no easy-peasy exploitation for us.

But… there is still hope.

Getting creative

Even though there is currently no well documented way of exploiting the Twig v2 template engine, we can still try our best to interact with the components of the CMS via the identified injection point. According to the Craft CMS documentation there are plenty of interesting objects, filters and methods which can be called from the template. Also it seems that we can call objects and methods from the Craft CMS itself. According to the documentation we should be able to query the editable sections:

So let’s give it a try…

Fancy! We got an array back, so our stuff seems to be working. Let’s hunt something more juicy now. Digging through the documentation reveals one specific method that seems very promising as it allows us to read information from the configuration files (this is also what some people used to exploit Craft CMS with former vulnerabilities):

{{ craft.config.get('someConfigSetting', 'someConfigFile') }} 1 { { craft . config . get ( 'someConfigSetting' , 'someConfigFile' ) } }

So let’s have a look at the configuration files and pick out something nice:

Plenty of juicy stuff which we can try to access, but this time we need to pass some parameters to the craft.config.get() method. Let’s try for the password entry within the db.php file:

(Un)fortunately the framework is shipped with some protection features which sanitize control characters by replacing them with corresponding HTML entities. When stumbling into such issues it is usually a good approach to perform some evasive maneuvers. For instance sometimes you get lucky by evaluating exotic character escape sequences or exotic encoding schemes which might bypass the filter. After spending some time with analyzing the filter behavior I came to the conclusion that it was quite robust and so a change of strategy was required. As I went back to reading the Craft documentation I started playing around with all of the available functions. The craft.request set looked quite interesting to me and while reading about the supported methods I finally stumbled upon this precious gem:

A quick test shows that this function indeed is reflecting the value of the client’s User-Agent header. As you can see the output is again sanitized with HTML entities:

In order to pass the return value on to another method we now simply need to use Twig’s set (https://twig.symfony.com/doc/2.x/tags/set.html) call and store the result in a new variable:

This allows us to store and reflect functions in a more flexible way. As we need to store two parameter values within our User-Agent string, we need to either use a second method as a springboard or we can use Twig’s filters in order to perform some string manipulation.

In this case I am utilizing slice() (https://twig.symfony.com/doc/2.x/filters/slice.html) which will allow us to combine multiple values within our User-Agent header.

Putting it all together

So far we have learned quite some stuff about the CMS and the template engine in scope. Based upon our knowledge we now can properly try to access sensitive information on our target by calling methods while passing on arbitrary variable values. The syntax should look something like this:

{% set dummy = craft.request.getUserAgent()|slice(0,8)%} {% set dummy2 = craft.request.getUserAgent()|slice(9,2)%} {{craft.config.get(dummy,dummy2)}} User-Agent: password db 1 2 3 4 5 { % set dummy = craft . request . getUserAgent ( ) | slice ( 0 , 8 ) % } { % set dummy2 = craft . request . getUserAgent ( ) | slice ( 9 , 2 ) % } { { craft . config . get ( dummy , dummy2 ) } } User - Agent : password db

Let’s give it a try and extract the database password from the config file of our target:

At the current point we have access to a broad variety of methods. For instance we can iterate through all CMS users, take their email addresses and let them know that their site requires a security fix:

{% for author in craft.users %} {{ author.email }} {% endfor %} 1 2 3 { % for author in craft . users % } { { author . email } } { % endfor % }

Here are some samples which I found to be of interest:

Conclusion

It turns out that the issue was caused by a CMS plugin called SEOmatic which you can find out more about here: https://straightupcraft.com/craft-plugins/seomatic and here: https://github.com/nystudio107/craft-seomatic

According to the developer the attack only works for URLs that are not associated with a corresponding Entry in Craft CMS. The reason is that the default setting for the canonicalUrl field was the Twig code:

{{ craft.app.request.pathInfo | striptags }} 1 { { craft . app . request . pathInfo | striptags } }

The way SEOmatic works, the global settings are a fallback only if nothing else matches. Most pages will match an entry, category, or product of some kind of other, and in those cases, the canonicalUrl would not be set to the offending Twig code, so the exploit wouldn’t work.

So you’d need to be attacking a URL that both exists as a Twig template (so it don’t throw a 404) and then also does not have an entry, category, or product associated with it.

I am happy to confirm that the developer fixed the issue right after the issue got reported. You can find the fixed version at: https://github.com/nystudio107/craft-seomatic/releases/tag/3.1.4

And here is the commit fixing the issue: https://github.com/nystudio107/craft-seomatic/commit/1e7d1d084ac3a89e7ec70620f2749110508d1ce1

Evaluation of impact:

As we life in a beautiful Information Age it is now time to determine how many publicly on the internet available instances of Craft CMS may be impacted by the vulnerability. Let’s pull and evaluate some data from Shodan.io (side note: some people asked me how I found their website in the first place, this is how):

In order to be able to process the information further I will split this up into multiple files which are grouped by port numbers:

Unfortunately we are only able to query the Shodan API for string values which are contained within the data sections of the information that was collected by Shodan. So I want to make sure that I am filtering for entries which actually have a proper Link header included and therefor might be affected by the vulnerability:

So 244 our of 269 left. Time for the final shot: let’s crawl the remaining targets and see what’s left:

Overall I identified 65 vulnerable targets which could have been worse. Then I simply dumped the email addresses of ~300 admin accounts and let them know of the problem. A bunch of them quickly returned to me and acknowledged that they have updated their installation properly. As the developer also pointed the matter out on Twitter most people should be running on a fixed version soon:

https://twitter.com/nystudio107/status/1021847835418009605

Final words:

Leveraging existing scanning and exploitation tools is always easy. But once you hit a wall spend some time with sharpening the axe before cutting down the tree. Make sure to identify and process comprehensive information in order to to get a better understanding of your target. It will pay off in the end. At the very least you will have learned something new.

Timeline: