Exploiting SSTI and XSS in the CMS Made Simple Web Application

Our Security Researcher found a vulnerability in a parameter in a URL in the address bar of the browser. Read more about how he did it, and how he was able to exploit it to carry out a few harmless changes.

CMS Made Simple is a content management system that was first released in July 2004 as an open source General Public License (GPL) package. It is currently used in both commercial and personal projects.

CMS Made Simple logo

As Security Researchers working on the Netsparker web application vulnerability scanner, we’re always excited about testing and scanning new open source web applications for vulnerabilities. Recently, I read researcher Osanda Malith’s blog post, CMSMS 2.1.6 Multiple Vulnerabilities, where he explains his findings following a review of the CMS Made Simple source code. I decided to see for myself what I could uncover.

The First Step: Noticing the Parameters in the URL

After installing CMS on our local system, I was determined to try to find a vulnerability. Of course, from a black box point of view, a freshly installed application with default configuration lacks a lot of functionality. However, I decided to take a closer look.

It didn’t take long before I noticed the following URL in the address bar of the browser:

URL of note in the address bar of CMS made simple

Finding so many parameters together in a URL always excites Security Researchers. Why? Well, the more parameters with different functionality you expose, the greater the number of potential attack surfaces.

Manually Reviewing Potential Attack Surfaces

At this point, I decided to take a closer look at the URL. If the purpose of a parameter is not obvious, and there is no alternative, changing the value of a particular parameter may be the quickest way to find out. For example, if any parameter value is displayed on the page, it’s a good idea to check if it’s vulnerable to Cross-site scripting (XSS) first.

Let’s look at what happened when I modified the cntnt01detailtemplate parameter value by adding =test.

As you might imagine, the entered string should match with another value in the back end. This error occurred even when I changed a single letter. I concluded that it was not a good idea to look for XSS there.

Perhaps the Source Code Would Help Me Decide What To Do

After quickly checking the source code to see why it generated an error, I could see that the value was related to the template detail, as the parameter name implies. It was built using Smarty Template Engine, which keeps content, functionality and templates separate. However, as we know, developers must be careful when using template engines, because if implemented incorrectly in applications, they can lead to critical security problems.

I continued with the black box tests.

Detecting and Exploiting Template Injection to Gain Remote Code Execution

I decided to add a simple payload to the following URL (instead of ‘Simplex News Detail’) to help me see what was happening:


I added, simply: string:Netsparker. This displayed ‘Netsparker’ in the window.

I then retested it with another payload:


I added: string:{6*3}. Again, I was able to display a number in the window.

Most template engines have a ‘sandboxed’ mode to prevent you from going further. However. depending on the template engine used, it is sometimes possible to escape the sandbox and execute arbitrary code. In this case, exploiting unsandboxed Smarty was a simple matter:


I added this: string: {php}phpinfo();{/php}. And, I was able to see the output of the phpinfo function displayed in the screenshot.

What Did I Overlook During the Manual Audit?

Basically, what I was trying to do was to observe the output following particular requests, or any particular user input during manual tests. The input you observe, such as cntnt01detailtemplate, can produce different outputs for many reasons. It is very difficult to use manual testing to observe all possible behaviors. The cntnt01detailtemplate did not initially reflect the changes I made on the parameter. At this point, many Security Researchers would not waste further time searching for an XSS vulnerability on this parameter. In addition, sometimes there are simply too many prerequisites for a successful attack to occur, and you may need to test hundreds of possibilities for a single parameter to even detect all of these preconditions.

This is a serious problem for security researchers – but not for the dead accurate web vulnerability scanner Netsparker.

Netsparker Automatically Identified the XSS Vulnerability in the Same Parameter

To add insult to injury, the Server Site Template Injection was not the only vulnerability in this parameter. There was an additional Cross-site Scripting vulnerability that could be triggered by double encoding the payload, and the urldecode function was used on that input value. This led to an XSS vulnerability, even though the special characters were encoded to HTML entities once they entered the application.

This is the XSS vulnerability we found when we scanned the web application with Netsparker:

XSS vulnerability

