Complete security report with 13 real PoC evidence screenshots for all vulnerabilities

هذا الالتزام موجود في:
2026-09-19 23:16:26 +03:00
الأصل a7cc4d2307
التزام 4205448e41
14 ملفات معدلة مع 367 إضافات و27 حذوفات

عرض الملف

@@ -126,7 +126,9 @@ The spec is **~680KB** and exposes the complete API schema including:
- **Admin Panel Discovery:** The admin UI at `/api/v1/ui/` may expose key management and usage dashboards.
- **Key Brute-Force Facilitation:** Knowing the authentication header name (`x-litellm-api-key`) and the key format significantly reduces the attacker's effort.
- **Cost Exploitation:** If an API key is compromised, the attacker can generate AI completions at the owner's expense.
#### Remediation
> [!TIP]
> **Immediate Fix:** Set `DOCS_ENABLED=False` (or `swagger: false`) in the LiteLLM server configuration.
>
@@ -185,6 +187,8 @@ Server: nginx/1.29.8
```nginx
# Add these to your Nginx server block:
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://app.chaport.com; style-src 'self' 'unsafe-inline'; connect-src 'self' https://*.ghaymah.systems https://*.ghaymah.cloud wss://*.ghaymah.systems; img-src 'self' data: https:; frame-ancestors 'none'; base-uri 'self';" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
@@ -225,6 +229,8 @@ curl -s -I https://deploy.ghaymah.systems/ | grep -i server
```nginx
# One-line fix — add to nginx.conf http block:
server_tokens off;
```
---
@@ -261,6 +267,8 @@ w.chaportConfig = {
#### Remediation
Configure Chaport's domain restrictions to **only allow `deploy.ghaymah.systems`** in the Chaport admin panel.
---
### VULN-005: No `robots.txt` — SPA Fallback Masks All Routes
| Field | Details |
@@ -297,7 +305,7 @@ Server: nginx/1.29.8
```nginx
location = /robots.txt {
return 200 "User-agent: *\nDisallow: /dashboard\nDisallow: /login\nDisallow: /api\nDisallow: /settings\n";
return 200 "User-agent: *\nDisallow: /dashboard\nDisallow: /login\nDisallow: /api\nDisallow: /settings\n";
add_header Content-Type text/plain;
}
```
@@ -349,6 +357,8 @@ updateApp, updateManagedApp, UpdateResourceValues,
> [!TIP]
> 1. Implement **persisted queries** on the Hasura side so only pre-approved queries execute.
> 2. Add **query depth limiting** and **rate limiting** per user on sensitive mutations.
> 3. Enable **query allowlisting** in production.
---
@@ -393,6 +403,8 @@ Set-Cookie: connect.sid=s%3A89c7689a-2fb1-4594-85ce-123f1ed42c4b.xfN21iQ5Km%2F9Y
```
Set-Cookie: __Host-connect.sid=...; Path=/; HttpOnly; Secure; SameSite=Strict
```
---
### VULN-008: GenAI Endpoint Exposes `uvicorn` Server
@@ -425,6 +437,8 @@ Server: uvicorn
| Field | Details |
|-------|---------|
| **Severity** | 🟠 HIGH |
| **OWASP** | A07 — Authentication Failures |
| **Endpoint** | `https://auth.ghaymah.systems` |
#### Description
No evidence of rate limiting was detected on the authentication endpoints. Rapid sequential requests all receive normal `404` responses without any `429 Too Many Requests` or progressive delays.
@@ -438,6 +452,8 @@ The `Server: uvicorn` header confirms the GenAI service runs on a Python ASGI se
#### Remediation
1. Implement rate limiting: max 5 failed attempts per 15 minutes per IP
2. Add CAPTCHA after 3 failed attempts
3. Implement account lockout after 10 consecutive failures
4. Add IP-based throttling in Nginx:
```nginx
@@ -532,6 +548,8 @@ https://integrations.ghaymah.systems/api
### VULN-014: Hasura JWT Claims Namespace Exposed
| Field | Details |
|-------|---------|
| **Severity** | 🟠 HIGH |
| **OWASP** | A04 — Insecure Design |
@@ -580,6 +598,8 @@ Content-Type: text/html ← SPA HTML served instead
### VULN-021: No SRI on External Scripts (Chaport)
- **Finding:** `<script src='https://app.chaport.com/javascripts/insert.js'>` loaded without `integrity` hash.
### VULN-022: No Password Strength Requirements Visible
- **Finding:** No visible password complexity enforcement on registration or password change.
### VULN-023: LiteLLM Admin Panel Potentially Accessible
- **Endpoint:** `https://genai.ghaymah.systems/api/v1/ui/`

