wuhp commited on
Commit
1163080
ยท
verified ยท
1 Parent(s): 99ef17e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +174 -134
app.py CHANGED
@@ -20,7 +20,12 @@ GITHUB_REPO_OWNER = "Wuhpondiscord"
20
  GITHUB_REPO_NAME = "ports"
21
  GITHUB_PORTS_DIR_PATH = "ports" # The directory within the repo where port files are located
22
 
23
- # --- Enhanced Port Parsing with Multi-Port Service Detection ---
 
 
 
 
 
24
 
25
  def get_github_directory_contents(owner: str, repo: str, path: str) -> List[Dict]:
26
  """
@@ -37,120 +42,167 @@ def get_github_directory_contents(owner: str, repo: str, path: str) -> List[Dict
37
  print(f"โš ๏ธ Error fetching GitHub directory listing for {path}: {e}")
38
  return []
39
 
40
- def load_custom_ports() -> Dict[int, str]:
41
  """
42
- Enhanced parser that:
43
- 1. Handles continuous format: '135 - MS SQL1433 - MSSQL1521 - Oracle'
44
- 2. Detects services on multiple ports
45
- 3. Groups related services
46
- Loads port definitions from all .txt files in the specified GitHub directory.
47
  """
48
- port_map = {80: "HTTP", 443: "HTTPS"}
49
- service_ports = defaultdict(list) # Track which ports belong to same service
50
-
51
- # Get the list of files from the GitHub directory
52
- repo_contents = get_github_directory_contents(GITHUB_REPO_OWNER, GITHUB_REPO_NAME, GITHUB_PORTS_DIR_PATH)
53
-
54
- port_files_urls = []
55
- for item in repo_contents:
56
- if item.get("type") == "file" and item.get("name", "").endswith(('.txt', '.csv', '.conf', '.list')):
57
- # The 'download_url' is the raw URL for the file content
58
- port_files_urls.append((item['name'], item['download_url']))
59
-
60
- if not port_files_urls:
61
- print(f"โš ๏ธ No port definition files found in '{GITHUB_PORTS_DIR_PATH}' on GitHub. Using default (80, 443).")
62
- return port_map
63
 
64
- print(f"โœ… Found {len(port_files_urls)} port definition files to process.")
65
-
66
- for filename, file_url in port_files_urls:
 
 
67
  try:
68
- print(f"๐ŸŒ Fetching port definitions from: {filename} ({file_url})")
69
- response = requests.get(file_url, timeout=10)
70
- response.raise_for_status()
71
- content = response.text
72
- print(f"โœ… Successfully fetched content for {filename}.")
73
-
74
- # --- Parsing Logic (remains largely the same) ---
75
-
76
- # Strategy 1: Continuous format parser
77
- continuous_pattern = r'(\d+)\s*-\s*([A-Za-z][\w\s\-\.\(\)]*?)(?=\d+\s*-|$)'
78
- matches = re.findall(continuous_pattern, content, re.MULTILINE)
79
-
80
- for port_str, desc in matches:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  try:
82
  port_num = int(port_str)
83
  if 1 <= port_num <= 65535:
84
- desc = desc.strip().strip('-').strip()
85
  desc = re.sub(r'\s+', ' ', desc)
86
  if desc:
87
- # Extract base service name
88
  base_service = desc.split('-')[0].strip()
89
  service_ports[base_service].append(port_num)
90
- port_map[port_num] = desc
 
91
  except ValueError:
92
  continue
93
-
94
- # Strategy 2: Line-by-line format
95
- for line in content.split('\n'):
96
- line = line.strip()
97
- if not line or line.startswith('#'):
98
- continue
99
-
100
- # Skip if already matched by continuous pattern
101
- if re.match(r'^\d+\s*-\s*[A-Za-z]', line):
102
- continue
103
-
104
- patterns = [
105
- r'^(.+?)\s*[:=]\s*(\d+)', # e.g., MySQL: 3306
106
- r'port\s*[:=]?\s*(\d+)\s+(.+)', # e.g., port 22 SSH
107
- r'(\d+)\s*/\s*\w+\s+(.+)', # e.g., 22/tcp SSH
108
- r'^(\d+)\s+(.+)$', # e.g., 22 SSH
109
- ]
110
-
111
- for pattern in patterns:
112
- match = re.match(pattern, line, re.IGNORECASE)
113
- if match:
114
- groups = match.groups()
115
-
116
- if groups[0].isdigit():
117
- port_str, desc = groups[0], groups[1]
118
- else:
119
- desc, port_str = groups[0], groups[1]
120
-
121
- try:
122
- port_num = int(port_str)
123
- if 1 <= port_num <= 65535:
124
- desc = re.sub(r'[^\w\s\-\.]+', '', desc).strip()
125
- desc = re.sub(r'\s+', ' ', desc)
126
- if desc:
127
- base_service = desc.split('-')[0].strip()
128
- service_ports[base_service].append(port_num)
129
- port_map[port_num] = desc
130
- break
131
- except ValueError:
132
- continue
133
-
134
- except requests.exceptions.RequestException as e:
135
- print(f"โš ๏ธ Error fetching {filename} from GitHub: {e}")
136
- except Exception as e:
137
- print(f"โš ๏ธ Error parsing {filename}: {e}")
138
 
139
- # Add multi-port info to descriptions
140
  for service, ports in service_ports.items():
141
  if len(ports) > 1:
142
  ports_sorted = sorted(ports)
143
  for port in ports_sorted:
144
- if port in port_map:
145
- current_desc = port_map[port]
146
  if "also on" not in current_desc.lower():
147
  other_ports = [str(p) for p in ports_sorted if p != port]
148
  if other_ports:
149
- port_map[port] = f"{current_desc} (also on {','.join(other_ports[:3])}{'...' if len(other_ports) > 3 else ''})"
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
- print(f"โœ… Loaded {len(port_map)} port definitions in total from GitHub.")
152
- return port_map
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
 
155
  # --- IP Extraction ---
156
 
@@ -299,15 +351,17 @@ def probe_target(ip: str, port: int, port_desc: str) -> Optional[Dict]:
299
  pass
300
 
301
 
302
- def start_analysis(file_obj, max_threads: int = 50, progress=gr.Progress()):
303
  """Main analysis with pure threading."""
304
  if file_obj is None:
305
  return pd.DataFrame([{"Error": "No file uploaded"}]), None
306
 
 
 
 
307
  try:
308
- progress(0, desc="๐Ÿ“‹ Parsing port definitions...")
309
- # Call load_custom_ports without a folder_path argument
310
- port_map = load_custom_ports()
311
 
312
  progress(0.1, desc="๐Ÿ” Extracting IP addresses...")
313
  ips = extract_ips(file_obj.name)
@@ -363,7 +417,7 @@ def start_analysis(file_obj, max_threads: int = 50, progress=gr.Progress()):
363
  return pd.DataFrame([{"Error": f"Analysis failed: {str(e)}"}]), None
364
 
365
 
366
- # --- UI ---
367
 
368
  custom_css = """
