Telgram Web challenge

well starting off, you’re met with this messaging app where u could choose a username and speak to them, and you’re also given the source code, with the source code given, analyzing the jwt functionality :
in the decode_token function:

anything weird ?? the public key is being used as the HMAC secret in this line :
expected_sig = hmac.new(PUBLIC_KEY.encode(), signing_input, hashlib.sha256).digest()this can directly make us able to forge an admin token with the public key, called jwt confusion, here’s a quick article on how to achieve it https://hacksudo.medium.com/lab-3-jwt-rs256-to-hs256-key-confusion-forging-admin-token-with-public-key-db30b8d43eb9
anyways, with the public key being directly accessible at /pubkey

we can effectively forge an admin token :

and there we are, now we can grab the first part of the flag :

nmctf{W3_T3ll_T0_Us3_Y0ur_3m41l
now onto the second part, which is the hard part, after reading the source code for a while and comparing inconsistencies, I remark that all endpoints/websocket actions etc.. are requiring auth @require_auth except for two (which were the actual path for the solve):
first one :

join_chat socket didn’t require auth, so I assume that I can possible join a chat I don’t belong to and read sensitive info, which would most likely be one of the chats for the second admin, though we need a chat_id, well it might sound easy now cuz the path is quite linear yet it took a long time, anyways :

this endpoint only requires u to be logged in and that’s it, what’s leaked is the registration timestamps and the user count,

so as u can see on the first day the user count is 2, which is most likely the 2 admin account that hold the info. but we need some valuable info about them, well…
timestamp might not see enough, but

they are the basis for the userid which will be valuable later (you’ll see why)
with timestamps present, we have the formula and well calculate the user id for the second timestamp (which will be the second admin’s userid), since the first admin is now useless,
with that :
>>> print(user_id_from_timestamp("2026-06-18 20:10:43"))5ad1bed6ca45e41ee6b5b840b98bfc51we have the second admin’s user id, but what is that useful for now ? can we get a chat_id with that ?

u technically would think of that, but we don’t know exactly who did he talk to and which chat would contain the flag, so trying to generate the chat_id is most likely to be useless, well
after analyzing the code even more, u dicover this :
the endpoint that supplies me with my chats :

will supply me with my chats, but as u can see, it can take an arbitary id as a query param, it’s not well scoped to my account, it just takes an id and fetches its chat, well, we have the second admin’s user id right ? lets see his chats !

there we are, we got chat id’s , remeber the unauth socket ? now we can use it since we have the chat ids and we can join chats !!
after testing the chat id one by one,

this was the interesting one,
and inside the burp repeater tab (websockets), well do a join_chat operation with that chat id and we’ll be able to join the chat ,

and THERE WE ARE , second part _Bu7_7h47_1d_N33d_T0_B3_53cr37}
so the full flag comes as nmctf{W3_T3ll_T0_Us3_Y0ur_3m41l_Bu7_7h47_1d_N33d_T0_B3_53cr37}
I ended up solving the challenge at the last minute as the second and last solve, this challenge is amazing (especially the second part) , where you chain stuff one by one till u get there, shoutout to the author @H4t3m for this challenge
cya on the next one !