Server Side Template Injection
VulnMachines SSTI
After going over to the web page it shows this
Trying to input something shows it get reflected back
Using basic SSTI payload works
Let’s use this SSTI check to know what template engine it uses
First i’ll try ${7*7}
but it doesn’t get evaluated
Next we move unto the down part of what we are using to get the template engine
Now i’ll try this ``
It gets evaluated
So we move unto ``
But doing that gives us an error
Your nickname contains restricted characters!
To bypass this we can try using double quote instead of single quote
Doing that works
From here we can say the template engine is either Jinja2 or Twig
But from the web server header we can then conclude it’s Jinja2 templating engine that is being used since the webserver is built on python
The aim of this challenge is to get the flag
Checking the env works and shows the flag
Flag: 56756c6e6d616368696e6573202d20506c61636520666f722050656e746573657273
It is in it’s hex form
I used python to decode it
#!/usr/bin/python3
# Author: Hack.You
encoded = '56756c6e6d616368696e6573202d20506c61636520666f722050656e746573657273'
decoded = bytes.fromhex(encoded)
flag = b'vnm{'+decoded+b'}'
print(f'Flag: {flag}')
Running it gives us the flag
But how about we try get RCE
First I need to know the classes that are present and preferably the subprocess class
Reason is because it’s a python module that allows us to execute commands
So here’s the payload:
But giving that to the web server gives an error
Your nickname contains restricted characters!
It seems like it filters another character
To make it easier i’ll intercept one of the post request and save it in a file
Now i’ll use ffuf to fuzz for allowed special characters
Sorry about the way my ffuf is that’s the new update 😥
But we can see the allowed characters :)
One way we can improvise in bypassing the filter is by converting the blacklisted character to hex
Using the updated payload works
Now we can check if the subprocess class is among it
Cool it’s among the classes
We need to get the position of the subprocess class number
Burp intruder can help with that :) i did try scripting this but unfortunately i can’t seem to make it send as raw data without it being converted to it’s string form
I’ll just fuzz using Burp Intruder
After running it I got this
We can see the request with a content length of 258
Checking it shows that it indeed works
And the position for the subprocess class is 286
We can then try reading the app.py file
Here’s the result gotten from reading app.py (P.S: I exfiltrated it 🙂)
import random
from flask import Flask, render_template_string, render_template, request
app = Flask(__name__)
app.config['SECRET_KEY'] = 'Follow us on Twitter rapidsafeguard'
#Tiaonmmn don't remember to remove this part on deploy so nobody will solve that hehe
'''
def encode(line, key, key2):
return ''.join(chr(x ^ ord(line[x]) ^ ord(key[::-1][x]) ^ ord(key2[x])) for x in range(len(line)))
app.config['flag'] = encode('', 'GQIS5EmzfZA1Ci8NslaoMxPXqrvFB7hYOkbg9y20W3', 'xwdFqMck1vA0pl7B8WO3DrGLma4sZ2Y6ouCPEHSQVT')
'''
app.config['flag'] = '56756c6e6d616368696e6573202d20506c61636520666f722050656e746573657273'
#app.config['flag']="Success fully pass SSTI"
nicknames = ['˜”*°★☆★_%s_★☆★°°*', '%s ~♡ⓛⓞⓥⓔ♡~', '%s Вêчңø в øĤлâйĤé', '♪ ♪ ♪ %s ♪ ♪ ♪ ', '[♥♥♥%s♥♥♥]', '%s, kOтO®Aя )(оТеЛ@ ©4@$tьЯ', '♔%s♔', '[♂+♂=♥]%s[♂+♂=♥]']
#nicknames=['psycho','rapidsafeguard']
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'POST':
try:
p = request.values.get('nickname')
id = random.randint(0, len(nicknames) - 1)
if p != None:
if '.' in p or '_' in p or '\'' in p:
return 'Your nickname contains restricted characters!'
return render_template_string(nicknames[id] % p)
except Exception as e:
print(e)
return 'Exception'
return render_template('index.html')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=1337)
We can also see the flag there 😅
And we’re done 👻