369
  .gradio-container {
@@ -388,7 +442,7 @@ custom_css = """
388
  }
389
  """
390
 
391
- demo = gr.Blocks(title="C2 Deep Scanner Pro", css=custom_css) # Apply CSS here
392
 
393
  with demo:
394
  gr.HTML("""
@@ -398,22 +452,7 @@ with demo:
398
  </div>
399
  """)
400
 
401
- with gr.Row():
402
- gr.Markdown("""
403
- ### ๐Ÿ“Œ Features
404
- - **Enhanced Banner Detection**: HTTP, HTTPS, and raw socket banners
405
- - **Multi-Port Services**: Shows related ports (e.g., MySQL on 3306, 3307)
406
- - **Smart Protocol Detection**: Tries HTTP and HTTPS automatically
407
- - **C2 Detection**: Cobalt Strike, Metasploit, Covenant, Empire & more
408
- """)
409
-
410
- gr.Markdown("""
411
- ### ๐Ÿ“ Input Files
412
- - VirusTotal JSON exports
413
- - SIEM/IDS log files
414
- - Network scan results
415
- - Plain text IP lists
416
- """)
417
 
418
  gr.Markdown("---")
419
 
@@ -440,25 +479,19 @@ with demo:
440
  )
441
 
442
  with gr.Column(scale=1):
443
- gr.Markdown(f"""
444
- ### ๐Ÿ”ง Port Format Support (from /{GITHUB_PORTS_DIR_PATH}/ on GitHub)
445
-
446
- **Continuous** (your format):
447
- ```
448
- 135 - MS SQL1433 - MSSQL
449
- ```
450
-
451
- **Standard formats**:
452
- ```
453
- 80 - HTTP
454
- MySQL: 3306
455
- 22/tcp SSH
456
- ```
457
 
458
- Multi-port services shown as:
459
- ```
460
- MySQL (also on 3307,3308)
461
- ```
462
  """)
