#!/usr/bin/env python3 # (c) 2025 Tor Houghton # requires pip install requests[socks] import argparse import ipaddress import itertools import requests import urllib3 from requests.exceptions import RequestException import sys urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) DEFAULT_PORTS = [ 80, 1080, 1220, 2080, 2301, 2381, 3080, 4080, 4081, 4961, 5080, 6080, 7080, 8000, 8008, 8080, 8088, 8100, 8333, 8443, 8888, 9043, 9060, 9080, 9100, 9443, 10000, 10080, 10443, 13000, 20000, 60834 ] scan_results = {} # { 'ip': [open_ports] } def expand_octet(octet): if '-' in octet: start, end = map(int, octet.split('-')) return list(map(str, range(start, end + 1))) else: return [octet] def expand_ip_range(expr): parts = expr.split('.') if len(parts) != 4: raise ValueError(f"Invalid IP format: {expr}") octets = [expand_octet(part) for part in parts] return ['.'.join(p) for p in itertools.product(*octets)] def parse_targets(target_arg): targets = set() for item in target_arg.split(','): item = item.strip() try: if '-' in item and not '/' in item: expanded = expand_ip_range(item) targets.update(expanded) else: # Fallback for CIDR or plain IP try: net = ipaddress.ip_network(item, strict=False) for ip in net.hosts(): targets.add(str(ip)) except ValueError: targets.add(item) except Exception as e: print(f"[!] Skipping invalid target '{item}': {e}") return sorted(targets, key=lambda ip: ipaddress.ip_address(ip)) def parse_ports(port_arg): ports = set() for part in port_arg.split(','): if '-' in part: start, end = map(int, part.split('-')) ports.update(range(start, end + 1)) else: ports.add(int(part)) return sorted(ports) def parse_proxy(proxy_arg, proxy_type): if ':' not in proxy_arg: raise ValueError("Proxy must be in the format :") host, port = proxy_arg.split(':') proxy_url = f"{proxy_type}://{host}:{port}" return { 'http': proxy_url, 'https': proxy_url } def scan_target(ip, port, proxies, timeout, verbose, scan_results): headers = {'Accept-Encoding': 'identity'} http_url = f"http://{ip}:{port}/" https_url = f"https://{ip}:{port}/" http_success = False https_success = False proxy_blocked_https = False proxy_upgraded_to_https = False try: if verbose: print(f"[+] Trying HTTP: {ip}:{port}") response = requests.get(http_url, proxies=proxies, timeout=timeout, headers=headers, verify=False) if response.url != http_url: proxy_upgraded_to_https = True if verbose: print(f"[!] Proxy upgraded {http_url} to {response.url}") if response.status_code < 500: if verbose: print(f"[✓] Port {port} appears OPEN on {ip} (HTTP)") http_success = True elif response.status_code == 500: if verbose: print(f"[×] Proxy says target refused {ip}:{port} (500)") elif verbose: print(f"[×] {ip}:{port} returned status {response.status_code}") except requests.exceptions.ReadTimeout: if verbose: print(f"[!] Timeout on {ip}:{port}") return except RequestException as e: if verbose: print(f"[!] Error on {ip}:{port} - {e}") return try: if verbose: print(f"[~] Trying HTTPS: {ip}:{port}") secure_response = requests.get(https_url, proxies=proxies, timeout=timeout, headers=headers, verify=False) if secure_response.status_code == 403: if verbose: print(f"[!] Proxy blocked HTTPS CONNECT to {ip}:{port} (403 Forbidden)") proxy_blocked_https = True elif secure_response.ok: if verbose: print(f"[✓] HTTPS succeeded on {ip}:{port}") https_success = True elif verbose: print(f"[×] HTTPS returned status {secure_response.status_code} on {ip}:{port}") except requests.exceptions.SSLError as e: if verbose: print(f"[!] SSL error on {ip}:{port} (likely self-signed): {e}") https_success = True # Still reachable except RequestException as e: if "Tunnel connection failed" in str(e) and "403" in str(e): if verbose: print(f"[!] Proxy blocked HTTPS CONNECT to {ip}:{port} (403 Forbidden)") proxy_blocked_https = True elif verbose: print(f"[×] HTTPS failed on {ip}:{port} - {e}") if proxy_blocked_https and not proxy_upgraded_to_https: if verbose: print(f"[!] Disregarding {ip}:{port} due to proxy block") return if http_success or https_success or proxy_upgraded_to_https: scan_results.setdefault(ip, []).append(port) def main(): parser = argparse.ArgumentParser(description="Flexible Proxy Scanner with SOCKS5 support") parser.add_argument('-t', '--target', required=True, help='Target IPs, ranges, or CIDRs (comma-separated)') parser.add_argument('--proxy', required=True, help='Proxy address in format host:port') parser.add_argument('--proxy-type', default='http', choices=['http', 'socks5', 'socks5h'], help='Proxy protocol to use (default: http)') parser.add_argument('-p', '--ports', default=None, help='Ports to scan (e.g., 80,443,8000-8080)') parser.add_argument('--timeout', type=float, default=3, help='Request timeout in seconds (default: 3)') parser.add_argument('--verbose', action='store_true', help='Show progress and detailed output') parser.add_argument('--summary', action='store_true', help='Print a summary of responsive targets and open ports') parser.add_argument('--examples', action='store_true', help='Show example usage and exit') args = parser.parse_args() if args.examples: print(r""" Examples: --------- # Scan a single IP on default ports through an HTTP proxy ./ppscan.py -t 10.0.0.1 --proxy 127.0.0.1:8888 --proxy-type http --summary # Scan multiple IPs and a range of ports ./ppscan.py -t 10.0.0.1,10.0.0.2 -p 80,443,8000-8080 --proxy 127.0.0.1:8888 --summary # Scan a /24 range using CIDR ./ppscan.py -t 192.168.1.0/24 -p 443 --proxy 127.0.0.1:8888 --summary # Use advanced IP range notation ./ppscan.py -t 10.10-11.1-2.1-5 --proxy 127.0.0.1:8888 --summary # Scan via a SOCKS5 proxy with low timeout ./ppscan.py -t 10.0.0.5 -p 22,80,443 --proxy 127.0.0.1:9050 --proxy-type socks5 --timeout 0.5 --summary # Show detailed progress and verbose output ./ppscan.py -t 10.0.0.5 -p 80,443 --proxy 127.0.0.1:8888 --verbose --summary """) sys.exit(0) try: targets = parse_targets(args.target) ports = parse_ports(args.ports) if args.ports else DEFAULT_PORTS proxies = parse_proxy(args.proxy, args.proxy_type) except Exception as e: print(f"Error: {e}") sys.exit(1) if args.verbose: print(f"[*] Starting scan using {args.proxy_type.upper()} proxy at {args.proxy}") print(f"[*] Scanning {len(targets)} target(s) across {len(ports)} port(s)\n") for ip in targets: for port in ports: scan_target(ip, port, proxies, args.timeout, args.verbose, scan_results) if args.summary: print("\n=== Scan Summary ===") for ip in sorted(scan_results, key=lambda ip: ipaddress.ip_address(ip)): ports = scan_results[ip] if ports: print(f"{ip} {','.join(str(p) for p in sorted(ports))}") else: print(f"{ip} (none)") if __name__ == '__main__': main()