Jinja2 SSTI - Filter Bypass help needed

So, I am still stuck, but I’ve made some progress.

I can write to the HTTP referrer string and I can call request.environ.HTTP_REFERER in the SSTI, but it still remains static.

So for example: {{os.popen.request.environ.HTTP_REFERER}} doesn’t work but {{request.environ.HTTP_REFERER}} does actually print whatever I put in the referer field.

Oh my god… Taz asking for help?

So for example: {{os.popen.request.environ.HTTP_REFERER}} doesn’t work but {{request.environ.HTTP_REFERER}} does actually print whatever I put in the referer field.

So clearly the OS module is not wholly imported, just os.popen.request. Maybe something useful in there? Not 100% sure though without being able to look at it.

Then again that has absolutely nothing to do with the HTTP_REFERER string unless you are down a rabbit hole, it does seem like a perfect XSS opportunity only question is how to trigger it…

It could well be a rabbit hole. Right now I have no way to tell.

However it is a lab on Jinja2 exploitation so I think XSS isn’t really part of the lab builders plans.

I have space for 41 characters between the {{ and }} markers, so my options are limited.

I’d like to do an os.popen('id').read() at the very least but ( and ) are blocked so that doesn’t work. Sending a referer with variations of the (id).read() string didn’t work.
Using __getchilditem__ syntax makes some progress but quickly runs out of space.

Type your comment> @TazWake said:

It could well be a rabbit hole. Right now I have no way to tell.

However it is a lab on Jinja2 exploitation so I think XSS isn’t really part of the lab builders plans.

SSTI is a subset of XSS

As for the rest, the only bracket bypass I can think of uses parenthesis… somehow the backend must be using them or else would not be able to run. That means there is prefiltering prior to hitting the template… have you looked into py modules that do this?

Yeah - the content filtering happens before the data is sent through to the Jinja2 part. As far as I can tell its a custom set of filtering and it creates all kinds of issues.

For example, any attempt to submit a request with (),[],%, ,+, \, " (and a few other symbols) is rejected before it gets to the engine. It also rejects various encodings to try and sneak them past.

Bump - just in case anyone has any good ideas.

I figure you would have told us already if you knew (mainly a bump excuse), but were you able to narrow down what the filter is likely built on? Is it getting filtered in the web app, a modification to the backend server running Jinja2, python, etc.?

The filter is the first stage of the application, the exploit path is built on an authentication form. If you create a user who already exists, the subsequent messages are passed through the app to an SSTI-vulnerable application.

For example, if you create the first user as {{'7'*7}}@example.com, nothing happens. If you then resend a user creation request with the same email, you get a message saying 77777@example.com already exists.

What this means is that the first creation has to work, which means it has to get through the filtering in place on that stage - this largely excludes anything I’ve tried :cry: :smile: .

I’ve made some progress here.

Turn out if I use "{{ ...... }}"@a.bc I can now use () and other characters in the SSTI string.

It does mean I lose another two characters in the payload though. Now I need a 39 character attack :smile:

Sadly, I still cant get anything to work! :smile: :cry: :scream:

Whoever created that challenge needs a hug, or a puppy, or both.

Type your comment> @TazWake said:

Sadly, I still cant get anything to work! :smile: :cry: :scream:

So I don’t know too much about the topic, but maybe some brainstorming triggers an idea.
So from what I know, character limits don’t go well with filter bypass. So what came to my mind is to declare variables and use them in another payload.

Something like:
{{asd=config.base_}}@ab.cd
{{asd.subclasses()}}@ab.cd

So this way you solve the problem of the character limit.

PD: I just run a htb machine with jinja2 to try this, and looks like it could work.

EDIT: the syntax to define a variable is {% set test = ‘DeepPurple’ %}

@gamedeth said:

So I don’t know too much about the topic, but maybe some brainstorming triggers an idea.
So from what I know, character limits don’t go well with filter bypass. So what came to my mind is to declare variables and use them in another payload.

Something like:
{{asd=config.base_}}@ab.cd
{{asd.subclasses()}}@ab.cd

So this way you solve the problem of the character limit.

PD: I just run a htb machine with jinja2 to try this, and looks like it could work.

EDIT: the syntax to define a variable is {% set test = ‘DeepPurple’ %}

Thats pretty genius! I hadn’t thought about that at all. I wont get a chance to try it until next week but its given me some new ideas and new hope! Thanks!

Type your comment> @gamedeth said:

Something like:
{{asd=config.base_}}@ab.cd
{{asd.subclasses()}}@ab.cd

So this way you solve the problem of the character limit.

PD: I just run a htb machine with jinja2 to try this, and looks like it could work.

EDIT: the syntax to define a variable is {% set test = ‘DeepPurple’ %}
I was thinking of that too, but didn’t suggest it because it needed either the % character when using variables, which was thought to be blocked initially, or line statements enabled in Jinja2 with an unblocked prefix.

I was looking at the namespace feature in Jinja2 to get around that, but it all required far too many characters.

Good luck!

I had a few minutes to try this today, basically just wanted to see if the advice from @gamedeth was viable.

So it sort of works. I cant use spaces but between the quotes, I can use a string like:

"{{d=config.base___}}"@b.cd

At the moment, everything I’ve tried has returned a 500 error but it’s given me something to work on.

Jinja2 needs the % character and the set keyword to set a variable, like the last edit of @gamedeth’s post. It needs that or line statements turned on, and it’s off by default.

@svenkali said:
Jinja2 needs the % character and the set keyword to set a variable, like the last edit of @gamedeth’s post. It needs that or line statements turned on, and it’s off by default.

Edit: I missed that you cannot use spaces, but I couldn’t find any other way of setting variables in Jinja2 (at least), except the set keyword, the same but using line statements, or through the namespace feature as mentioned.

@svenkali said:

@svenkali said:
Jinja2 needs the % character and the set keyword to set a variable, like the last edit of @gamedeth’s post. It needs that or line statements turned on, and it’s off by default.

Edit: I missed that you cannot use spaces, but I couldn’t find any other way of setting variables in Jinja2 (at least), except the set keyword, the same but using line statements, or through the namespace feature as mentioned.

Yeah, the character restrictions are really messing me over here!

I’ve tried encoding spaces in a variety of ways but they all fail the initial step. Hopefully, I will be able to get back to it next week and maybe come up with some ideas…

(assuming the lab isn’t broken and unsolvable!!!)

There are a few things to consider here. The first is obvious - this is a Jinja2 application. As such, we really need to have an understanding about the flask structure, associated libraries etc. It’s worth setting up a flask app to play with just to see the different classes and how to access them.

The second issue is the fact that the vulnerability is triggered by a payload that must conform to an e-mail address standard - of less than 50 characters. It’s worth knowing how e-mail addresses can be structured - have a look at Email address - Wikipedia for details. Some of these will allow the inclusion of symbols (brackets etc…) as mentioned by Taz previously. This is a great learning point!!

So, it looks like you can send functions in the payload.

The third thing to consider is how Jinja2 updates the config file. Research of the flask framework will show a standard update() function for this. So you can easily update config variables - try it! The last thing to remember here is that config variables don’t necessarily need to be strings - they can be objects too.

So with a combination of config variables and GET parameters you should be able to build the right payload to get the required information.

I tried the update with strings, not objects, and I didn’t get anywhere.

If I can bring myself to return to the lab I will give that a try…