Let’s take a closer look at why we did not find this XSS vulnerability during the manual tests. Even when using a whitebox approach, the XSS was quite hard to find. This was because the developers used object-oriented programming (OOP) style with lots of interconnected classes, and put some of the application logic into templates. This makes it very hard to trace the code and find out where data enters and exits the web application.

A Detailed Explanation of the Cross-site Scripting Vulnerability Identified in CMS Made Simple

However, let me provide you with an overview of the vulnerability. The data enters the application in the lib/classes/class.moduleoperations.inc.php file in the function GetModuleParameters, where the $_REQUEST variable is processed. The code strips the prefix from the parameter values and returns the resulting values in the $params array.

Following that, the parameters are sanitized against XSS in the lib/classes/class.CMSModule.php file, as shown.

This should be sufficient to sanitize the parameters. However, it is always better to save the raw data and encode it, depending on the context in which it is used. As mentioned above, the vulnerability is in the detail template parameter, which is currently correctly sanitized.

In the modules/News/action.detail.php file, the detailtemplate parameter is URL-decoded again, which ensures that the path to the template does not contain any URL-encoded characters. After all, the parameter is not intended to be printed, and should only be passed to a function that fetches the template. Even if it was printed, HTML special characters are still replaced with HTML entities.

So far everything looks fine. The detailtemplate parameter is not printed anywhere, and is only used to load the template. The problem, however, is that there is no valid template with the name of our payload. Therefore an exception is thrown in the index.php file.

The errorConsole function is located in the lib/classes/internal/class.Smarty_CMS.php file, and contains the following code.

As you can see, $e->getMessage() is assigned to the template. However, the problem is that this contains the detailtemplate parameter, which was URL-decoded. When we look into the template located in the lib/assets/templates/cmsms-error-console.tpl file, it becomes apparent why only logged-in users, like administrators, are vulnerable to XSS.

The $loggedin template variable was assigned above and is only true if the user is logged in. The output of detailtemplate is in {$e_message}. As already mentioned, this is still encoded with HTML specialchars, even though urldecode() was already used to remove URL-encoded values.

The problem is that only certain special characters are sanitized by the HTML special character function, for example <, > and &. What it doesn’t sanitize is the percentage (%) character. However, this is the prefix of URL-encoded bytes. This means that if we double encode the value, by passing %253c in the template name, it will contain %3c instead of <.

For example instead of <script>alert(1)</script>, we pass %253cscript%253ealert(1)%253c/script%253e as our payload. It does not contain any character that is encoded to an HTML entity, so it will be stored as %3cscript%3ealert(1)%3c/script%3e on the server side. However, once the urldecode() function is used on this value it will be decoded to <script>alert(1)</script> and therefore introduce an XSS vulnerability through double encoding.

The Importance of Automating the Vulnerability Assessment

Even though the vulnerability is relatively easy to fix, it is very hard to find, even in a whitebox test. While it looks like everything is sanitized correctly, a decoding function combined with the right input can still lead to cross-site scripting.

It is often easier to use an automated vulnerability scanner like Netsparker, since, as in this case, it can conduct a more thorough and accurate analysis of an application than a penetration tester. There are various reasons for this, one of which is the large codebase and the OOP style, which makes debugging harder. The other is the fact that testers usually have a limited amount of time in which they are able to analyse the application.

Had the vulnerable CMS Made Simple versions been scanned with Netsparker before being published, the vulnerabilities could have been fixed before being deployed in any production environment.

Since we know that open source projects are instrumental in providing secure applications to a broad range of customers, we supply free Netsparker Enterprise licenses to all open source web application developers.

XSS Vulnerability Classification and Severity Table

Classification ID / Severity
PCI v3.1 6.5.7
PCI v3.2 6.5.7
CWE 79
OWASP 2013 A3
HIPAA 164.308(a)
Netsparker High

SSTI Vulnerability Classification and Severity Table

Classification ID / Severity
PCI v3.1 6.5.1
PCI v3.2 6.5.1
CWE 917
OWASP 2013 A1
HIPAA 164.308(a)
Netsparker High