前言

写 CTF EXP 和代码审计时,Python 的各种内置函数和模块特性经常需要速查。本文按主题整理了 Python 最常用的 40+ 个知识点——从模块导入到反射、从迭代器到装饰器、从 Flask 到 FastAPI——每个都附带可直接复制的代码示例。


一、模块与包

1.1 模块——单个 .py 文件

1
2
3
4
5
6
7
# 文件: my_module.py
def hello():
return "Hello, World!"

# 使用:
# import my_module
# my_module.hello()

每个模块都有一个内置属性 __name__

  • 当模块被直接运行时,__name__ 被设置为 '__main__'
  • 当模块被导入时,__name__ 被设置为模块本身的名称
1
2
3
# 文件末尾常见写法:
if __name__ == '__main__':
main()

1.2 包——含 __init__.py 的目录

1
2
3
4
my_package/
├── __init__.py
├── module_a.py
└── module_b.py
1
2
3
# 使用:
import my_package.module_a
from my_package import module_b

1.3 importlib —— 动态导入模块

1
2
3
4
5
6
7
8
import importlib

# 动态导入
math_module = importlib.import_module('math')
print(math_module.sqrt(16)) # 4.0

# 重新加载模块
importlib.reload(math_module)

二、动态调用——反射三大方法

2.1 hasattr —— 检查是否拥有属性/方法

1
2
3
4
5
6
7
8
class Person:
def __init__(self):
self.name = "Alice"

p = Person()
print(hasattr(p, 'name')) # True
print(hasattr(p, 'age')) # False
print(hasattr(p, '__init__')) # True

2.2 getattr —— 动态获取属性值

1
2
3
4
5
6
7
8
9
class Person:
def __init__(self):
self.name = "Alice"
self.age = 25

p = Person()
print(getattr(p, 'name')) # Alice
print(getattr(p, 'age')) # 25
print(getattr(p, 'gender', 'unknown')) # unknown(默认值)

与字符串反转结合绕过关键字检测:

1
2
getattr(obj, 'metsys'[::-1])('whoami')
# 等价于 obj.system('whoami')

2.3 setattr —— 动态设置属性

1
2
3
4
5
6
7
8
class Config:
pass

config = Config()
setattr(config, 'port', 8080)
setattr(config, 'debug', True)
print(config.port) # 8080
print(config.debug) # True

2.4 type() —— 动态创建类

1
2
3
4
5
6
7
8
9
10
User = type('User', (object,), {
'uname': 'test',
'is_admin': 0,
'__repr__': lambda o: o.uname, # o 表示传入类实例本身
})

u = User()
print(u.uname) # test
print(u.is_admin) # 0
print(u) # test(调用 __repr__)
参数 含义
'User' 类名
(object,) 继承的父类(元组)
{...} 类属性和方法的字典

三、函数与迭代器

3.1 lambda —— 匿名函数

1
2
3
4
5
6
7
8
9
# def 写法
def add(x, y):
return x + y

# lambda 等价写法
add_lambda = lambda x, y: x + y

print(add(3, 5)) # 8
print(add_lambda(3, 5)) # 8

直接调用:

1
(lambda x: x * 2)(5)  # 10

高阶函数配合:

1
2
3
4
numbers = [1, 2, 3, 4, 5, 6]
even_numbers = filter(lambda x: x % 2 == 0, numbers)
print(list(even_numbers)) # [2, 4, 6]
# lambda 默认返回布尔值:满足条件 True,不满足 False

3.2 装饰器

基本结构:

1
2
3
4
5
6
7
def 装饰器名(被装饰函数):
def 包装函数(*args, **kwargs):
# 装饰逻辑(权限检查、日志记录等)
result = 被装饰函数(*args, **kwargs)
# 后续逻辑(结果处理、统计等)
return result
return 包装函数

实战示例:

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

def log_time(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f"耗时: {time.time() - start:.2f}s")
return result
return wrapper

@log_time # 等价于 add = log_time(add)
def add(a, b):
return a + b

print(add(2, 3)) # 输出: 耗时: 0.00s \n 5

3.3 迭代器

Python 中许多内置对象都是可迭代的,可以用 iter() 获取其迭代器,用 next() 逐元素消费。

文件迭代器:

