#!/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()
|