#!/usr/bin/env python3 """ WebRecon - A tool for web reconnaissance and basic vulnerability scanning. """ import requests import logging from typing import Dict, List from urllib.parse import urljoin, urlencode # Added urlencode logger = logging.getLogger(__name__) class WebRecon: """ A class for performing basic web reconnaissance and simple vulnerability checks. """ def __init__(self, config: Dict): """ Initializes the WebRecon tool. Args: config (Dict): The configuration dictionary for the framework. """ self.config = config # Expanded wordlist for discovering common paths self.wordlist = [ "admin", "login", "dashboard", "api", "robots.txt", "sitemap.xml", "test", "dev", "backup", "v1", "v2", "v3", ".git", ".env", "config.php", "phpinfo.php", "index.php", "main.php", "home.php", "portal.php", "upload", "files", "images", "assets", "downloads", "includes", "src", "backup.zip", "data.sql", "admin.bak", "panel" ] self.headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' } self.test_parameters = ["id", "page", "cat", "item", "view", "name", "query", "search"] # Added for vulnerability testing def analyze(self, target: str) -> Dict: """ Analyzes a web target to find common directories, files, and basic vulnerabilities. Args: target (str): The base URL to analyze. It should include the scheme (http/https). Returns: Dict: A dictionary containing the findings including discovered paths and vulnerabilities. """ logger.info(f"Starting web reconnaissance and basic vulnerability scan on {target}") findings = { "target": target, "status_code": None, "headers": {}, "discovered_paths": [], "vulnerabilities": [] # New key for vulnerabilities } if not target.startswith(('http://', 'https://')): target = f"http://{target}" logger.info(f"No scheme provided. Defaulting to http: {target}") # 1. Check base URL connectivity and headers try: response = requests.head(target, headers=self.headers, timeout=5, allow_redirects=True) findings["status_code"] = response.status_code findings["headers"] = dict(response.headers) logger.info(f"Target {target} is online. Status: {response.status_code}") except requests.RequestException as e: logger.error(f"Failed to connect to {target}: {e}") return {"error": f"Failed to connect to target: {e}"} # 2. Discover common paths for path in self.wordlist: url_to_check = urljoin(target, path) try: res = requests.get(url_to_check, headers=self.headers, timeout=3, allow_redirects=False) if res.status_code >= 200 and res.status_code < 400: logger.info(f"Found path: {url_to_check} (Status: {res.status_code})") findings["discovered_paths"].append({ "path": url_to_check, "status_code": res.status_code }) except requests.RequestException: # Ignore connection errors for sub-paths continue # 3. Perform basic vulnerability checks logger.info(f"Performing basic vulnerability checks on {target}") findings["vulnerabilities"].extend(self._check_sqli(target)) findings["vulnerabilities"].extend(self._check_xss(target)) findings["vulnerabilities"].extend(self._check_lfi(target)) logger.info(f"Web reconnaissance on {target} finished. Found {len(findings['discovered_paths'])} paths and {len(findings['vulnerabilities'])} vulnerabilities.") return findings def _check_sqli(self, target: str) -> List[Dict]: """Checks for basic SQL Injection vulnerabilities.""" vulnerabilities = [] sqli_payloads = ["'", " or 1=1-- -", " or 1=1#", "\" or 1=1-- -"] sqli_error_patterns = ["sql syntax", "mysql_fetch_array()", "error in your sql syntax", "warning: mysql", "unclosed quotation mark"] for param in self.test_parameters: for payload in sqli_payloads: test_url = f"{target}?{param}={urlencode({'': payload})}" try: response = requests.get(test_url, headers=self.headers, timeout=5) for error_pattern in sqli_error_patterns: if error_pattern in response.text.lower(): vulnerabilities.append({ "type": "SQL Injection", "severity": "High", "url": test_url, "parameter": param, "payload": payload, "response_snippet": response.text[:200], "description": f"Potential SQL Injection via parameter '{param}' with payload '{payload}'" }) logger.warning(f"Potential SQLi found: {test_url}") # Stop after first finding for this param/type break except requests.RequestException: continue return vulnerabilities def _check_xss(self, target: str) -> List[Dict]: """Checks for basic Cross-Site Scripting (XSS) vulnerabilities.""" vulnerabilities = [] xss_payloads = [ "", "", "" ] for param in self.test_parameters: for payload in xss_payloads: test_url = f"{target}?{param}={urlencode({'': payload})}" try: response = requests.get(test_url, headers=self.headers, timeout=5) if payload.replace('alert(1)', 'alert(1)') in response.text or \ payload in response.text: # Check for reflected payload vulnerabilities.append({ "type": "Cross-Site Scripting (XSS)", "severity": "Medium", "url": test_url, "parameter": param, "payload": payload, "response_snippet": response.text[:200], "description": f"Potential XSS via parameter '{param}' with payload '{payload}'" }) logger.warning(f"Potential XSS found: {test_url}") # Stop after first finding for this param/type break except requests.RequestException: continue return vulnerabilities def _check_lfi(self, target: str) -> List[Dict]: """Checks for basic Local File Inclusion (LFI) vulnerabilities.""" vulnerabilities = [] lfi_payloads = [ "../../../../etc/passwd", "....//....//....//etc/passwd", "%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd" # URL-encoded ] lfi_patterns = ["root:x:", "daemon:x:", "bin:x:"] # Common patterns in /etc/passwd for param in self.test_parameters: for payload in lfi_payloads: test_url = f"{target}?{param}={urlencode({'': payload})}" try: response = requests.get(test_url, headers=self.headers, timeout=5) if any(pattern in response.text for pattern in lfi_patterns): vulnerabilities.append({ "type": "Local File Inclusion (LFI)", "severity": "High", "url": test_url, "parameter": param, "payload": payload, "response_snippet": response.text[:200], "description": f"Potential LFI via parameter '{param}' with payload '{payload}'" }) logger.warning(f"Potential LFI found: {test_url}") # Stop after first finding for this param/type break except requests.RequestException: continue return vulnerabilities