463
 
464
  gr.Markdown("---")
@@ -473,7 +506,7 @@ with demo:
473
 
474
  run_btn.click(
475
  fn=start_analysis,
476
- inputs=[file_input, max_threads],
477
  outputs=[output_table, download_btn]
478
  )
479
 
@@ -486,6 +519,13 @@ with demo:
486
 
487
 
488
  if __name__ == "__main__":
 
 
 
 
 
 
 
489
  demo.queue(max_size=10)
490
  demo.launch(
491
  server_name="0.0.0.0",
@@ -493,5 +533,5 @@ if __name__ == "__main__":
493
  share=False,
494
  show_error=True,
495
  theme=gr.themes.Soft(),
496
- ssr_mode=False # CRITICAL: Disables SSR to prevent event loop errors
497
  )
 
20
  GITHUB_REPO_NAME = "ports"
21
  GITHUB_PORTS_DIR_PATH = "ports" # The directory within the repo where port files are located
22
 
23
+ # Global variable to store parsed port definitions from all files
24
+ # Format: {filename: {port_num: description}}
25
+ ALL_GITHUB_PORT_DEFINITIONS: Dict[str, Dict[int, str]] = {}
26
+ AVAILABLE_PORT_FILES: List[str] = [] # List of filenames from GitHub
27
+
28
+ # --- GitHub File Listing and Caching ---
29
 
30
  def get_github_directory_contents(owner: str, repo: str, path: str) -> List[Dict]:
31
  """
 
42
  print(f"โš ๏ธ Error fetching GitHub directory listing for {path}: {e}")
43
  return []
44
 
45
+ def parse_single_port_file_content(content: str) -> Dict[int, str]:
46
  """
47
+ Parses the content of a single port definition file.
48
+ Returns a dictionary of {port: description}.
 
 
 