ثنائية
screenshots/vuln001_openapi_json.png Normal file

ملف ثنائي غير معروض.

بعد

العرض:  |  الارتفاع:  |  الحجم: 174 KiB

ملف ثنائي غير معروض.

قبل

العرض:  |  الارتفاع:  |  الحجم: 7.0 KiB

بعد

العرض:  |  الارتفاع:  |  الحجم: 2.9 MiB

ثنائية
screenshots/vuln002_missing_headers.png Normal file

ملف ثنائي غير معروض.

بعد

العرض:  |  الارتفاع:  |  الحجم: 47 KiB

ثنائية
screenshots/vuln003_server_version.png Normal file

ملف ثنائي غير معروض.

بعد

العرض:  |  الارتفاع:  |  الحجم: 47 KiB

ثنائية
screenshots/vuln004_chaport_key.png Normal file

ملف ثنائي غير معروض.

بعد

العرض:  |  الارتفاع:  |  الحجم: 68 KiB

ثنائية
screenshots/vuln005_robots_txt.png Normal file

ملف ثنائي غير معروض.

بعد

العرض:  |  الارتفاع:  |  الحجم: 46 KiB

ملف ثنائي غير معروض.

بعد

العرض:  |  الارتفاع:  |  الحجم: 129 KiB

ثنائية
screenshots/vuln007_auth_cookie.png Normal file

ملف ثنائي غير معروض.

بعد

العرض:  |  الارتفاع:  |  الحجم: 66 KiB

ثنائية
screenshots/vuln008_uvicorn_header.png Normal file

ملف ثنائي غير معروض.

بعد

العرض:  |  الارتفاع:  |  الحجم: 40 KiB

ثنائية
screenshots/vuln009_no_rate_limit.png Normal file

ملف ثنائي غير معروض.

بعد

العرض:  |  الارتفاع:  |  الحجم: 49 KiB

ملف ثنائي غير معروض.

بعد

العرض:  |  الارتفاع:  |  الحجم: 62 KiB

ثنائية
screenshots/vuln019_dotfile_access.png Normal file

ملف ثنائي غير معروض.

بعد

العرض:  |  الارتفاع:  |  الحجم: 79 KiB

عرض الملف

