惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
Netflix TechBlog - Medium
Blog — PlanetScale
Blog — PlanetScale
月光博客
月光博客
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
爱范儿
爱范儿
量子位
博客园 - 聂微东
Engineering at Meta
Engineering at Meta
WordPress大学
WordPress大学
GbyAI
GbyAI
MyScale Blog
MyScale Blog
IT之家
IT之家
P
Proofpoint News Feed
M
MIT News - Artificial intelligence
The Cloudflare Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Hugging Face - Blog
Hugging Face - Blog
The Register - Security
The Register - Security
Microsoft Security Blog
Microsoft Security Blog
博客园_首页
MongoDB | Blog
MongoDB | Blog
F
Fortinet All Blogs
博客园 - 三生石上(FineUI控件)
Y
Y Combinator Blog
雷峰网
雷峰网
V
Visual Studio Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
Last Week in AI
Last Week in AI
博客园 - 叶小钗
D
DataBreaches.Net
B
Blog
B
Blog RSS Feed
大猫的无限游戏
大猫的无限游戏
aimingoo的专栏
aimingoo的专栏
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
The GitHub Blog
The GitHub Blog
云风的 BLOG
云风的 BLOG
Recent Announcements
Recent Announcements
阮一峰的网络日志
阮一峰的网络日志
小众软件
小众软件
腾讯CDC
T
Threat Research - Cisco Blogs
SecWiki News
SecWiki News
Martin Fowler
Martin Fowler
D
Docker
Cisco Talos Blog
Cisco Talos Blog
T
Tenable Blog
Webroot Blog
Webroot Blog
宝玉的分享
宝玉的分享

JieJiSS' Blog

为 SQLAlchemy Model 添加 type hint 和 type check Boot an Arch Linux RISC-V using qemu-system 关于基于机器学习的多目标对象追踪算法的文献综述 - JieJiSS' Blog RV64 板子更换 rootfs 指南 - JieJiSS' Blog A RISC-V gcc pitfall revealed by a glibc update Setup an Arch Linux RISC-V Development Environment 解构赋值踩坑:神秘失踪的数据 - JieJiSS' Blog How's the GDPR (used to) related to my blog Rust is incompatible with LLVM, at least partially python-mtrpacket 在 riscv64 上编译测试不通过 - JieJiSS' Blog 快速获得 RISC-V 开发环境 - JieJiSS' Blog 在 RISC-V 上编译 Node.js 16 Redmi AirDots 3 Pro 连接电脑后蓝牙类别不正确的解决方案 为什么 ArrowFunction 不能 new - JieJiSS' Blog Bilibili 2020-10-24 CTF WriteUp - JieJiSS' Blog 对于又一种对SS流式加密的攻击(奇虎团队)的详细分析 - JieJiSS' Blog 在MacOS上配置dnscrypt-proxy(免分流) - JieJiSS' Blog 市县中心点坐标(经纬度) - JieJiSS' Blog 二项式定理及其推广公式的一种简单理解办法 - JieJiSS' Blog
ACTF2022 safer-tg-bot-{1,2} WP - JieJiSS' Blog
2022-07-23 · via JieJiSS' Blog

safer-telegram-bot-1

  1. Search for flag1 in the source code
1
const user1 = createUser(~~(1 + Math.random() * 1000000), "test", fs.readFileSync(__dirname + "/flag1.txt", "utf8"));
  1. Search for user1.flag