49
  """
50
+ single_file_port_map = {}
51
+ service_ports = defaultdict(list) # For multi-port detection within this file's context
 
 
 
 
 
 
 
 
 
 
 
 
 
52
 
53
+ # Strategy 1: Continuous format parser
54
+ continuous_pattern = r'(\d+)\s*-\s*([A-Za-z][\w\s\-\.\(\)]*?)(?=\d+\s*-|$)'
55
+ matches = re.findall(continuous_pattern, content, re.MULTILINE)
56
+
57
+ for port_str, desc in matches:
58
  try:
59
+ port_num = int(port_str)
60
+ if 1 <= port_num <= 65535:
61
+ desc = desc.strip().strip('-').strip()
62
+ desc = re.sub(r'\s+', ' ', desc)
63
+ if desc:
64
+ base_service = desc.split('-')[0].strip()
65
+ service_ports[base_service].append(port_num)
66
+ single_file_port_map[port_num] = desc
67
+ except ValueError:
68
+ continue
69
+
70
+ # Strategy 2: Line-by-line format
71
+ for line in content.split('\n'):
72
+ line = line.strip()
73
+ if not line or line.startswith('#'):
74
+ continue
75
+
76
+ if re.match(r'^\d+\s*-\s*[A-Za-z]', line): # Skip if already matched by continuous pattern
77
+ continue
78
+
79
+ patterns = [
80
+ r'^(.+?)\s*[:=]\s*(\d+)', # e.g., MySQL: 3306
81
+ r'port\s*[:=]?\s*(\d+)\s+(.+)', # e.g., port 22 SSH
82
+ r'(\d+)\s*/\s*\w+\s+(.+)', # e.g., 22/tcp SSH
83
+ r'^(\d+)\s+(.+)$', # e.g., 22 SSH
84
+ ]
85
+
86
+ for pattern in patterns:
87
+ match = re.match(pattern, line, re.IGNORECASE)
88
+ if match:
89
+ groups = match.groups()
90
+
91
+ if groups[0].isdigit():
92
+ port_str, desc = groups[0], groups[1]
93
+ else:
94
+ desc, port_str = groups[0], groups[1]
95
+
96
  try:
97
  port_num = int(port_str)
98
  if 1 <= port_num <= 65535:
99
+ desc = re.sub(r'[^\w\s\-\.]+', '', desc).strip()
100
  desc = re.sub(r'\s+', ' ', desc)
101
  if desc:
 
102
  base_service = desc.split('-')[0].strip()
103
  service_ports[base_service].append(port_num)
104
+ single_file_port_map[port_num] = desc
105
+ break
106
  except ValueError:
107
  continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
 
109
+ # Add multi-port info to descriptions (within this single file's context)
110
  for service, ports in service_ports.items():
111
  if len(ports) > 1:
112
  ports_sorted = sorted(ports)
113
  for port in ports_sorted:
114
+ if port in single_file_port_map:
115
+ current_desc = single_file_port_map[port]
116
  if "also on" not in current_desc.lower():
117
  other_ports = [str(p) for p in ports_sorted if p != port]
118
  if other_ports:
119
+ single_file_port_map[port] = f"{current_desc} (also on {','.join(other_ports[:3])}{'...' if len(other_ports) > 3 else ''})"
120
+
121
+ return single_file_port_map
122
+
123
+ def cache_all_github_port_files():
124
+ """
125
+ Fetches and parses all port definition files from GitHub, storing them in
126
+ the global ALL_GITHUB_PORT_DEFINITIONS and AVAILABLE_PORT_FILES.
127
+ """
128
+ global ALL_GITHUB_PORT_DEFINITIONS, AVAILABLE_PORT_FILES
129
+ ALL_GITHUB_PORT_DEFINITIONS.clear()
130
+ AVAILABLE_PORT_FILES.clear()
131
+
132
+ repo_contents = get_github_directory_contents(GITHUB_REPO_OWNER, GITHUB_REPO_NAME, GITHUB_PORTS_DIR_PATH)
133
 
134
+ port_files_to_fetch = []
135
+ for item in repo_contents:
136
+ if item.get("type") == "file" and item.get("name", "").endswith(('.txt', '.csv', '.conf', '.list')):
137
+ port_files_to_fetch.append((item['name'], item['download_url']))
138
+
139
+ if not port_files_to_fetch:
140
+ print(f"โš ๏ธ No port definition files found in '{GITHUB_PORTS_DIR_PATH}' on GitHub.")
141
+ return
142
+
143
+ print(f"โœ… Found {len(port_files_to_fetch)} port definition files to cache.")
144
+
145
+ for filename, file_url in port_files_to_fetch:
146
+ try:
147
+ print(f"๐ŸŒ Caching {filename} from {file_url}")
148
+ response = requests.get(file_url, timeout=10)
149
+ response.raise_for_status()
150
+ content = response.text
151
+ ALL_GITHUB_PORT_DEFINITIONS[filename] = parse_single_port_file_content(content)
152
+ AVAILABLE_PORT_FILES.append(filename)
153
+ print(f"โœ… Cached {filename} with {len(ALL_GITHUB_PORT_DEFINITIONS[filename])} ports.")
154
+ except requests.exceptions.RequestException as e:
155
+ print(f"โš ๏ธ Error fetching {filename} for caching: {e}")
156
+ except Exception as e:
157
+ print(f"โš ๏ธ Error parsing {filename} for caching: {e}")
158
+
159
+ AVAILABLE_PORT_FILES.sort() # Keep the list sorted for UI
160
+ print(f"Finished caching. Total {len(AVAILABLE_PORT_FILES)} files available.")
161
+
162
+ # --- Dynamic Port Map Construction ---
163
+
164
+ def get_selected_port_map(selected_files: List[str]) -> Dict[int, str]:
165
+ """
166
+ Combines port definitions from selected files into a single port_map.
167
+ """
168
+ combined_port_map = {80: "HTTP", 443: "HTTPS"} # Always include defaults
169
+ all_service_ports = defaultdict(list) # To track multi-port across all selected files
170
 
171
+ if not selected_files:
172
+ print("No port files selected. Using default ports (80, 443).")
173
+ return combined_port_map
174
+
175
+ for filename in selected_files:
176
+ if filename in ALL_GITHUB_PORT_DEFINITIONS:
177
+ file_ports = ALL_GITHUB_PORT_DEFINITIONS[filename]
178
+ for port_num, desc in file_ports.items():
179
+ if port_num not in combined_port_map:
180
+ combined_port_map[port_num] = desc
181
+ else:
182
+ # If port exists, append new description if different, or choose more verbose
183
+ existing_desc = combined_port_map[port_num]
184
+ if desc != existing_desc and len(desc) > len(existing_desc):
185
+ combined_port_map[port_num] = desc
186
+
187
+ # Update service_ports for multi-port detection across combined map
188
+ base_service = desc.split('-')[0].strip()
189
+ if port_num not in all_service_ports[base_service]:
190
+ all_service_ports[base_service].append(port_num)
191
+
192
+ # Re-apply multi-port info based on the combined map
193
+ for service, ports in all_service_ports.items():
194
+ if len(ports) > 1:
195
+ ports_sorted = sorted(ports)
196
+ for port in ports_sorted:
197
+ if port in combined_port_map:
198
+ current_desc = combined_port_map[port]
199
+ if "also on" not in current_desc.lower():
200
+ other_ports = [str(p) for p in ports_sorted if p != port]
201
+ if other_ports:
202
+ combined_port_map[port] = f"{current_desc} (also on {','.join(other_ports[:3])}{'...' if len(other_ports) > 3 else ''})"
203
+
204
+ print(f"Generated port map from {len(selected_files)} selected files with {len(combined_port_map)} total port definitions.")
205
+ return combined_port_map
206
 
207
  # --- IP Extraction ---
208
 
 
351
  pass
352
 
353
 
354
+ def start_analysis(file_obj, max_threads: int, selected_port_files: List[str], progress=gr.Progress()):
355
  """Main analysis with pure threading."""
356
  if file_obj is None:
357
  return pd.DataFrame([{"Error": "No file uploaded"}]), None
358
 
359
+ if not selected_port_files:
360
+ return pd.DataFrame([{"Error": "Please select at least one port list to scan."}]), None
361
+
362
  try:
363
+ progress(0, desc="๐Ÿ“‹ Generating port map from selected lists...")
364
+ port_map = get_selected_port_map(selected_port_files)
 
365
 
366
  progress(0.1, desc="๐Ÿ” Extracting IP addresses...")
367
  ips = extract_ips(file_obj.name)
 
417
  return pd.DataFrame([{"Error": f"Analysis failed: {str(e)}"}]), None
418
 
419
 
420
+ # --- Gradio UI ---
421
 
422
  custom_css = """