1
2
3
with open('test.txt', 'r') as file:
print(next(file)) # 第一行
print(next(file)) # 第二行

字典迭代器:

1
2
3
4
5
6
7
8
9
my_dict = {'a': 1, 'b': 2, 'c': 3}

key_iterator = iter(my_dict) # 遍历键
value_iterator = iter(my_dict.values()) # 遍历值
item_iterator = iter(my_dict.items()) # 遍历键值对

print(next(key_iterator)) # 'a'
print(next(value_iterator)) # 1
print(next(item_iterator)) # ('a', 1)

range / 字符串 / 集合迭代器:

1
2
3
4
5
6
7
8
9
10
11
12
# range
range_iter = iter(range(5))
print(next(range_iter)) # 0

# 字符串
str_iter = iter("hello")
print(next(str_iter)) # 'h'

# 集合(无序!)
my_set = {1, 2, 3}
set_iter = iter(my_set)
print(next(set_iter)) # 1、2 或 3(无序)

3.4 compile + exec

compile 接受三个参数:

参数 含义
code_str 要执行的代码字符串
filename 命名,方便报错定位(如 "test"
mode 'exec' / 'eval' / 'single'——与后面执行用的函数保持一致
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
locals_dict = {}

code_str = '''
a = 1 # 局部变量(存入 locals_dict)
print(a) # 如果 __builtins__ 被设为 None,这行会报错!
'''

code = compile(code_str, "test", "exec")

try:
exec(code, {"__builtins__": None}, locals_dict)
except NameError as e:
print("报错:", e)
# 报错:name 'print' is not defined
print("locals_dict 中的 a:", locals_dict.get("a")) # 输出: 1

exec 三个参数:

参数 含义
code 要执行的代码字符串或 code 对象
globals 全局命名空间字典,代码中可以直接用里面的变量
locals 局部命名空间字典,执行后可从中取回代码块中赋值的新变量

3.5 ** —— 关键字参数解包

1
2
3
4
5
6
7
8
9
def render_user_template(tpl: str, **context) -> str:
template = _user_tpl_env.from_string(tpl or "")
return template.render(**context)

# 调用
render_user_template("<h1>Hello {{name}}</h1>", name="Alice", age=25, city="Beijing")

# context 内部:
# {"name": "Alice", "age": 25, "city": "Beijing"}

3.6 海象表达式(:=

1
2
3
4
5
6
7
8
9
# 海象运算符是表达式,有返回值
x = (y := 5) + 3
print(x) # 8
print(y) # 5

# 等价于:
temp = 5
y = temp
x = temp + 3

实战用法(栈帧逃逸精简 payload 中):

1
2
{}[[*((l := []).append(...) or l[0])][0]]
# ↑ 海象表达式:同时赋值给 l 并返回 l

四、字典与字符串

4.1 items() 方法

1
2
3
4
5
6
7
8
src = {'name': 'Alice', 'age': 25, 'city': 'New York'}

items = src.items()
print(items)
# dict_items([('name', 'Alice'), ('age', 25), ('city', 'New York')])

for key, value in src.items():
print(f"{key}: {value}")

4.2 get() 方法

1
2
3
4
d = {'a': 1}
print(d.get('a')) # 1
print(d.get('b')) # None
print(d.get('b', 404)) # 404(默认值)

4.3 字符串常用方法

1
2
3
4
5
6
7
8
path = "////etc/passwd"
print(path.lstrip("/")) # 'etc/passwd' —— 一直删除左边的 / 直到遇到非 / 字符

s = "a/b/c"
parts = s.split("/", 1) # 根据 / 切一刀 → ['a', 'b/c']

s = "123"
print(s.isdigit()) # True

五、路径与文件操作

5.1 os.path 常用函数

1
2
3
4
5
6
7
import os

os.path.join('static/', path) # 路径拼接
os.path.exists(path) # 是否存在
os.path.isdir(path) # 是否是目录
os.path.dirname(path) # 取父目录
os.path.commonpath([a, b]) # 两个路径的最长公共前缀

目录拼接漏洞: os.path.join(UPLOAD_FOLDER, filename) 中,如果 filename/ 开头(绝对路径),os.path.join 会忽略前面的路径,直接使用该绝对路径。这是 Zip Slip 和路径穿越的经典原因。

5.2 pathlib.Path

1
2
3
4
5
6
7
8
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent # 当前文件所在目录的绝对路径
Path('/etc/passwd') # 创建 Path 对象
Path('/static/index.html').is_absolute() # 判断是否绝对路径

current = Path("/a/b/c")
print(current.relative_to("/a")) # b/c —— 计算相对路径
方法 作用
.resolve() 解析为绝对路径
.parent 获取父目录
.is_absolute() 判断是否绝对路径
.relative_to(base) 计算相对于 base 的路径

path.parent.mkdir(parents=True, exist_ok=True) — 创建的是 Path 对象的父目录;os.makedirs(target, exist_ok=True) — 创建的是 target 目录本身。

5.3 with 语句 —— 自动关闭资源

1
2
3
4
5
6
7
8
9
# 普通写法(需手动关闭)
file = open("test.txt", "r")
content = file.read()
file.close() # 容易忘记!

# with 写法(自动关闭)
with open("test.txt", "r") as file:
content = file.read()
# 此处文件已自动关闭

with 不仅用于文件, socketzipfile.ZipFilesubprocess.Popen 等也支持。

5.4 zipfile —— ZIP 压缩包操作

1
2
3
4
5
6
import zipfile

with zipfile.ZipFile(zip_path, "r") as zf:
for info in zf.infolist(): # 遍历包内每个文件
name = info.filename
print(name)

Zip Slip 漏洞: ZIP 包内文件名如果包含 ../,解压时可能写入任意目录。应在解压前检查 info.filename 是否有路径穿越风险。

5.5 tempfile —— 临时文件

1
2
3
4
5
6
7
8
import tempfile

self.temp_file = tempfile.NamedTemporaryFile(
mode='w',
suffix='.py',
dir='/tmp',
delete=False # 程序运行完后不删除
)

六、网络请求

6.1 requests 基本用法

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

# GET
resp = requests.get(url, headers=headers, timeout=5, verify=False)
# verify=False: 忽略 HTTPS 证书验证

# POST(JSON)
data = {'key1': 'value1', 'key2': 'value2'}
resp = requests.post(url, json=data)

# POST(表单)
resp = requests.post(url, data=data)

常见 HTTP 状态码:200(成功)、301/302(重定向)、401(未授权)、403(禁止)、404(不存在)、500(服务器错误)。

代理:

1
2
3
4
5
proxies = {
"http": "http://xx.xx.xx.xx:80",
"https": "https://127.0.0.1:443"
}
requests.get(url, headers=headers, proxies=proxies)

6.2 requests.Session —— 保持会话

1
2
3
4
session = requests.Session()
response1 = session.get("https://example.com") # 首次连接
response2 = session.get("https://example.com") # 复用 TCP 连接 + Cookie
session.close() # 必须手动关闭!

6.3 urllib —— 原生 HTTP 请求

1
2
3
4
5
6
7
8
9
10
import urllib.request
import urllib.error

try:
req = urllib.request.Request(url, headers={"User-Agent": "question-app/1.0"})
with urllib.request.urlopen(req, timeout=3) as response:
data = response.read().decode('utf-8')
status = response.status
except urllib.error.URLError as e:
print(f"Error: {e}")

urllib.request.urlopen 的两个特性:

  1. 可以通过 file:/// 协议读取本地文件(绕过某些 WAF)
  2. 内部会对 URL 做一次 decode——如果服务端也 decode 一次,可以用二次编码绕过 WAF

七、Flask 专题

7.1 Flask app 对象结构

1
2
3
4
5
6
7
app(Flask 应用对象)
├─ config(配置字典)
├─ url_map(路由表)
└─ jinja_loader(模板加载器对象)
├─ encoding(编码设置)
└─ searchpath(搜索路径列表)
└─ [/var/www/html/templates]

7.2 常见敏感文件

文件 内容
/etc/passwd 系统用户信息(用户名:口令:UID:GID:注释:主目录:Shell
/proc/self/cmdline 当前进程的启动命令(如 python /app/app.py
/proc/self/environ 当前进程环境变量(含 SECRET_KEY
/proc/self/maps 当前进程内存映射(结合 /proc/self/mem 读内存)
/proc/self/cwd 当前工作目录的软链接
/proc/self/fd/<N> 打开的文件描述符

7.3 flask.request 对象

1
2
3
4
5
6
7
from flask import request

request.path # 请求路径(不含参数)
request.full_path # 请求路径 + 参数
request.args.get('name') # GET 参数
request.form.get('username') # POST 表单参数
request.files.get('plugin') # 上传的文件对象

文件保存:

1
2
3
file = request.files.get('plugin')  # <input type="file" name="plugin">
saved = UPLOAD_DIR / f"{uuid4().hex}-{filename}"
file.save(saved)

7.4 url_for —— 动态生成 URL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from flask import Flask, url_for

app = Flask(__name__)

@app.route('/user/<username>')
def user_profile(username):
return f"这是 {username} 的个人主页"

# 生成相对路径
link = url_for('user_profile', username='zhangsan')
print(link) # /user/zhangsan

# 特殊字符自动编码
link2 = url_for('user_profile', username='li si')
print(link2) # /user/li%20si

# 生成绝对路径(含域名)
link3 = url_for('user_profile', username='zhangsan', _external=True)
# → http://localhost:5000/user/zhangsan

7.5 flask.make_response —— 构造响应

1
2
3
4
5
from flask import make_response

make_response("Hello World", 200)
make_response("<h1>Hello</h1>", 200)
make_response(flask.redirect(next_url))

flask.redirect 如果 next_url 可控,可以跳转到恶意网站(Open Redirect)。

7.6 SSTI 关联——模板渲染

1
2
3
4
person = request.args.get('name')
template = '<h2>%s!</h2>' % person
return render_template_string(template)
# ↑ 用户输入直接拼入模板 → SSTI!

7.7 正则黑名单绕过示例

Flask 应用中常见的 WAF 写法:

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

blacklist = ['/', 'flag', 'cat', '+']
cmd = input()

for word in blacklist:
# (^|[^\w]) 单词起始边界
# ([^\w]|$) 单词结束边界
pattern = r'(^|[^\w]){}([^\w]|$)'.format(re.escape(word))
if re.search(pattern, cmd):
return "执行失败"

re.escape(word) 对关键字做转义防止被当作正则特殊字符。(^|[^\w])([^\w]|$) 确保是完整单词匹配而不是子串——例如 flag 不会误杀 flag_content


八、命令执行速查

8.1 os 模块

1
2
3
4
5
6
import os

os.listdir('/') # 列出目录
os.popen('bash') # 执行命令并返回管道
os.unlink('1.txt') # 删除文件
os.environ['KEY'] # 获取环境变量

os.popen2 / os.popen3 / os.popen4 可以替代 os.popen

8.2 subprocess.run

1
2
3
4
5
import subprocess

subprocess.run('ls /', shell=True) # shell 字符串方式
subprocess.run(['ls', '/']) # 列表方式(推荐)
subprocess.run(['cmd'], capture_output=True, text=True) # 捕获输出

8.3 Python 中可用于执行系统命令的函数(完整清单)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
eval, execfile, compile, open, file, map, input,
os.system, os.popen, os.popen2, os.popen3, os.popen4, os.open, os.pipe,
os.listdir, os.access,
os.execl, os.execle, os.execlp, os.execlpe, os.execv,
os.execve, os.execvp, os.execvpe, os.spawnl, os.spawnle, os.spawnlp, os.spawnlpe,
os.spawnv, os.spawnve, os.spawnvp, os.spawnvpe,
pickle.load, pickle.loads, cPickle.load, cPickle.loads,
subprocess.call, subprocess.check_call, subprocess.check_output, subprocess.Popen,
commands.getstatusoutput, commands.getoutput, commands.getstatus,
glob.glob, linecache.getline,
shutil.copyfileobj, shutil.copyfile, shutil.copy, shutil.copy2, shutil.move, shutil.make_archive,
dircache.listdir, dircache.opendir,
io.open,
popen2.popen2, popen2.popen3, popen2.popen4,
timeit.timeit, timeit.repeat,
sys.call_tracing,
code.interact, code.compile_command, codeop.compile_command,
pty.spawn,
posixfile.open, posixfile.fileopen,
platform.popen

8.4 base64.b64decode —— 双面行为

1
2
3
4
5
6
import base64

# 输入混合内容,只解码前面能解码的部分,后面自动忽略
base64.b64decode("SGVsbG8gV29ybGQh;cat /flag")
# → b'Hello World!'
# ;cat /flag 被静默忽略!

这个特性在 Python 和 Shell 对 base64 的容错差异场景中是经典的命令注入绕过点。


九、线程与进程

9.1 threading 基本用法

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

# Event 对象:线程间同步
event = threading.Event()

def worker():
print("Worker waiting...")
event.wait() # 阻塞,直到 event.set() 被调用
print("Worker running!")

threading.Thread(target=worker).start()
# Thread 参数:target=函数, args=位置参数元组

input("Press Enter to notify the worker...")
event.set() # 唤醒 worker 线程

9.2 subprocess.Popen —— 子进程管理

1
2
3
4
self.process.poll()                    # 检查进程是否终止
self.process.terminate() # 终止子进程
self.process.stdout.readline() # 读取子进程标准输出的一行
stdout, stderr = self.process.communicate() # 获取标准输出 + 标准错误

十、数据库

10.1 SQLite3

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

conn = sqlite3.connect('test.db')
conn.row_factory = sqlite3.Row # 结果返回类字典对象而非元组
cur = conn.cursor()

# 建表
cur.execute("""
CREATE TABLE IF NOT EXISTS users (
username TEXT PRIMARY KEY,
about TEXT DEFAULT '',
avatar_local TEXT DEFAULT '',
avatar_url TEXT DEFAULT ''
)
""")

# 插入
cur.execute("INSERT OR IGNORE INTO users(username) VALUES (?)", ("admin",))

conn.commit()
conn.close()

row_factory = sqlite3.Row 的作用:

默认(None sqlite3.Row
结果返回元组 (1, 'admin', 25) 结果返回 Row 对象
只能用索引访问 row[0] 可用列名访问 row['username']
列顺序变化会导致索引失效 列名访问不受顺序影响
1
2
3
4
5
6
7
8
conn.row_factory = sqlite3.Row
cur = conn.cursor()
cur.execute("SELECT id, username, age FROM users WHERE id = 1")
row = cur.fetchone()

print(row['id']) # 1
print(row['username']) # admin
print(row[0]) # 1(也支持索引)

10.2 Redis 模块

Redis 常用于 Flask session 存储、缓存和消息队列。在 CTF 中经常配合 SSRF 的 gopher:// 协议进行未授权访问攻击。


十一、其他常用模块速查

11.1 pydash.set_ —— 深层赋值(原型链污染)

1
2
3
4
5
6
7
import pydash

data = {"user": {"name": "Alice"}}
pydash.set_(data, "user.age", 25)
# 也可以 pydash.set_(data, "user[age]", 25)
print(data)
# {"user": {"name": "Alice", "age": 25}}

如果 key 可控则可能造成原型链污染。

11.2 uuid.getnode() —— 获取 MAC 地址

1
2
3
4
5
6
7
import uuid

print(uuid.getnode()) # 返回十进制整数,如 2485377892354

# 等价于从文件读取再换算:
# cat /sys/class/net/eth0/address → 02:42:ac:11:00:02
# int("0242ac110002", 16) → 2485377892354

11.3 random —— 伪随机(可预测)

1
2
3
4
5
import random

random.seed(123)
print(str(random.random() * 233))
# 相同种子 → 相同序列 → 可复现

11.4 re.sub —— 正则替换

1
2
3
4
5
6
import re

s = "hello/world?flag=1"
s = re.sub(r"[^a-zA-Z0-9_\-]", "_", s)
print(s) # "hello_world_flag_1"
# 把所有非字母数字下划线连字符的字符替换为下划线

11.5 socket.getaddrinfo —— DNS 解析

1
2
3
4
import socket

results = socket.getaddrinfo("example.com", None)
# 返回主机名对应的网络地址信息列表,含 IP、地址族、协议等

11.6 FastAPI —— 现代异步 Web 框架

1
2
3
4
5
6
7
8
9
10
11
from fastapi import FastAPI, Request

app = FastAPI(title="admin_panel", docs_url=None, redoc_url=None)

@app.middleware("http")
async def add_service_header(request: Request, call_next):
# call_next 把请求传给后续的路由处理函数
# await 暂停当前函数,等待异步操作完成
response = await call_next(request)
response.headers["X-Service"] = "admin_panel"
return response

参考