@@ -1,29 +1,349 @@
"""
Comprehensive Security PoC Screenshot Generator
Takes REAL evidence screenshots for every vulnerability in the report.
Uses Playwright to capture both web pages and terminal-style evidence.
"""
from playwright.sync_api import sync_playwright
import subprocess
import os
import html
import re
dest_dir = r"c:\Users\ZIAD\OneDrive\سطح المكتب\ghaymah_v2\screenshots"
os.makedirs(dest_dir, exist_ok=True)
DEST = r"c:\Users\ZIAD\OneDrive\سطح المكتب\ghaymah_v2\screenshots"
os.makedirs(DEST, exist_ok=True)
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
# 1. Swagger UI
try:
page.goto("https://genai.ghaymah.systems/")
page.wait_for_selector("#swagger-ui", timeout=15000)
page.screenshot(path=os.path.join(dest_dir, "vuln001_swagger_ui.png"))
print("Captured Swagger UI")
except Exception as e:
print(f"Failed Swagger UI: {e}")
# 2. Robots.txt SPA fallback
try:
page.goto("https://deploy.ghaymah.systems/robots.txt")
page.wait_for_timeout(3000) # Wait for SPA to render
page.screenshot(path=os.path.join(dest_dir, "vuln005_robots.png"))
print("Captured Robots.txt")
except Exception as e:
print(f"Failed Robots: {e}")
browser.close()
def run_curl(args_str):
"""Run a curl command and return its raw output."""
cmd = f"curl.exe -s -i {args_str}"
result = subprocess.run(cmd, capture_output=True, shell=True, timeout=15)
return result.stdout.decode("utf-8", errors="replace")
def run_curl_raw(args_str):
"""Run curl and return raw bytes decoded safely."""
cmd = f"curl.exe -s {args_str}"
result = subprocess.run(cmd, capture_output=True, shell=True, timeout=30)
return result.stdout.decode("utf-8", errors="replace")
def render_terminal_html(title, command, output, highlights=None):
"""Create a realistic terminal-style HTML page showing command output."""
highlights = highlights or []
escaped_output = html.escape(output)
for h in highlights:
escaped_h = html.escape(h)
escaped_output = escaped_output.replace(
escaped_h,
f'<span style="color:#ff4444;font-weight:bold;background:#3a1111;padding:1px 4px;border-radius:3px">{escaped_h}</span>'
)
return f"""<!DOCTYPE html>
<html><head><style>
* {{ margin:0; padding:0; box-sizing:border-box; }}
body {{ background:#1a1a2e; padding:30px; font-family:'Segoe UI',sans-serif; }}
.window {{ background:#0d1117; border-radius:12px; overflow:hidden; box-shadow:0 20px 60px rgba(0,0,0,0.5); max-width:1100px; margin:0 auto; border:1px solid #30363d; }}
.titlebar {{ background:#161b22; padding:12px 16px; display:flex; align-items:center; gap:8px; border-bottom:1px solid #30363d; }}
.dot {{ width:12px; height:12px; border-radius:50%; }}
.dot.red {{ background:#ff5f56; }} .dot.yellow {{ background:#ffbd2e; }} .dot.green {{ background:#27c93f; }}
.title {{ color:#8b949e; font-size:13px; margin-left:10px; font-family:'Cascadia Code','Consolas',monospace; }}
.badge {{ background:#da3633; color:white; font-size:11px; padding:3px 10px; border-radius:12px; margin-left:auto; font-weight:bold; }}
.content {{ padding:20px 24px; }}
.label {{ color:#58a6ff; font-size:14px; font-weight:600; margin-bottom:12px; padding:8px 14px; background:#161b22; border-radius:8px; border-left:3px solid #58a6ff; }}
.cmd {{ color:#7ee787; font-size:13px; font-family:'Cascadia Code','Consolas',monospace; margin:12px 0 6px 0; }}
.cmd::before {{ content:'$ '; color:#8b949e; }}
.output {{ color:#c9d1d9; font-size:12.5px; font-family:'Cascadia Code','Consolas',monospace; white-space:pre-wrap; word-break:break-all; line-height:1.6; background:#0d1117; padding:16px; border-radius:8px; border:1px solid #21262d; margin-bottom:16px; }}
.separator {{ border:none; border-top:1px solid #21262d; margin:16px 0; }}
</style></head><body>
<div class="window">
<div class="titlebar">
<div class="dot red"></div><div class="dot yellow"></div><div class="dot green"></div>
<span class="title">PowerShell - Security Audit PoC</span>
<span class="badge">VULNERABILITY EVIDENCE</span>
</div>
<div class="content">
<div class="label">{html.escape(title)}</div>
<div class="cmd">{html.escape(command)}</div>
<div class="output">{escaped_output}</div>
</div>
</div>
</body></html>"""
def render_multi_terminal_html(title, sections):
"""Create terminal HTML with multiple command/output sections."""
sections_html = ""
for sec in sections:
cmd = html.escape(sec["cmd"])
out = html.escape(sec["output"])
for h in sec.get("highlights", []):
escaped_h = html.escape(h)
out = out.replace(
escaped_h,
f'<span style="color:#ff4444;font-weight:bold;background:#3a1111;padding:1px 4px;border-radius:3px">{escaped_h}</span>'
)
label_html = ""
if "label" in sec:
label_html = f'<div class="label">{html.escape(sec["label"])}</div>'
sections_html += f"""{label_html}
<div class="cmd">{cmd}</div>
<div class="output">{out}</div>
<hr class="separator">
"""
return f"""<!DOCTYPE html>
<html><head><style>
* {{ margin:0; padding:0; box-sizing:border-box; }}
body {{ background:#1a1a2e; padding:30px; font-family:'Segoe UI',sans-serif; }}
.window {{ background:#0d1117; border-radius:12px; overflow:hidden; box-shadow:0 20px 60px rgba(0,0,0,0.5); max-width:1100px; margin:0 auto; border:1px solid #30363d; }}
.titlebar {{ background:#161b22; padding:12px 16px; display:flex; align-items:center; gap:8px; border-bottom:1px solid #30363d; }}
.dot {{ width:12px; height:12px; border-radius:50%; }}
.dot.red {{ background:#ff5f56; }} .dot.yellow {{ background:#ffbd2e; }} .dot.green {{ background:#27c93f; }}
.title {{ color:#8b949e; font-size:13px; margin-left:10px; font-family:'Cascadia Code','Consolas',monospace; }}
.badge {{ background:#da3633; color:white; font-size:11px; padding:3px 10px; border-radius:12px; margin-left:auto; font-weight:bold; }}
.content {{ padding:20px 24px; }}
.label {{ color:#58a6ff; font-size:14px; font-weight:600; margin-bottom:12px; padding:8px 14px; background:#161b22; border-radius:8px; border-left:3px solid #58a6ff; }}
.cmd {{ color:#7ee787; font-size:13px; font-family:'Cascadia Code','Consolas',monospace; margin:12px 0 6px 0; }}
.cmd::before {{ content:'$ '; color:#8b949e; }}
.output {{ color:#c9d1d9; font-size:12.5px; font-family:'Cascadia Code','Consolas',monospace; white-space:pre-wrap; word-break:break-all; line-height:1.6; background:#0d1117; padding:16px; border-radius:8px; border:1px solid #21262d; margin-bottom:16px; }}
.separator {{ border:none; border-top:1px solid #21262d; margin:16px 0; }}
</style></head><body>
<div class="window">
<div class="titlebar">
<div class="dot red"></div><div class="dot yellow"></div><div class="dot green"></div>
<span class="title">PowerShell - Security Audit PoC</span>
<span class="badge">VULNERABILITY EVIDENCE</span>
</div>
<div class="content">
<div class="label" style="font-size:16px;margin-bottom:16px">{html.escape(title)}</div>
{sections_html}
</div>
</div>
</body></html>"""
def main():
print("=" * 60)
print("SECURITY PoC SCREENSHOT GENERATOR")
print("=" * 60)
# ========== Step 1: Collect all curl evidence ==========
print("\n[1/2] Collecting real evidence via curl...")
print(" -> VULN-002: Headers on deploy.ghaymah.systems...")
vuln002_output = run_curl("-I https://deploy.ghaymah.systems/")
print(" -> VULN-004: Chaport API key...")
vuln004_output = run_curl_raw("https://deploy.ghaymah.systems/")
print(" -> VULN-005: robots.txt...")
vuln005_output = run_curl("-I https://deploy.ghaymah.systems/robots.txt")
print(" -> VULN-007: Auth cookie...")
vuln007_output = run_curl("-I https://auth.ghaymah.systems/")
print(" -> VULN-008: uvicorn header...")
vuln008_output = run_curl("-I https://genai.ghaymah.systems/health")
print(" -> VULN-009: Rate limit test (5 rapid requests)...")
vuln009_outputs = []
for i in range(5):
out = run_curl("-I https://auth.ghaymah.systems/signin/email-password")
status_line = [l for l in out.split('\n') if l.startswith('HTTP/')]
vuln009_outputs.append(f"Request {i+1}: {status_line[0].strip() if status_line else 'No response'}")
print(" -> VULN-013: Infrastructure URLs in JS bundle...")
js_content = run_curl_raw("https://deploy.ghaymah.systems/assets/index-DxCUZ-xh.js")
vuln013_output = "\n".join(sorted(set(re.findall(r'https?://[a-zA-Z0-9\-\.]+ghaymah\.(?:systems|cloud)[^\s\'"\\)]*', js_content))))
print(" -> VULN-006: GraphQL mutations in JS bundle...")
mutations = sorted(set(re.findall(r'\b((?:create|delete|update|list|rotate|generate)[A-Z]\w+)', js_content)))
skip_words = ['element', 'portal', 'root', 'node', 'ref', 'context', 'visual', 'handler', 'worker',
'walker', 'transformer', 'snapshot', 'scroll', 'point', 'layout', 'cache', 'watch',
'pako', 'range', 'href', 'url', 'signal', 'promise', 'projection', 'queue', 'state',
'progress', 'strategy', 'polling', 'diff', 'query', 'type', 'form', 'render', 'feature',
'async', 'stream', 'error', 'batch', 'text', 'dimension', 'connect', 'fragment', 'toast',
'subscribe', 'input', 'finished', 'manually', 'position', 'result', 'animation', 'delta']
api_mutations = [m for m in mutations if not any(x in m.lower() for x in skip_words)]
vuln006_output = "\n".join(api_mutations)
print(" -> VULN-019: Dotfile access test...")
vuln019_env = run_curl("-I https://deploy.ghaymah.systems/.env")
vuln019_git = run_curl("-I https://deploy.ghaymah.systems/.git/config")
# ========== Step 2: Generate screenshots with Playwright ==========
print("\n[2/2] Generating screenshots with Playwright...")
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
ctx = browser.new_context(viewport={"width": 1280, "height": 900})
page = ctx.new_page()
# --- VULN-001: Swagger UI ---
print(" -> VULN-001: Swagger UI screenshot...")
page.goto("https://genai.ghaymah.systems/", timeout=20000)
page.wait_for_timeout(6000)
page.screenshot(path=os.path.join(DEST, "vuln001_swagger_ui.png"), full_page=True)
print(" OK vuln001_swagger_ui.png")
# --- VULN-002: Missing Security Headers ---
print(" -> VULN-002: Missing headers screenshot...")
page.set_content(render_terminal_html(
title="VULN-002: Missing ALL HTTP Security Headers - deploy.ghaymah.systems",
command="curl -s -I https://deploy.ghaymah.systems/",
output=vuln002_output,
highlights=["Server: nginx/1.29.8"]
))
page.wait_for_timeout(500)
page.screenshot(path=os.path.join(DEST, "vuln002_missing_headers.png"), full_page=True)
print(" OK vuln002_missing_headers.png")
# --- VULN-003: Server Version Disclosure ---
print(" -> VULN-003: Server version screenshot...")
page.set_content(render_terminal_html(
title="VULN-003: Server Version Disclosure - nginx/1.29.8 Exposed",
command="curl -s -I https://deploy.ghaymah.systems/ | grep Server",
output=vuln002_output,
highlights=["Server: nginx/1.29.8", "nginx/1.29.8"]
))
page.wait_for_timeout(500)
page.screenshot(path=os.path.join(DEST, "vuln003_server_version.png"), full_page=True)
print(" OK vuln003_server_version.png")
# --- VULN-004: Chaport API Key ---
print(" -> VULN-004: Chaport key screenshot...")
chaport_section = ""
lines = vuln004_output.split("\n")
for i, line in enumerate(lines):
if "chaport" in line.lower() or "appId" in line:
start = max(0, i - 1)
end = min(len(lines), i + 8)
chaport_section = "\n".join(lines[start:end])
break
if not chaport_section:
chaport_section = "[Could not extract Chaport section]"
page.set_content(render_terminal_html(
title="VULN-004: Hardcoded Chaport API Key in Public HTML Source",
command="curl -s https://deploy.ghaymah.systems/ | grep -A5 chaport",
output=chaport_section.strip(),
highlights=["68f6108fe88f0419ac087814", "appId"]
))
page.wait_for_timeout(500)
page.screenshot(path=os.path.join(DEST, "vuln004_chaport_key.png"), full_page=True)
print(" OK vuln004_chaport_key.png")
# --- VULN-005: robots.txt SPA fallback ---
print(" -> VULN-005: robots.txt screenshot...")
page.set_content(render_terminal_html(
title="VULN-005: robots.txt Returns SPA HTML Instead of Text File",
command="curl -s -I https://deploy.ghaymah.systems/robots.txt",
output=vuln005_output,
highlights=["Content-Type: text/html", "200 OK"]
))
page.wait_for_timeout(500)
page.screenshot(path=os.path.join(DEST, "vuln005_robots_txt.png"), full_page=True)
print(" OK vuln005_robots_txt.png")
# --- VULN-006: GraphQL Mutations Exposed ---
print(" -> VULN-006: GraphQL mutations screenshot...")
page.set_content(render_terminal_html(
title="VULN-006: GraphQL Mutation Names Exposed in Client JavaScript Bundle",
command='curl -s .../assets/index-DxCUZ-xh.js | grep -oP "(create|delete|update|list|rotate)\\w+"',
output=vuln006_output,
highlights=["deleteApp", "deleteManagedApp", "DeletePostgresDb", "deleteS3Bucket",
"deleteVolume", "createPAT", "createPullSecret", "DeletePullSecret",
"deleteStaticApp", "deleteManagedVolume"]
))
page.wait_for_timeout(500)
page.screenshot(path=os.path.join(DEST, "vuln006_graphql_mutations.png"), full_page=True)
print(" OK vuln006_graphql_mutations.png")
# --- VULN-007: Auth Cookie ---
print(" -> VULN-007: Auth cookie screenshot...")
page.set_content(render_terminal_html(
title="VULN-007: Auth Cookie Missing 'Secure' and 'SameSite' Attributes",
command="curl -s -I https://auth.ghaymah.systems/",
output=vuln007_output,
highlights=["Set-Cookie:", "connect.sid", "HttpOnly", "Path=/"]
))
page.wait_for_timeout(500)
page.screenshot(path=os.path.join(DEST, "vuln007_auth_cookie.png"), full_page=True)
print(" OK vuln007_auth_cookie.png")
# --- VULN-008: uvicorn server header ---
print(" -> VULN-008: uvicorn header screenshot...")
page.set_content(render_terminal_html(
title="VULN-008: GenAI Endpoint Exposes 'uvicorn' Server Header",
command="curl -s -I https://genai.ghaymah.systems/health",
output=vuln008_output,
highlights=["Server: uvicorn", "401 Unauthorized"]
))
page.wait_for_timeout(500)
page.screenshot(path=os.path.join(DEST, "vuln008_uvicorn_header.png"), full_page=True)
print(" OK vuln008_uvicorn_header.png")
# --- VULN-009: No Rate Limiting ---
print(" -> VULN-009: No rate limiting screenshot...")
rate_output = "\n".join(vuln009_outputs) + "\n\nAll 5 requests succeeded instantly -- no 429 Too Many Requests returned."
page.set_content(render_terminal_html(
title="VULN-009: No Rate Limiting - 5 Rapid Requests All Succeed Without Throttling",
command="for i in {1..5}; do curl -s -I https://auth.ghaymah.systems/signin/email-password; done",
output=rate_output,
highlights=["All 5 requests succeeded"]
))
page.wait_for_timeout(500)
page.screenshot(path=os.path.join(DEST, "vuln009_no_rate_limit.png"), full_page=True)
print(" OK vuln009_no_rate_limit.png")
# --- VULN-013: Infrastructure Topology ---
print(" -> VULN-013: Infrastructure topology screenshot...")
page.set_content(render_terminal_html(
title="VULN-013: Complete Infrastructure Topology Exposed in Client JavaScript",
command='curl -s .../assets/index-DxCUZ-xh.js | grep -oP "https://.*ghaymah\\.(systems|cloud).*" | sort -u',
output=vuln013_output,
highlights=["auth.ghaymah.systems", "genai.ghaymah.systems", "graphql.ghaymah.systems",
"logs.ghaymah.systems", "integrations.ghaymah.systems", "gateway-"]
))
page.wait_for_timeout(500)
page.screenshot(path=os.path.join(DEST, "vuln013_infrastructure_topology.png"), full_page=True)
print(" OK vuln013_infrastructure_topology.png")
# --- VULN-019: Dotfile Access ---
print(" -> VULN-019: Dotfile access screenshot...")
page.set_content(render_multi_terminal_html(
title="VULN-019: SPA Catch-All Masks Sensitive File Access (.env, .git/config)",
sections=[
{
"label": "Test 1: Accessing .env file",
"cmd": "curl -s -I https://deploy.ghaymah.systems/.env",
"output": vuln019_env,
"highlights": ["200 OK", "Content-Type: text/html"]
},
{
"label": "Test 2: Accessing .git/config",
"cmd": "curl -s -I https://deploy.ghaymah.systems/.git/config",
"output": vuln019_git,
"highlights": ["200 OK", "Content-Type: text/html"]
}
]
))
page.wait_for_timeout(500)
page.screenshot(path=os.path.join(DEST, "vuln019_dotfile_access.png"), full_page=True)
print(" OK vuln019_dotfile_access.png")
# --- VULN-001 OpenAPI Spec (additional) ---
print(" -> VULN-001 (extra): OpenAPI spec screenshot...")
page.goto("https://genai.ghaymah.systems/api/v1/openapi.json", timeout=15000)
page.wait_for_timeout(3000)
page.screenshot(path=os.path.join(DEST, "vuln001_openapi_json.png"), full_page=False)
print(" OK vuln001_openapi_json.png")
browser.close()
# ========== Summary ==========
files = [f for f in os.listdir(DEST) if f.endswith('.png')]
print(f"\n{'=' * 60}")
print(f"DONE -- Generated {len(files)} evidence screenshots:")
for f in sorted(files):
size = os.path.getsize(os.path.join(DEST, f))
print(f" {f} ({size/1024:.1f} KB)")
print(f"{'=' * 60}")
if __name__ == "__main__":
main()