Update app.py
Browse files
app.py
CHANGED
|
@@ -15,88 +15,93 @@ from collections import defaultdict
|
|
| 15 |
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
| 16 |
warnings.filterwarnings('ignore')
|
| 17 |
|
|
|
|
|
|
|
|
|
|
| 18 |
# --- Enhanced Port Parsing with Multi-Port Service Detection ---
|
| 19 |
|
| 20 |
-
def load_custom_ports(
|
| 21 |
"""
|
| 22 |
Enhanced parser that:
|
| 23 |
1. Handles continuous format: '135 - MS SQL1433 - MSSQL1521 - Oracle'
|
| 24 |
2. Detects services on multiple ports
|
| 25 |
3. Groups related services
|
|
|
|
| 26 |
"""
|
| 27 |
port_map = {80: "HTTP", 443: "HTTPS"}
|
| 28 |
service_ports = defaultdict(list) # Track which ports belong to same service
|
| 29 |
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
|
|
|
|
|
|
| 37 |
try:
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
try:
|
| 47 |
port_num = int(port_str)
|
| 48 |
if 1 <= port_num <= 65535:
|
| 49 |
-
desc =
|
| 50 |
desc = re.sub(r'\s+', ' ', desc)
|
| 51 |
if desc:
|
| 52 |
-
# Extract base service name
|
| 53 |
base_service = desc.split('-')[0].strip()
|
| 54 |
service_ports[base_service].append(port_num)
|
| 55 |
port_map[port_num] = desc
|
|
|
|
| 56 |
except ValueError:
|
| 57 |
continue
|
| 58 |
-
|
| 59 |
-
# Strategy 2: Line-by-line format
|
| 60 |
-
for line in content.split('\n'):
|
| 61 |
-
line = line.strip()
|
| 62 |
-
if not line or line.startswith('#'):
|
| 63 |
-
continue
|
| 64 |
-
|
| 65 |
-
if re.match(r'^\d+\s*-\s*[A-Za-z]', line):
|
| 66 |
-
continue
|
| 67 |
-
|
| 68 |
-
patterns = [
|
| 69 |
-
r'^(.+?)\s*[:=]\s*(\d+)',
|
| 70 |
-
r'port\s*[:=]?\s*(\d+)\s+(.+)',
|
| 71 |
-
r'(\d+)\s*/\s*\w+\s+(.+)',
|
| 72 |
-
r'^(\d+)\s+(.+)$',
|
| 73 |
-
]
|
| 74 |
-
|
| 75 |
-
for pattern in patterns:
|
| 76 |
-
match = re.match(pattern, line, re.IGNORECASE)
|
| 77 |
-
if match:
|
| 78 |
-
groups = match.groups()
|
| 79 |
-
|
| 80 |
-
if groups[0].isdigit():
|
| 81 |
-
port_str, desc = groups[0], groups[1]
|
| 82 |
-
else:
|
| 83 |
-
desc, port_str = groups[0], groups[1]
|
| 84 |
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
base_service = desc.split('-')[0].strip()
|
| 92 |
-
service_ports[base_service].append(port_num)
|
| 93 |
-
port_map[port_num] = desc
|
| 94 |
-
break
|
| 95 |
-
except ValueError:
|
| 96 |
-
continue
|
| 97 |
-
|
| 98 |
-
except Exception as e:
|
| 99 |
-
print(f"⚠️ Error reading {filename}: {e}")
|
| 100 |
|
| 101 |
# Add multi-port info to descriptions
|
| 102 |
for service, ports in service_ports.items():
|
|
@@ -114,147 +119,9 @@ def load_custom_ports(folder_path="ports") -> Dict[int, str]:
|
|
| 114 |
print(f"✅ Loaded {len(port_map)} port definitions")
|
| 115 |
return port_map
|
| 116 |
|
|
|
|
| 117 |
|
| 118 |
-
#
|
| 119 |
-
|
| 120 |
-
def extract_ips(file_path: str) -> List[str]:
|
| 121 |
-
"""Enhanced IP extractor with validation."""
|
| 122 |
-
try:
|
| 123 |
-
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
| 124 |
-
content = f.read()
|
| 125 |
-
|
| 126 |
-
found_ips = set(re.findall(r'\b(?:\d{1,3}\.){3}\d{1,3}\b', content))
|
| 127 |
-
|
| 128 |
-
valid_ips = []
|
| 129 |
-
for ip in found_ips:
|
| 130 |
-
octets = [int(x) for x in ip.split('.')]
|
| 131 |
-
|
| 132 |
-
if any(o > 255 for o in octets):
|
| 133 |
-
continue
|
| 134 |
-
|
| 135 |
-
if (octets[0] in [0, 127, 255] or
|
| 136 |
-
octets[0] == 10 or
|
| 137 |
-
(octets[0] == 172 and 16 <= octets[1] <= 31) or
|
| 138 |
-
(octets[0] == 192 and octets[1] == 168) or
|
| 139 |
-
(octets[0] == 169 and octets[1] == 254)):
|
| 140 |
-
continue
|
| 141 |
-
|
| 142 |
-
valid_ips.append(ip)
|
| 143 |
-
|
| 144 |
-
return sorted(valid_ips)
|
| 145 |
-
except Exception as e:
|
| 146 |
-
print(f"Error extracting IPs: {e}")
|
| 147 |
-
return []
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
# --- C2 Detection ---
|
| 151 |
-
|
| 152 |
-
C2_SIGNATURES = {
|
| 153 |
-
"Cobalt Strike": ["404 Not Found", "application/ocsp-response", "BeEF", "cobaltstrike"],
|
| 154 |
-
"Metasploit": ["Metasploit", "Mettle", "meterpreter"],
|
| 155 |
-
"Covenant": ["covenant", "GruntHTTP", "Auth/Login"],
|
| 156 |
-
"Empire": ["Empire", "session_id", "admin/login.php"],
|
| 157 |
-
"Sliver": ["sliver", "implant"],
|
| 158 |
-
"Mythic": ["mythic", "agent_message"]
|
| 159 |
-
}
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
def probe_target(ip: str, port: int, port_desc: str) -> Optional[Dict]:
|
| 163 |
-
"""Enhanced probe with better banner detection and multiple protocol attempts."""
|
| 164 |
-
sock = None
|
| 165 |
-
try:
|
| 166 |
-
# Port connectivity check
|
| 167 |
-
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
| 168 |
-
sock.settimeout(1.2)
|
| 169 |
-
result = sock.connect_ex((ip, port))
|
| 170 |
-
|
| 171 |
-
if result != 0:
|
| 172 |
-
return None
|
| 173 |
-
|
| 174 |
-
# Try to get raw banner first (works for many services)
|
| 175 |
-
banner = "N/A"
|
| 176 |
-
try:
|
| 177 |
-
sock.send(b'\r\n')
|
| 178 |
-
raw_banner = sock.recv(1024).decode('utf-8', errors='ignore').strip()
|
| 179 |
-
if raw_banner and len(raw_banner) > 3:
|
| 180 |
-
banner = raw_banner[:200] # First 200 chars
|
| 181 |
-
except:
|
| 182 |
-
pass
|
| 183 |
-
finally:
|
| 184 |
-
if sock:
|
| 185 |
-
sock.close()
|
| 186 |
-
sock = None
|
| 187 |
-
|
| 188 |
-
# Try HTTP/HTTPS
|
| 189 |
-
for protocol in ["https", "http"]:
|
| 190 |
-
# Smart protocol selection
|
| 191 |
-
if protocol == "https" and port not in [443, 8443, 2083, 2087, 9443, 8000, 4443]:
|
| 192 |
-
if port < 8000: # Skip HTTPS for low ports unless common HTTPS ports
|
| 193 |
-
continue
|
| 194 |
-
|
| 195 |
-
try:
|
| 196 |
-
resp = requests.get(
|
| 197 |
-
f"{protocol}://{ip}:{port}",
|
| 198 |
-
timeout=1.8,
|
| 199 |
-
verify=False,
|
| 200 |
-
allow_redirects=False,
|
| 201 |
-
headers={'User-Agent': 'Mozilla/5.0'}
|
| 202 |
-
)
|
| 203 |
-
|
| 204 |
-
# Get server banner
|
| 205 |
-
server = resp.headers.get('Server', '')
|
| 206 |
-
powered_by = resp.headers.get('X-Powered-By', '')
|
| 207 |
-
|
| 208 |
-
if server or powered_by:
|
| 209 |
-
banner = f"{server} {powered_by}".strip()
|
| 210 |
-
elif banner == "N/A":
|
| 211 |
-
banner = f"HTTP {resp.status_code}"
|
| 212 |
-
|
| 213 |
-
content = resp.text[:5000]
|
| 214 |
-
|
| 215 |
-
category = f"Web ({port_desc})"
|
| 216 |
-
|
| 217 |
-
# C2 Detection
|
| 218 |
-
for name, sigs in C2_SIGNATURES.items():
|
| 219 |
-
if any(sig.lower() in content.lower() or
|
| 220 |
-
sig.lower() in str(resp.headers).lower() for sig in sigs):
|
| 221 |
-
category = f"🚨 POTENTIAL {name}"
|
| 222 |
-
break
|
| 223 |
-
|
| 224 |
-
return {
|
| 225 |
-
"IP": ip,
|
| 226 |
-
"Port": port,
|
| 227 |
-
"Service": port_desc,
|
| 228 |
-
"Type": category,
|
| 229 |
-
"Banner": banner
|
| 230 |
-
}
|
| 231 |
-
except requests.exceptions.SSLError:
|
| 232 |
-
# SSL error on HTTP, try HTTPS
|
| 233 |
-
if protocol == "http":
|
| 234 |
-
continue
|
| 235 |
-
else:
|
| 236 |
-
break
|
| 237 |
-
except:
|
| 238 |
-
continue
|
| 239 |
-
|
| 240 |
-
# Port is open but not HTTP
|
| 241 |
-
return {
|
| 242 |
-
"IP": ip,
|
| 243 |
-
"Port": port,
|
| 244 |
-
"Service": port_desc,
|
| 245 |
-
"Type": "Open (Non-HTTP)",
|
| 246 |
-
"Banner": banner if banner != "N/A" else "Unknown"
|
| 247 |
-
}
|
| 248 |
-
|
| 249 |
-
except Exception as e:
|
| 250 |
-
return None
|
| 251 |
-
finally:
|
| 252 |
-
if sock:
|
| 253 |
-
try:
|
| 254 |
-
sock.close()
|
| 255 |
-
except:
|
| 256 |
-
pass
|
| 257 |
-
|
| 258 |
|
| 259 |
def start_analysis(file_obj, max_threads: int = 50, progress=gr.Progress()):
|
| 260 |
"""Main analysis with pure threading."""
|
|
@@ -263,7 +130,8 @@ def start_analysis(file_obj, max_threads: int = 50, progress=gr.Progress()):
|
|
| 263 |
|
| 264 |
try:
|
| 265 |
progress(0, desc="📋 Parsing port definitions...")
|
| 266 |
-
|
|
|
|
| 267 |
|
| 268 |
progress(0.1, desc="🔍 Extracting IP addresses...")
|
| 269 |
ips = extract_ips(file_obj.name)
|
|
@@ -396,8 +264,8 @@ with demo:
|
|
| 396 |
)
|
| 397 |
|
| 398 |
with gr.Column(scale=1):
|
| 399 |
-
gr.Markdown("""
|
| 400 |
-
### 🔧 Port Format Support
|
| 401 |
|
| 402 |
**Continuous** (your format):
|
| 403 |
```
|
|
@@ -442,26 +310,26 @@ with demo:
|
|
| 442 |
|
| 443 |
|
| 444 |
if __name__ == "__main__":
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
sample_file = "ports/common_ports.txt"
|
| 448 |
-
if not os.path.exists(sample_file):
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
21 - FTP
|
| 452 |
-
22 - SSH
|
| 453 |
-
23 - Telnet
|
| 454 |
-
25 - SMTP
|
| 455 |
-
80 - HTTP
|
| 456 |
-
443 - HTTPS
|
| 457 |
-
3306 - MySQL
|
| 458 |
-
3307 - MySQL Alternate
|
| 459 |
-
3389 - RDP
|
| 460 |
-
5432 - PostgreSQL
|
| 461 |
-
5433 - PostgreSQL Alternate
|
| 462 |
-
8080 - HTTP Alt
|
| 463 |
-
8443 - HTTPS Alt
|
| 464 |
-
""")
|
| 465 |
|
| 466 |
demo.queue(max_size=10)
|
| 467 |
demo.launch(
|
|
|
|
| 15 |
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
| 16 |
warnings.filterwarnings('ignore')
|
| 17 |
|
| 18 |
+
# GitHub raw URL for the ports file
|
| 19 |
+
GITHUB_PORTS_RAW_URL = "https://raw.githubusercontent.com/Wuhpondiscord/ports/main/common_ports.txt"
|
| 20 |
+
|
| 21 |
# --- Enhanced Port Parsing with Multi-Port Service Detection ---
|
| 22 |
|
| 23 |
+
def load_custom_ports() -> Dict[int, str]:
|
| 24 |
"""
|
| 25 |
Enhanced parser that:
|
| 26 |
1. Handles continuous format: '135 - MS SQL1433 - MSSQL1521 - Oracle'
|
| 27 |
2. Detects services on multiple ports
|
| 28 |
3. Groups related services
|
| 29 |
+
Loads port definitions from a specified GitHub raw URL.
|
| 30 |
"""
|
| 31 |
port_map = {80: "HTTP", 443: "HTTPS"}
|
| 32 |
service_ports = defaultdict(list) # Track which ports belong to same service
|
| 33 |
|
| 34 |
+
try:
|
| 35 |
+
print(f"🌐 Fetching port definitions from: {GITHUB_PORTS_RAW_URL}")
|
| 36 |
+
response = requests.get(GITHUB_PORTS_RAW_URL, timeout=10)
|
| 37 |
+
response.raise_for_status() # Raise an HTTPError for bad responses (4xx or 5xx)
|
| 38 |
+
content = response.text
|
| 39 |
+
print("✅ Successfully fetched port definitions.")
|
| 40 |
|
| 41 |
+
# Strategy 1: Continuous format parser
|
| 42 |
+
continuous_pattern = r'(\d+)\s*-\s*([A-Za-z][\w\s\-\.\(\)]*?)(?=\d+\s*-|$)'
|
| 43 |
+
matches = re.findall(continuous_pattern, content, re.MULTILINE)
|
| 44 |
+
|
| 45 |
+
for port_str, desc in matches:
|
| 46 |
try:
|
| 47 |
+
port_num = int(port_str)
|
| 48 |
+
if 1 <= port_num <= 65535:
|
| 49 |
+
desc = desc.strip().strip('-').strip()
|
| 50 |
+
desc = re.sub(r'\s+', ' ', desc)
|
| 51 |
+
if desc:
|
| 52 |
+
# Extract base service name
|
| 53 |
+
base_service = desc.split('-')[0].strip()
|
| 54 |
+
service_ports[base_service].append(port_num)
|
| 55 |
+
port_map[port_num] = desc
|
| 56 |
+
except ValueError:
|
| 57 |
+
continue
|
| 58 |
+
|
| 59 |
+
# Strategy 2: Line-by-line format
|
| 60 |
+
for line in content.split('\n'):
|
| 61 |
+
line = line.strip()
|
| 62 |
+
if not line or line.startswith('#'):
|
| 63 |
+
continue
|
| 64 |
+
|
| 65 |
+
# Skip if already matched by continuous pattern
|
| 66 |
+
if re.match(r'^\d+\s*-\s*[A-Za-z]', line):
|
| 67 |
+
continue
|
| 68 |
+
|
| 69 |
+
patterns = [
|
| 70 |
+
r'^(.+?)\s*[:=]\s*(\d+)', # e.g., MySQL: 3306
|
| 71 |
+
r'port\s*[:=]?\s*(\d+)\s+(.+)', # e.g., port 22 SSH
|
| 72 |
+
r'(\d+)\s*/\s*\w+\s+(.+)', # e.g., 22/tcp SSH
|
| 73 |
+
r'^(\d+)\s+(.+)$', # e.g., 22 SSH
|
| 74 |
+
]
|
| 75 |
+
|
| 76 |
+
for pattern in patterns:
|
| 77 |
+
match = re.match(pattern, line, re.IGNORECASE)
|
| 78 |
+
if match:
|
| 79 |
+
groups = match.groups()
|
| 80 |
+
|
| 81 |
+
if groups[0].isdigit():
|
| 82 |
+
port_str, desc = groups[0], groups[1]
|
| 83 |
+
else:
|
| 84 |
+
desc, port_str = groups[0], groups[1]
|
| 85 |
+
|
| 86 |
try:
|
| 87 |
port_num = int(port_str)
|
| 88 |
if 1 <= port_num <= 65535:
|
| 89 |
+
desc = re.sub(r'[^\w\s\-\.]+', '', desc).strip()
|
| 90 |
desc = re.sub(r'\s+', ' ', desc)
|
| 91 |
if desc:
|
|
|
|
| 92 |
base_service = desc.split('-')[0].strip()
|
| 93 |
service_ports[base_service].append(port_num)
|
| 94 |
port_map[port_num] = desc
|
| 95 |
+
break
|
| 96 |
except ValueError:
|
| 97 |
continue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
|
| 99 |
+
except requests.exceptions.RequestException as e:
|
| 100 |
+
print(f"⚠️ Error fetching port definitions from GitHub: {e}")
|
| 101 |
+
print("Using default port definitions.")
|
| 102 |
+
except Exception as e:
|
| 103 |
+
print(f"⚠️ Error parsing fetched port definitions: {e}")
|
| 104 |
+
print("Using default port definitions.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
|
| 106 |
# Add multi-port info to descriptions
|
| 107 |
for service, ports in service_ports.items():
|
|
|
|
| 119 |
print(f"✅ Loaded {len(port_map)} port definitions")
|
| 120 |
return port_map
|
| 121 |
|
| 122 |
+
# --- Rest of your code remains the same ---
|
| 123 |
|
| 124 |
+
# ... (IP Extraction, C2 Detection, Probe Target, Start Analysis, UI) ...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
|
| 126 |
def start_analysis(file_obj, max_threads: int = 50, progress=gr.Progress()):
|
| 127 |
"""Main analysis with pure threading."""
|
|
|
|
| 130 |
|
| 131 |
try:
|
| 132 |
progress(0, desc="📋 Parsing port definitions...")
|
| 133 |
+
# Call load_custom_ports without a folder_path argument
|
| 134 |
+
port_map = load_custom_ports()
|
| 135 |
|
| 136 |
progress(0.1, desc="🔍 Extracting IP addresses...")
|
| 137 |
ips = extract_ips(file_obj.name)
|
|
|
|
| 264 |
)
|
| 265 |
|
| 266 |
with gr.Column(scale=1):
|
| 267 |
+
gr.Markdown(f"""
|
| 268 |
+
### 🔧 Port Format Support (from {GITHUB_PORTS_RAW_URL.split('/')[-1]})
|
| 269 |
|
| 270 |
**Continuous** (your format):
|
| 271 |
```
|
|
|
|
| 310 |
|
| 311 |
|
| 312 |
if __name__ == "__main__":
|
| 313 |
+
# Removed local file creation, as we're now fetching from GitHub
|
| 314 |
+
# os.makedirs("ports", exist_ok=True)
|
| 315 |
+
# sample_file = "ports/common_ports.txt"
|
| 316 |
+
# if not os.path.exists(sample_file):
|
| 317 |
+
# with open(sample_file, 'w') as f:
|
| 318 |
+
# f.write("""# Common Ports
|
| 319 |
+
# 21 - FTP
|
| 320 |
+
# 22 - SSH
|
| 321 |
+
# 23 - Telnet
|
| 322 |
+
# 25 - SMTP
|
| 323 |
+
# 80 - HTTP
|
| 324 |
+
# 443 - HTTPS
|
| 325 |
+
# 3306 - MySQL
|
| 326 |
+
# 3307 - MySQL Alternate
|
| 327 |
+
# 3389 - RDP
|
| 328 |
+
# 5432 - PostgreSQL
|
| 329 |
+
# 5433 - PostgreSQL Alternate
|
| 330 |
+
# 8080 - HTTP Alt
|
| 331 |
+
# 8443 - HTTPS Alt
|
| 332 |
+
# """)
|
| 333 |
|
| 334 |
demo.queue(max_size=10)
|
| 335 |
demo.launch(
|