1
2
3
4
5
6
7
8
9
10
11
12
13
bot.on("callback_query", async (query) => {
const callbackData = query.data || "";
const userId = parseInt(callbackData.split("_")[0]);
if(userId !== user1.uid) {
return await sendMessage(chatId, `...not authorized. ...`);
}
if(!isAuthorizedUid(query.from.id)) {
authorizedUids.push({

});
}
return await sendMessage(chatId, "...Your flag is `" + toSafeCode(user1.flag) + "`", ...);
}
  1. Read the Telegram Bot API document

CallbackQuery

This object represents an incoming callback query from a callback button in an inline keyboard.

Field Type Description
id String Unique identifier for this query
from User Sender
data String Optional. Data associated with the callback button. Be aware that the message originated the query can contain no callback buttons with this data.

Conclusion: We need to make sure that in the provided callback_data, the substring before the first _ equals to user1's uid.

We can tell that the callback data is set in the handler of /login, and there are three types of them:

  • "0_login_callback:" + msg.chat.id + ":" + msg.message_id
  • authorizedUids[0].uid + "_login_callback:" + msg.chat.id + ":" + msg.message_id
  • "-1_login_callback:" + msg.chat.id + ":" + msg.message_id

Hence, we only need to click the button exactly when the second kind of callback data appears. Under the competition environment, the time frame available for this is about 400ms. Since the first type of callback data will last for 2 seconds to 16 seconds, trying to click the button with human hands and expecting the flag to appear is probably not feasible.

After a quick search in Google, we can find two major automated Telegram MTProto API Framework: Telethon and Pyrogram. Here, a solution based on pyrogram is provided:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import asyncio
from pyrogram import Client, filters

api_id = YOUR_API_ID
api_hash = "YOUR_API_HASH"

bot = Client("my_account", api_id, api_hash)

@bot.on_edited_message()
def auth(client, message):
if message.chat.username == "actfgamebot01bot":
print("attempting to login as user1...")
if message.reply_markup and message.reply_markup.inline_keyboard:
client.request_callback_answer("actfgamebot01bot", message.id, message.reply_markup.inline_keyboard[0][0].callback_data)

bot.run()

The above code will be triggered twice per a /login's response message, but that's OK.

safer-telegram-bot-2

There are 3 expected methods to solve this challenge.

Background

root user's userid is set to 777000, which is the same as Telegram official account's userid. In other words, we need to let the official account send /iamroot to the bot. This is not quite possible; however, if we search for "Telegram 777000" on Google, we can find a GitHub issue: [BUG] PTB detect anonymous send channel as 777000. By observing the screenshot, we can see that when a channel is linked to a group (see also: Discussion Groups), messages sent in the channel will be automatically forwarded to the discussion group. This forward operation is actually done by user 777000, which means that bot will think this message comes from Telegram's official account.

But the exploit is not so easy. If we invite the bot to a group, it will quit automatically:

1
2
3
4
5
6
7
8
bot.on("my_chat_member", async (update) => {

if(String(update.chat.id).startsWith("-100")) {
await sendMessage(update.chat.id, "This bot is not allowed to join groups");
await bot.leaveChat(update.chat.id);
return;
}
});

Thus, the problem becomes "how to stop the bot from quitting groups".

Solution 1: I'm a Telegram Mechanism Expert

We may recognize that in the callback function bind to the my_chat_member event, an if statement is used to check whether update.chat.id starts with -100. Telegram's groups and channels use merely the same underlying codes, and their chatIds both start with -100. However, people familiar with Telegram will know that not all groups starts with -100. This is caused by one of the history burdens of Telegram. Specifically, Telegram has two types of chats: group and supergroup. Supergroup supports more functions in comparison with group, e.g. setting admins with different admin rights, linking to a channel to act as it's discussion group, obtaining a group username so that it becomes a public group, preserving all history messages, etc. The Telegram dev team is devoting much efforts to hide the UX difference between groups and supergroup. Newly created chats are all groups by default, which has negative chatId but not starting with -100 (Aha!), and will escalate to supergroup automatically when users try to perform actions that are not supported by groups on it. Note that during the escalation process, the group (which is becoming a supergroup) will discard its old chatId and obtain a new one, which starts with -100.

Knowing this, it is not hard to come up with a viable solution:

  1. Create an ordinary group
  2. Invite the bot into this group
  3. Link the group to your channel, so that the group becomes a discussion group, which is necessarily a supergroup.
  4. At this point, an automatic escalation will happen on the group. The client will prompt the user whether the previous 100 messages is visible to the bot.
    • If you choose false, then everything works fine;
    • Otherwise, the bot will receive those messages (as Updates) again, which will probably trigger the my_chat_member callback again, resulting in the bot leaving the group (because the now supergroup has a chatId starting with -100). To avoid this consequence, you can send 100 garbage messages prior to linking the chat to your channel.
  5. Send /iamroot in your channel, and receive flag2.

Solution 2: Prototype Pollution

This path is added for those not familiar with Telegram.

Diving into the handler of /addkw key reply command, we can discover that the program tries to write the reply specified by the user into the corresponding entry of user1's keywordMap:

1
2
3
4
5
6
onText(/^\/addkw (\S+) (\S+)/, async (msg, match) => {
const keyword = match[1];
const reply = match[2];
user1["keywordMap?." + keyword] = () => reply;
await sendMessage(msg.chat.id, "success");
});

Noticing keywordMap?. looks suspicious, let's have a quick glance at its definition:

1
2
3
4
5
6
7
8
9
10
get(target, prop) {
const paths = prop.split("?.");
let current = target;
for (const path of paths) {
current = current[path];
if(!current)
return undefined;
}
return current;
}

Inside the getter function, the key is split at ?. , before accessing corresponding values layer-by-layer. By doing so, it implements something similar to the ?. optional chaining operator. However, here it does not filter the key to be accessed, hence we can construct a prototype pollution. For instance, we set the key to be __proto__, and now we can overwrite Object.prototype.

Send /addkw __proto__?.test 1 to bot, and we can pollute Object.prototype.test

1
2
const a = {};
console.log(a.test);

Read the source code of node-telegram-bot-api, and we can know that the framework tries to determine Update type by a series of ifs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

const pollAnswer = update.poll_answer;
const chatMember = update.chat_member;
const myChatMember = update.my_chat_member;

} else if (pollAnswer) {
debug('Process Update poll_answer %j', pollAnswer);
this.emit('poll_answer', pollAnswer);
} else if (chatMember) {
debug('Process Update chat_member %j', chatMember);
this.emit('chat_member', chatMember);
} else if (myChatMember) {
debug('Process Update my_chat_member %j', myChatMember);
this.emit('my_chat_member', myChatMember);

Obviously, we can pollute any attribute access operation before update.my_chat_member, e.g. chat_member, so that the handler of my_chat_member will never be invoked:

1
/addkw __proto__?.chat_member 1

Solution 3: Race Condition

If the method of racing condition is to be carried out, some special techniques might be needed. The very first Update the bot will receive after it enters the group is always the Update representing the bot's join chat event, hence making it impossible for other callbacks to be triggered before my_chat_member. What's more, the auto-forwarding of channel messages to linked discussion groups in Telegram has a noticeable lag, so if the attacker invite the bot prior to sending the message in channel, the exploitation will never success.

So, we need to send /iamroot in the channel first, and after sleeping for a proper duration, we'll invite the bot to join the discussion group, so that this message is forwarded to the group between the asynchronous my_chat_member handler's await sendMessage and await bot.leaveChat call.

来源:https://blog.jiejiss.com/