423
  .gradio-container {
 
442
  }
443
  """
444
 
445
+ demo = gr.Blocks(title="C2 Deep Scanner Pro", css=custom_css)
446
 
447
  with demo:
448
  gr.HTML("""
 
452
  </div>
453
  """)
454
 
455
+ # Removed the large feature/input files markdown sections
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
456
 
457
  gr.Markdown("---")
458
 
 
479
  )
480
 
481
  with gr.Column(scale=1):
482
+ # Port List Selector
483
+ port_file_selector = gr.CheckboxGroup(
484
+ choices=AVAILABLE_PORT_FILES, # Will be populated on launch
485
+ value=AVAILABLE_PORT_FILES, # Select all by default
486
+ label=f"๐Ÿ”ง Select Port Lists (from /{GITHUB_PORTS_DIR_PATH}/ on GitHub)",
487
+ info="Choose which port definition files to use for scanning."
488
+ )
489
+ gr.Markdown("""
490
+ **Port Format Support:**
491
+ - **Continuous** (e.g., `135 - MS SQL1433 - MSSQL`)
492
+ - **Standard formats** (e.g., `80 - HTTP`, `MySQL: 3306`, `22/tcp SSH`)
 
 
 
493
 
494
+ Multi-port services shown as: `MySQL (also on 3307,3308)`
 
 
 
495
  """)
496
 
497
  gr.Markdown("---")
 
506
 
507
  run_btn.click(
508
  fn=start_analysis,
509
+ inputs=[file_input, max_threads, port_file_selector], # Added port_file_selector
510
  outputs=[output_table, download_btn]
511
  )
512
 
 
519
 
520
 
521
  if __name__ == "__main__":
522
+ # Cache port files BEFORE launching the Gradio app
523
+ # This populates AVAILABLE_PORT_FILES for the CheckboxGroup
524
+ cache_all_github_port_files()
525
+
526
+ # Update the choices for the CheckboxGroup after caching
527
+ # This must be done here if the Gradio app is already defined
528
+ # or ensure AVAILABLE_PORT_FILES is populated before gr.CheckboxGroup is instantiated
529
  demo.queue(max_size=10)
530
  demo.launch(
531
  server_name="0.0.0.0",
 
533
  share=False,
534
  show_error=True,
535
  theme=gr.themes.Soft(),
536
+ ssr_mode=False
537
  )