Skip to content

Level 5: PALINDROME's Invitation (osint/misc)

We start with a GitHub repository: https://github.com/palindrome-wow/PALINDROME-PORTAL.

Unfortunately, as the challenge description suggests, there is nothing interesting in the commit history of the repo. However, if we look into the GitHub actions run for this repo, we observe a run "Portal opening", which has the following log output:

--2023-09-08 04:01:29--  ***/:dIcH:..uU9gp1%3C@%3C3Q%22DBM5F%3C)64S%3C(01tF(Jj%25ATV@$Gl
Resolving chals.tisc23.ctf.sg (chals.tisc23.ctf.sg)... 18.143.127.62, 18.143.207.255
Caching chals.tisc23.ctf.sg => 18.143.127.62 18.143.207.255
Connecting to chals.tisc23.ctf.sg (chals.tisc23.ctf.sg)|18.143.127.62|:45938... Closed fd 4
failed: Connection timed out.

There is a hostname (chals.tisc23.ctf.sg) and a port (45938) as well as some funny URL-encoded string which decodes to :dIcH:..uU9gp1<@<3Q"DBM5F<)64S<(01tF(Jj%ATV@$Gl.

Heading over to http://chals.tisc23.ctf.sg:45938 and entering the funny string as the password, we obtain a Discord server invite link, as well as a Discord bot token hidden in a HTML comment. Now the real challenge begins.

Upon joining the Discord server, there doesn't seem to be any channels, or any sign of the flag.

Using the Nextcord Python library, I used the supplied bot token to login as the bot and list channels:

py
import nextcord

intent = nextcord.Intents.default()
bot = commands.Bot(intents=intent)

@bot.event
async def on_ready():
    channels = bot.guilds[0].channels
    print(channels)
    
    print(f'We have logged in as {bot.user}')

bot.run('token')

This revealed the existence of a meeting-records channel, as well as a flag channel.

I then proceeded to list the messages in each of this channels. Unfortunately, the bot did not have permission to read messages in the flag channel, but there was one message in the meeting-records channel with type=<MessageType.thread_created: 18> , indicating that this message was the start of a discord thread.

I then proceeded to list the contents of the thread:

py
meetings = channels[-2]
async for message in meetings.history(limit=100):
    if message.flags.has_thread:
        async for m in message.thread.history(limit=100, oldest_first=True):
            print(m.content)

An interesting story was revealed:

Anya: (Excitedly bouncing on her toes) Mama, Mama! Guess what, guess what? I overheard Loid talking to Agent Smithson about a new mission for their spy organization PALINDROME!
Yor: (Smiling warmly) Really, Anya? That's wonderful! Tell me all about it.
Anya: (Whispers) It's something about infiltrating Singapore's cyberspace. They're planning to do something big there!
Yor: (Intrigued) Oh, that sounds like a challenging mission. I'm sure your Papa will handle it well. We'll be cheering him on from the sidelines.
Anya: (Nods) Yeah, but Papa said it's a complicated operation, and they need some special permission with the number '66688' involved. I wonder what that means.
Yor: (Trying not to give too much away) Hmm, '66688,' you say? Well, it's not something I'm familiar with. But I'm sure it must be related to the clearance or authorization they need for this specific task. Spies always use these secret codes to communicate sensitive information.
Anya: (Eager to help) I want to help Papa with this mission, Mama! Can we find out more about it? Maybe there's a clue hidden somewhere in the house!
Yor: (Playing along) Of course, my little spy-in-training! We can look for any clues that might be lying around. But remember, we have to be careful not to interfere with Papa's work directly. He wouldn't want us to get into any trouble.
Anya: (Giggling) Don't worry, Mama, I won't mess up anything. But I really want to be useful!
Yor: (Pats Anya's head affectionately) You already are, Anya. Just by being here and supporting us, you make everything better. Now, let's focus on finding that clue. Maybe it's hidden in one of your favorite places.
Anya: (Eyes lighting up) My room! I'll check there first!
(Anya rushes off to her room, and after a moment, she comes back with a colorful birthday invitation. Notably, the invitation is signed off with: client_id 1076936873106231447)
Anya: (Excitedly) Mama, look what I found! It's an invitation to a secret spy meeting!
Yor: (Pretending to be surprised) Oh, my goodness! That's amazing, Anya. And it's for a secret spy meeting disguised as your birthday party? How cool is that?
Anya: (Giggling) Yeah! Papa must have planned it for me. But, Mama, it's not my birthday yet. Do you think this is part of their mission?
Yor: (Nods knowingly) You might be onto something, Anya. Spies often use such clever tactics to keep their missions covert. Let's keep this invitation safe and see if anything happens closer to your supposed birthday.
Anya: (Feeling important) I'll guard it with my life, Mama! And when the time comes, we'll be ready for whatever secret mission they have planned!
Yor: (Hugging Anya gently) That's the spirit, my little spy. We'll be the best team and support Papa in whatever way we can. But remember, we must keep everything a secret too.
Anya: (Whispering) I promise, Mama. Our lips are sealed!

There's a few bits of interesting information here:

  • they need some special permission with the number '66688' involved. I wonder what that means.

    • According to this website, the number 66688 corresponds with the permission integer for "View channels", "View audit log", and "Read message history"
  • the invitation is signed off with: client_id 1076936873106231447

So the only unexplored permission is "View audit log". Let's have a look:

image-20230927212257049

Hmm it seems somehow the BetterInvites Bot is giving users the 'Admin' role. According to the bot's documentation, it assigns roles to users when they click the right invite link.

Scrolling all the way to the start of the audit log, we observe a series of invites being created (or deleted):

image-20230927213918344

In fact, there are only two invite links that are still active, one of which is the one we used to join the server. After leaving the server and rejoining using the other invite link, we can now view the flag channel:

img