Educational Content: This article is written for educational purposes to help developers and cybersecurity students understand software concepts. Always follow ethical guidelines and applicable laws.
Telegram bots have quietly become one of the most powerful tools in a developer's toolkit. Shop bots, reseller panels, OTP delivery, server alerts, file sharing, payment notifications — if you've been building anything in the past two or three years, there's a decent chance a Telegram bot is somewhere in your stack.
They're also one of the most commonly misconfigured pieces of infrastructure I come across. And when a Telegram bot is misconfigured, the consequences range from annoying to genuinely damaging — leaked user data, unauthorized commands, bots turned into spam tools, entire seller panels exposed.
This post covers how attackers actually go after Telegram bots, the mistakes that make it easy, and concrete steps to build something that holds up.
How Telegram Bots Work — The Part That Matters for Security
When you create a bot with BotFather, you get a token. That token is the bot. Anyone who has it can send messages as your bot, read every message your bot receives, modify settings, and add your bot to groups. There's no username/password separation. No 2FA. The token is the complete credential.
This is important to internalize because most bot security problems trace back to one thing: the token ended up somewhere it shouldn't have.
Common ways this happens:
- Token hardcoded in source code that got pushed to a public GitHub repository
- Token stored in a
.envfile that's inside the web root and accessible via browser - Token passed as a GET parameter in webhook URLs that show up in server logs
- Token shared in a Telegram group or Discord server during debugging
- Token in a config file with permissions set to 644 (world-readable)
If your token leaks, an attacker doesn't need to hack anything. They just use the Telegram API directly. They can read your bot's message history, impersonate it, or quietly redirect your webhook.
The Webhook Interception Problem
Most production bots use webhooks — Telegram sends updates to your server via HTTPS when something happens. This is efficient and real-time. It's also something developers frequently set up insecurely.
The vulnerability looks like this: your webhook URL is something like https://yoursite.com/bot/webhook.php with no verification that the incoming request actually came from Telegram. Anyone who discovers that URL can POST fake updates to it and trigger whatever your bot would normally do — send messages, process orders, run commands.
Telegram gives you a clean fix for this. When you set a webhook, you can set a secret token:
// Setting webhook with secret token via Telegram API
$botToken = 'YOUR_BOT_TOKEN';
$webhookUrl = 'https://yoursite.com/bot/webhook.php';
$secretToken = bin2hex(random_bytes(32)); // generate and store this securely
file_get_contents(
"https://api.telegram.org/bot{$botToken}/setWebhook" .
"?url=" . urlencode($webhookUrl) .
"&secret_token=" . $secretToken
);
Then in your webhook handler, verify every incoming request:
<?php
$secretToken = 'YOUR_STORED_SECRET_TOKEN';
$incomingToken = $_SERVER['HTTP_X_TELEGRAM_BOT_API_SECRET_TOKEN'] ?? '';
if (!hash_equals($secretToken, $incomingToken)) {
http_response_code(403);
exit('Forbidden');
}
$update = json_decode(file_get_contents('php://input'), true);
// process update safely
Now your webhook only processes requests that Telegram signed with your secret. Any fake POST from an attacker gets rejected immediately.
Command Injection Through Bot Messages
This one is subtle but shows up in bots that do anything with user-provided input — file names, search queries, system commands.
Imagine a bot that lets admins run a server status command by typing /status. A naive implementation might look like:
$command = $message_text; // user sent "/status && cat /etc/passwd"
shell_exec($command);
That's obviously dangerous. But the same class of problem shows up in less obvious places — bots that build SQL queries from message text, bots that use user input to build file paths, bots that pass message content to external APIs without sanitizing it first.
The rule is the same as in any other context: never trust input. Validate it strictly against what you expect before doing anything with it.
$allowed_commands = ['/start', '/status', '/help', '/balance'];
$incoming = trim($message_text);
if (!in_array($incoming, $allowed_commands, true)) {
sendMessage($chat_id, "Unknown command.");
exit;
}
// now safe to act on $incoming
For bots that take dynamic input — like a username or order ID — validate the format before using it:
$order_id = trim($message_text);
if (!preg_match('/^\d{1,10}$/', $order_id)) {
sendMessage($chat_id, "Please send a valid order ID.");
exit;
}
// safe to use $order_id in a query with a prepared statement
Privilege Escalation in Group Bots
If your bot operates in a group and has admin commands, this is a real attack vector. The pattern goes like this: an attacker joins a group your bot is in, sends a command like /makeadmin 12345678, and if your bot doesn't properly verify who is allowed to run that command, it processes it.
Always verify the sender's Telegram user ID against a whitelist before processing privileged commands:
$admin_ids = [123456789, 987654321]; // your actual admin Telegram user IDs
$sender_id = $update['message']['from']['id'];
function isAdmin($sender_id, $admin_ids) {
return in_array($sender_id, $admin_ids, true);
}
if (str_starts_with($message_text, '/makeadmin')) {
if (!isAdmin($sender_id, $admin_ids)) {
sendMessage($chat_id, "You don't have permission to do that.");
exit;
}
// process admin command
}
Store admin IDs in your config or database — never derive them from the message itself. An attacker can send any user ID they want in a message payload.
Rate Limiting on Bot Commands
Bots that interact with databases or external APIs without rate limiting are trivially easy to abuse. Someone writes a loop that sends your bot 500 requests per second, your database gets hammered, your hosting gets flagged, or your third-party API quota gets burned through.
A basic per-user rate limiter for a Telegram bot:
function botRateLimit($user_id, $db, $max = 10, $window = 60) {
$stmt = $db->prepare(
"SELECT count FROM bot_rate_limits
WHERE user_id = ? AND window_start > NOW() - INTERVAL ? SECOND"
);
$stmt->execute([$user_id, $window]);
$row = $stmt->fetch();
if ($row && $row['count'] >= $max) {
return false;
}
$db->prepare(
"INSERT INTO bot_rate_limits (user_id, count, window_start) VALUES (?, 1, NOW())
ON DUPLICATE KEY UPDATE count = count + 1"
)->execute([$user_id]);
return true;
}
if (!botRateLimit($sender_id, $db)) {
sendMessage($chat_id, "Slow down! Too many requests.");
exit;
}
Storing the Bot Token Securely
Your bot token should never be in your source code. Not even in a private repository — private repos get misconfigured, collaborators get compromised, and repositories get accidentally made public.
The correct approach on a PHP + Hostinger setup:
// config.php - outside web root ideally, or protected by .htaccess
define('BOT_TOKEN', getenv('TELEGRAM_BOT_TOKEN'));
Set the environment variable in your hosting control panel or in a .env file that is never inside public_html. If you must keep it inside public_html, protect it via .htaccess:
<Files ".env">
Order allow,deny
Deny from all
</Files>
<Files "config.php">
Order allow,deny
Deny from all
</Files>
And set the file permissions to 600 — readable only by the owner, not by the web server process or other users.
What To Do If Your Token Leaks
First — don't panic, but act fast. Go to BotFather immediately and use /revoke on the bot. This invalidates the old token and generates a new one. Update your webhook and config with the new token.
Then audit what happened. Check your server logs for any requests made using the compromised token. If your bot handles payments or user data, check whether any transactions look suspicious. If user data was accessed, you may need to notify affected users depending on your local regulations.
Finally — rotate any other credentials that were stored alongside the token. If your config file leaked, assume everything in it is compromised.
The Bottom Line
Telegram bots are genuinely useful. They're also fast to build, which means security shortcuts are easy to take and easy to miss. The issues covered here — token exposure, missing webhook verification, no input validation, no rate limiting, no privilege checks — aren't exotic vulnerabilities. They're basics that get skipped when someone's moving fast.
The good news is that fixing all of them adds maybe a few hours to a project. The cost of not fixing them — a compromised bot, leaked user data, burned API quotas, a defaced seller panel — is much higher.
Build the extra hour in. It's worth it every single time.
— JSHook Team