├── assets ├── image-20250727174545111.png ├── image-20250727174917003.png └── image-20250727175111548.png ├── README.md └── UnauthorizedScan.py /assets/image-20250727174545111.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoenix118go/Unauthorized_VUL_GUI/HEAD/assets/image-20250727174545111.png -------------------------------------------------------------------------------- /assets/image-20250727174917003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoenix118go/Unauthorized_VUL_GUI/HEAD/assets/image-20250727174917003.png -------------------------------------------------------------------------------- /assets/image-20250727175111548.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoenix118go/Unauthorized_VUL_GUI/HEAD/assets/image-20250727175111548.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unauthorized_VUL_GUI 2 | 3 | 一款最全未授权访问漏洞批量检测工具,集成40+常见未授权访问漏洞;使用PYQT6,为其添加了GUI界面 4 | 5 | 1.弥补了源代码中不支持自定义端口的功能 6 | 7 | 2.导出功能支持excel数据导出,存在未授权访问漏洞的会用红色标识 8 | 9 | 3.【2025.07.31】修复了扫描输出的显示问题以及提供了扫描端口的优先级问题 10 | 11 | 12 | 13 | ### GUI显示 14 | 15 | ![image-20250727174545111](./assets/image-20250727174545111.png) 16 | 17 | 扫描过程&结果 18 | 19 | ![image-20250727174917003](./assets/image-20250727174917003.png) 20 | 21 | 导出结果显示,可能存在漏洞的地址会被标红 22 | 23 | ![image-20250727175111548](./assets/image-20250727175111548.png) 24 | 25 | 26 | 27 | ## 参考链接 28 | 29 | - https://github.com/hackerchuan1/Unauthorized_VUl 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /UnauthorizedScan.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | import signal 4 | from datetime import datetime 5 | from concurrent.futures import ThreadPoolExecutor, as_completed 6 | from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem, 7 | QPushButton, QTextEdit, QLineEdit, QFileDialog, QLabel, 8 | QProgressBar, QCheckBox, QGroupBox, QFormLayout, QSpinBox, 9 | QMessageBox, QSplitter, QApplication, QScrollArea) 10 | from PyQt6.QtCore import Qt, QThread, pyqtSignal, QUrl 11 | from PyQt6.QtGui import QColor, QTextCursor, QIcon 12 | import openpyxl 13 | from openpyxl.styles import Font, Alignment, Border, Side, PatternFill 14 | from openpyxl.utils import get_column_letter 15 | import requests 16 | import socket 17 | import urllib3 18 | from urllib.parse import urlparse 19 | 20 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 21 | 22 | 23 | def normalize_target_input(raw_target: str) : 24 | """ 25 | 目标输入解析:支持IP、IP:PORT、URL、域名、域名:PORT等格式 26 | 返回标准化信息字典(含host、port、scheme、full_url) 27 | """ 28 | if not raw_target : 29 | return None 30 | 31 | # 处理纯IP:PORT格式 32 | if ":" in raw_target and not raw_target.startswith(("http://", "https://", "ftp://")) : 33 | parts = raw_target.split(":", 1) # 只分割第一个冒号(避免IPv6冲突) 34 | if len(parts) == 2 and parts[1].isdigit() : 35 | host, port = parts[0], int(parts[1]) 36 | return { 37 | "host" : host, 38 | "port" : port, 39 | "scheme" : "http", 40 | "full_url" : f"http://{host}:{port}" 41 | } 42 | 43 | # 处理URL或域名格式 44 | if not raw_target.startswith(("http://", "https://")) : 45 | raw_target = "http://" + raw_target # 补全协议 46 | 47 | parsed = urlparse(raw_target) 48 | return { 49 | "host" : parsed.hostname or raw_target, # 兼容纯IP输入 50 | "port" : parsed.port, 51 | "scheme" : parsed.scheme or "http", 52 | "full_url" : raw_target 53 | } 54 | 55 | 56 | # ---------------------- 2. 漏洞扫描核心类(含速率优化) ---------------------- 57 | class VulnerabilityScanner : 58 | def __init__(self, proxy=None, timeout=5) : # 缩短超时时间(可配置) 59 | self.proxy = proxy 60 | self.timeout = timeout 61 | # 复用会话(连接池优化) 62 | self.session = requests.Session() 63 | self.session.mount('http://', requests.adapters.HTTPAdapter(pool_connections=20, pool_maxsize=20)) 64 | self.session.mount('https://', requests.adapters.HTTPAdapter(pool_connections=20, pool_maxsize=20)) 65 | 66 | if proxy : 67 | self.session.proxies = {"http" : proxy, "https" : proxy, "socks": proxy,"socks5": proxy} 68 | self.session.verify = False 69 | self.session.headers = { 70 | "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36" 71 | } 72 | 73 | # 服务检测器和默认端口(保持原有映射) 74 | self.detectors = { 75 | "ftp" : self.check_ftp, 76 | "redis" : self.check_redis, 77 | "docker" : self.check_docker, 78 | "docker_registry" : self.check_docker_registry, 79 | "elasticsearch" : self.check_elasticsearch, 80 | "jenkins" : self.check_jenkins, 81 | "kibana" : self.check_kibana, 82 | "zookeeper" : self.check_zookeeper, 83 | "mongodb" : self.check_mongodb, 84 | "kubernetes" : self.check_kubernetes, 85 | "jupyter" : self.check_jupyter, 86 | "nacos" : self.check_nacos, 87 | "ollama" : self.check_ollama, 88 | "rsync" : self.check_rsync, 89 | "swagger" : self.check_swagger, 90 | "springboot" : self.check_springboot, 91 | "druid" : self.check_druid, 92 | "ldap" : self.check_ldap, 93 | "vnc" : self.check_vnc, 94 | "couchdb" : self.check_couchdb, 95 | "spark" : self.check_spark, 96 | "weblogic" : self.check_weblogic, 97 | "hadoop" : self.check_hadoop, 98 | "jboss" : self.check_jboss, 99 | "activemq" : self.check_activemq, 100 | "zabbix" : self.check_zabbix, 101 | "memcached" : self.check_memcached, 102 | "rabbitmq" : self.check_rabbitmq, 103 | "nfs" : self.check_nfs, 104 | "dubbo" : self.check_dubbo, 105 | "solr" : self.check_solr, 106 | "harbor" : self.check_harbor, 107 | "smb" : self.check_smb, 108 | "wordpress" : self.check_wordpress, 109 | "crowd" : self.check_crowd, 110 | "uwsgi" : self.check_uwsgi, 111 | "kong" : self.check_kong, 112 | "thinkadmin" : self.check_thinkadmin 113 | } 114 | 115 | self.default_ports = { 116 | "ftp" : 21, 117 | "redis" : 6379, 118 | "docker" : 2375, 119 | "docker_registry" : 5000, 120 | "elasticsearch" : 9200, 121 | "jenkins" : 8080, 122 | "kibana" : 5601, 123 | "zookeeper" : 2181, 124 | "mongodb" : 27017, 125 | "kubernetes" : 8080, 126 | "jupyter" : 8888, 127 | "nacos" : 8848, 128 | "ollama" : 11434, 129 | "rsync" : 873, 130 | "swagger" : 80, 131 | "springboot" : 8080, 132 | "druid" : 8080, 133 | "ldap" : 389, 134 | "vnc" : 5900, 135 | "couchdb" : 5984, 136 | "spark" : 6066, 137 | "weblogic" : 7001, 138 | "hadoop" : 8088, 139 | "jboss" : 8080, 140 | "activemq" : 8161, 141 | "zabbix" : 10051, 142 | "memcached" : 11211, 143 | "rabbitmq" : 15672, 144 | "nfs" : 2049, 145 | "dubbo" : 28096, 146 | "solr" : 8983, 147 | "harbor" : 80, 148 | "smb" : 445, 149 | "wordpress" : 80, 150 | "crowd" : 8095, 151 | "uwsgi" : 1717, 152 | "kong" : 8001, 153 | "thinkadmin" : 80 154 | } 155 | 156 | # 非HTTP协议服务列表 157 | self.NON_HTTP_SERVICES = { 158 | "smb", "ftp", "redis", "zookeeper", "mongodb", "ldap", 159 | "vnc", "memcached", "nfs", "dubbo", "rsync", "uwsgi" 160 | } 161 | 162 | # ---------------------- 检测方法(含速率优化) ---------------------- 163 | def check_ftp(self, target_info) : 164 | host = target_info["host"] 165 | port = target_info.get("port", 21) 166 | try : 167 | with socket.create_connection((host, port), timeout=self.timeout) as sock : 168 | sock.sendall(b"USER anonymous\r\n") 169 | response = sock.recv(1024).decode(errors="ignore") 170 | if "331" in response : 171 | sock.sendall(b"PASS anonymous@example.com\r\n") 172 | response = sock.recv(1024).decode(errors="ignore") 173 | if "230" in response : 174 | return True, "FTP anonymous login successful" 175 | except Exception as e : 176 | return False, f"检测失败: {str(e)}" 177 | return False, "未发现未授权访问" 178 | 179 | def check_redis(self, target_info) : 180 | host = target_info["host"] 181 | port = target_info.get("port", 6379) 182 | try : 183 | with socket.create_connection((host, port), timeout=self.timeout) as sock : 184 | sock.sendall(b"INFO\r\n") 185 | response = sock.recv(1024).decode(errors="ignore") 186 | if "redis_version" in response : 187 | return True, "Redis未授权访问" 188 | except Exception as e : 189 | return False, f"检测失败: {str(e)}" 190 | return False, "未发现未授权访问" 191 | 192 | def check_docker(self, target_info) : 193 | """检测Docker未授权访问""" 194 | host = target_info["host"] 195 | port = target_info.get("port", 2375) 196 | scheme = target_info.get("scheme", "http") 197 | url = f"{scheme}://{host}:{port}/version" 198 | try : 199 | response = self.session.get(url, timeout=self.timeout) 200 | if response.status_code == 200 and "ApiVersion" in response.text : 201 | return True, f"Docker unauthorized access" 202 | except Exception as e : 203 | return False, f"Docker detection failed: {str(e)}" 204 | return False, "Docker unauthorized access not found" 205 | 206 | def check_docker_registry(self, target_info): 207 | """检测Docker Registry未授权访问""" 208 | host = target_info["host"] 209 | port = target_info.get("port", 5000) 210 | scheme = target_info.get("scheme", "http") 211 | url = f"{scheme}://{host}:{port}/v2/_catalog" 212 | try: 213 | response = self.session.get(url, timeout=self.timeout) 214 | if response.status_code == 200 and "repositories" in response.text: 215 | return True, "Docker Registry unauthorized access" 216 | except Exception as e: 217 | return False, f"Docker Registry detection failed: {str(e)}" 218 | return False, "Docker Registry unauthorized access not found" 219 | 220 | def check_elasticsearch(self, target_info): 221 | """检测Elasticsearch未授权访问""" 222 | host = target_info["host"] 223 | port = target_info.get("port", 9200) 224 | scheme = target_info.get("scheme", "http") 225 | url = f"{scheme}://{host}:{port}/_cat/indices" 226 | try: 227 | response = self.session.get(url, timeout=self.timeout) 228 | if response.status_code == 200 or "green" in response.text or "yellow" in response.text: 229 | return True, "Elasticsearch unauthorized access" 230 | except Exception as e: 231 | return False, f"Elasticsearch detection failed: {str(e)}" 232 | return False, "Elasticsearch unauthorized access not found" 233 | 234 | def check_jenkins(self, target_info): 235 | """检测Jenkins未授权访问""" 236 | host = target_info["host"] 237 | port = target_info.get("port", 8080) 238 | scheme = target_info.get("scheme", "http") 239 | url = f"{scheme}://{host}:{port}/api/json" 240 | try: 241 | response = self.session.get(url, timeout=self.timeout) 242 | if response.status_code == 200 and "jobs" in response.text: 243 | return True, "Jenkins unauthorized access" 244 | except Exception as e: 245 | return False, f"Jenkins detection failed: {str(e)}" 246 | return False, "Jenkins unauthorized access not found" 247 | 248 | def check_kibana(self, target_info): 249 | """检测Kibana未授权访问""" 250 | host = target_info["host"] 251 | port = target_info.get("port", 5601) 252 | scheme = target_info.get("scheme", "http") 253 | url = f"{scheme}://{host}:{port}/api/status" 254 | try: 255 | response = self.session.get(url, timeout=self.timeout) 256 | if response.status_code == 200 and "status" in response.text: 257 | return True, "Kibana unauthorized access" 258 | except Exception as e: 259 | return False, f"Kibana detection failed: {str(e)}" 260 | return False, "Kibana unauthorized access not found" 261 | 262 | def check_zookeeper(self, target_info): 263 | """检测Zookeeper未授权访问""" 264 | host = target_info["host"] 265 | port = target_info.get("port", 2181) 266 | try: 267 | with socket.create_connection((host, port), timeout=self.timeout) as sock: 268 | sock.sendall(b"stat\r\n") 269 | response = sock.recv(1024).decode(errors="ignore") 270 | if "Zookeeper version" in response: 271 | return True, "Zookeeper unauthorized access" 272 | except Exception as e: 273 | return False, f"Zookeeper detection failed: {str(e)}" 274 | return False, "Zookeeper unauthorized access not found" 275 | 276 | def check_mongodb(self, target_info): 277 | """检测MongoDB未授权访问""" 278 | host = target_info["host"] 279 | port = target_info.get("port", 27017) 280 | try: 281 | with socket.create_connection((host, port), timeout=self.timeout) as sock: 282 | sock.sendall(b"db.adminCommand('ping')\r\n") 283 | response = sock.recv(1024).decode(errors="ignore") 284 | if "ok" in response: 285 | return True, "MongoDB unauthorized access" 286 | except Exception as e: 287 | return False, f"MongoDB detection failed: {str(e)}" 288 | return False, "MongoDB unauthorized access not found" 289 | 290 | def check_kubernetes(self, target_info): 291 | """检测Kubernetes未授权访问""" 292 | host = target_info["host"] 293 | port = target_info.get("port", 8080) 294 | scheme = target_info.get("scheme", "http") 295 | url = f"{scheme}://{host}:{port}/api/v1/namespaces/default/pods" 296 | try: 297 | response = self.session.get(url, timeout=self.timeout) 298 | if response.status_code == 200 and "items" in response.text: 299 | return True, "Kubernetes unauthorized access" 300 | except Exception as e: 301 | return False, f"Kubernetes detection failed: {str(e)}" 302 | return False, "Kubernetes unauthorized access not found" 303 | 304 | def check_jupyter(self, target_info): 305 | """检测Jupyter Notebook未授权访问""" 306 | host = target_info["host"] 307 | port = target_info.get("port", 8888) 308 | scheme = target_info.get("scheme", "http") 309 | url = f"{scheme}://{host}:{port}/api/kernels" 310 | try: 311 | response = self.session.get(url, timeout=self.timeout) 312 | if response.status_code == 200 and "kernels" in response.text: 313 | return True, "Jupyter Notebook unauthorized access" 314 | except Exception as e: 315 | return False, f"Jupyter detection failed: {str(e)}" 316 | return False, "Jupyter unauthorized access not found" 317 | 318 | def check_nacos(self, target_info): 319 | """检测Nacos未授权访问""" 320 | host = target_info["host"] 321 | port = target_info.get("port", 8848) 322 | scheme = target_info.get("scheme", "http") 323 | url = f"{scheme}://{host}:{port}/nacos/v1/auth/users?pageNo=1&pageSize=10" 324 | try: 325 | response = self.session.get(url, timeout=self.timeout) 326 | if response.status_code == 200 and "username" in response.text: 327 | return True, "Nacos unauthorized access" 328 | except Exception as e: 329 | return False, f"Nacos detection failed: {str(e)}" 330 | return False, "Nacos unauthorized access not found" 331 | 332 | def check_ollama(self, target_info): 333 | """检测Ollama未授权访问""" 334 | host = target_info["host"] 335 | port = target_info.get("port", 11434) 336 | scheme = target_info.get("scheme", "http") 337 | url = f"{scheme}://{host}:{port}/api/tags" 338 | try: 339 | response = self.session.get(url, timeout=self.timeout) 340 | if response.status_code == 200 and "models" in response.text: 341 | return True, "Ollama unauthorized access" 342 | except Exception as e: 343 | return False, f"Ollama detection failed: {str(e)}" 344 | return False, "Ollama unauthorized access not found" 345 | 346 | def check_rsync(self, target_info): 347 | """检测Rsync未授权访问""" 348 | host = target_info["host"] 349 | port = target_info.get("port", 873) 350 | try: 351 | with socket.create_connection((host, port), timeout=self.timeout) as sock: 352 | sock.sendall(b"@RSYNCD: 31.0\n") 353 | response = sock.recv(1024).decode(errors="ignore") 354 | if "RSYNCD" in response: 355 | return True, "Rsync unauthorized access" 356 | except Exception as e: 357 | return False, f"Rsync detection failed: {str(e)}" 358 | return False, "Rsync unauthorized access not found" 359 | 360 | def check_swagger(self, target_info): 361 | """Swagger UI未授权访问检测""" 362 | host = target_info["host"] 363 | port = target_info.get("port", 80) 364 | scheme = target_info.get("scheme", "http") 365 | 366 | # 常见的Swagger UI路径 367 | paths = [ 368 | "/swagger-ui.html", 369 | "/swagger/index.html", 370 | "/swagger/ui/index", 371 | "/swagger", 372 | "/api-docs", 373 | "/v2/api-docs", 374 | "/swagger-resources", 375 | "/swagger-ui", 376 | "/api/swagger-ui.html", 377 | "/docs", 378 | "/swagger-ui/index.html" 379 | ] 380 | 381 | # 尝试所有可能的路径 382 | for path in paths: 383 | url = f"{scheme}://{host}:{port}{path}" if port else f"{scheme}://{host}{path}" 384 | try: 385 | response = self.session.get(url, timeout=self.timeout) 386 | if response.status_code == 200: 387 | # 使用更精确的识别方法 388 | if any(keyword in response.text for keyword in 389 | ["Swagger UI", "swagger-ui", "swagger.json", "swagger.yaml"]): 390 | return True, f"Swagger UI unauthorized access (path: {path})" 391 | except Exception: 392 | continue 393 | 394 | return False, "Swagger unauthorized access not found" 395 | 396 | def check_springboot(self, target_info): 397 | """检测SpringBoot Actuator未授权访问""" 398 | host = target_info["host"] 399 | port = target_info.get("port", 80) 400 | scheme = target_info.get("scheme", "http") 401 | 402 | # 常见的Actuator路径 403 | paths = [ 404 | "/actuator", 405 | "/actuator/health", 406 | "/actuator/env", 407 | "/actuator/metrics", 408 | "/actuator/beans", 409 | "/actuator/mappings" 410 | ] 411 | 412 | for path in paths: 413 | url = f"{scheme}://{host}:{port}{path}" if port else f"{scheme}://{host}{path}" 414 | try: 415 | response = self.session.get(url, timeout=self.timeout) 416 | if response.status_code == 200 and "actuator" in response.text: 417 | return True, f"SpringBoot Actuator unauthorized access (path: {path})" 418 | except Exception: 419 | continue 420 | 421 | return False, "SpringBoot unauthorized access not found" 422 | 423 | def check_druid(self, target_info): 424 | """检测Druid未授权访问""" 425 | host = target_info["host"] 426 | port = target_info.get("port", 80) 427 | scheme = target_info.get("scheme", "http") 428 | 429 | # 常见的Druid路径 430 | paths = [ 431 | "/druid/index.html", 432 | "/druid/login.html", 433 | "/druid/weburi.html", 434 | "/druid/websession.html", 435 | "/druid/sql.html" 436 | ] 437 | 438 | for path in paths: 439 | url = f"{scheme}://{host}:{port}{path}" if port else f"{scheme}://{host}{path}" 440 | try: 441 | response = self.session.get(url, timeout=self.timeout) 442 | if response.status_code == 200 and "Druid" in response.text: 443 | return True, f"Druid unauthorized access (path: {path})" 444 | except Exception: 445 | continue 446 | 447 | return False, "Druid unauthorized access not found" 448 | 449 | def check_ldap(self, target_info): 450 | """检测LDAP未授权访问""" 451 | host = target_info["host"] 452 | port = target_info.get("port", 389) 453 | try: 454 | with socket.create_connection((host, port), timeout=self.timeout) as sock: 455 | # LDAP匿名绑定尝试 456 | bind_request = bytes.fromhex("30 0c 02 01 01 60 07 02 01 03 04 00 80 00") 457 | sock.sendall(bind_request) 458 | response = sock.recv(1024) 459 | if response and len(response) > 0: 460 | return True, "LDAP unauthorized access (anonymous bind possible)" 461 | except Exception as e: 462 | return False, f"LDAP detection failed: {str(e)}" 463 | return False, "LDAP unauthorized access not found" 464 | 465 | def check_vnc(self, target_info): 466 | """检测VNC未授权访问""" 467 | host = target_info["host"] 468 | port = target_info.get("port", 5900) 469 | try: 470 | with socket.create_connection((host, port), timeout=self.timeout) as sock: 471 | # 读取VNC协议响应 472 | response = sock.recv(1024) 473 | if b"RFB" in response: 474 | return True, "VNC service exposed (possible unauthorized access)" 475 | except Exception as e: 476 | return False, f"VNC detection failed: {str(e)}" 477 | return False, "VNC unauthorized access not found" 478 | 479 | def check_couchdb(self, target_info): 480 | """检测CouchDB未授权访问""" 481 | host = target_info["host"] 482 | port = target_info.get("port", 5984) 483 | scheme = target_info.get("scheme", "http") 484 | url = f"{scheme}://{host}:{port}/_all_dbs" 485 | try: 486 | response = self.session.get(url, timeout=self.timeout) 487 | if response.status_code == 200 and "[" in response.text: 488 | return True, "CouchDB unauthorized access" 489 | except Exception as e: 490 | return False, f"CouchDB detection failed: {str(e)}" 491 | return False, "CouchDB unauthorized access not found" 492 | 493 | def check_spark(self, target_info): 494 | """检测Apache Spark未授权访问""" 495 | host = target_info["host"] 496 | port = target_info.get("port", 6066) 497 | scheme = target_info.get("scheme", "http") 498 | url = f"{scheme}://{host}:{port}/" 499 | try: 500 | response = self.session.get(url, timeout=self.timeout) 501 | if response.status_code == 200 and "Spark" in response.text: 502 | return True, "Apache Spark unauthorized access" 503 | except Exception as e: 504 | return False, f"Apache Spark detection failed: {str(e)}" 505 | return False, "Apache Spark unauthorized access not found" 506 | 507 | def check_weblogic(self, target_info): 508 | """检测Weblogic未授权访问""" 509 | host = target_info["host"] 510 | port = target_info.get("port", 7001) 511 | scheme = target_info.get("scheme", "http") 512 | url = f"{scheme}://{host}:{port}/console/login/LoginForm.jsp" 513 | try: 514 | response = self.session.get(url, timeout=self.timeout) 515 | if response.status_code == 200 and "WebLogic Server" in response.text: 516 | return True, "Weblogic console exposed (possible unauthorized access)" 517 | except Exception as e: 518 | return False, f"Weblogic detection failed: {str(e)}" 519 | return False, "Weblogic unauthorized access not found" 520 | 521 | def check_hadoop(self, target_info): 522 | """检测Hadoop YARN未授权访问""" 523 | host = target_info["host"] 524 | port = target_info.get("port", 8088) 525 | scheme = target_info.get("scheme", "http") 526 | url = f"{scheme}://{host}:{port}/ws/v1/cluster/apps" 527 | try: 528 | response = self.session.get(url, timeout=self.timeout) 529 | if response.status_code == 200 and "apps" in response.text: 530 | return True, "Hadoop YARN unauthorized access" 531 | except Exception as e: 532 | return False, f"Hadoop detection failed: {str(e)}" 533 | return False, "Hadoop unauthorized access not found" 534 | 535 | def check_jboss(self, target_info): 536 | """检测JBoss未授权访问""" 537 | host = target_info["host"] 538 | port = target_info.get("port", 8080) 539 | scheme = target_info.get("scheme", "http") 540 | url = f"{scheme}://{host}:{port}/jmx-console/" 541 | try: 542 | response = self.session.get(url, timeout=self.timeout) 543 | if response.status_code == 200 and "JBoss" in response.text: 544 | return True, "JBoss JMX console exposed (possible unauthorized access)" 545 | except Exception as e: 546 | return False, f"JBoss detection failed: {str(e)}" 547 | return False, "JBoss unauthorized access not found" 548 | 549 | def check_activemq(self, target_info): 550 | """检测ActiveMQ未授权访问""" 551 | host = target_info["host"] 552 | port = target_info.get("port", 8161) 553 | scheme = target_info.get("scheme", "http") 554 | url = f"{scheme}://{host}:{port}/admin/" 555 | try: 556 | response = self.session.get(url, timeout=self.timeout) 557 | if response.status_code == 200 and "ActiveMQ" in response.text: 558 | return True, "ActiveMQ management console exposed (possible unauthorized access)" 559 | except Exception as e: 560 | return False, f"ActiveMQ detection failed: {str(e)}" 561 | return False, "ActiveMQ unauthorized access not found" 562 | 563 | def check_zabbix(self, target_info): 564 | """检测Zabbix未授权访问""" 565 | host = target_info["host"] 566 | port = target_info.get("port", 10051) 567 | scheme = target_info.get("scheme", "http") 568 | url = f"{scheme}://{host}:{port}/" 569 | try: 570 | response = self.session.get(url, timeout=self.timeout) 571 | if response.status_code == 200 and "Zabbix" in response.text: 572 | return True, "Zabbix service exposed (possible unauthorized access)" 573 | except Exception as e: 574 | return False, f"Zabbix detection failed: {str(e)}" 575 | return False, "Zabbix unauthorized access not found" 576 | 577 | def check_memcached(self, target_info): 578 | """检测Memcached未授权访问""" 579 | host = target_info["host"] 580 | port = target_info.get("port", 11211) 581 | try: 582 | with socket.create_connection((host, port), timeout=self.timeout) as sock: 583 | sock.sendall(b"stats\r\n") 584 | response = sock.recv(1024).decode(errors="ignore") 585 | if "STAT" in response: 586 | return True, "Memcached unauthorized access" 587 | except Exception as e: 588 | return False, f"Memcached detection failed: {str(e)}" 589 | return False, "Memcached unauthorized access not found" 590 | 591 | def check_rabbitmq(self, target_info): 592 | """检测RabbitMQ未授权访问""" 593 | host = target_info["host"] 594 | port = target_info.get("port", 15672) 595 | scheme = target_info.get("scheme", "http") 596 | url = f"{scheme}://{host}:{port}/api/overview" 597 | try: 598 | response = self.session.get(url, timeout=self.timeout) 599 | if response.status_code == 200 and "management_version" in response.text: 600 | return True, "RabbitMQ management API unauthorized access" 601 | except Exception as e: 602 | return False, f"RabbitMQ detection failed: {str(e)}" 603 | return False, "RabbitMQ unauthorized access not found" 604 | 605 | def check_nfs(self, target_info): 606 | """检测NFS未授权访问""" 607 | host = target_info["host"] 608 | port = target_info.get("port", 2049) 609 | try: 610 | with socket.create_connection((host, port), timeout=self.timeout) as sock: 611 | sock.sendall(b"\x80\x00\x00\x00") 612 | response = sock.recv(1024) 613 | if response and len(response) > 0: 614 | return True, "NFS service exposed (possible unauthorized access)" 615 | except Exception as e: 616 | return False, f"NFS detection failed: {str(e)}" 617 | return False, "NFS unauthorized access not found" 618 | 619 | def check_dubbo(self, target_info): 620 | """检测Dubbo未授权访问""" 621 | host = target_info["host"] 622 | port = target_info.get("port", 28096) 623 | try: 624 | with socket.create_connection((host, port), timeout=self.timeout) as sock: 625 | sock.sendall(b"ls\r\n") 626 | response = sock.recv(1024).decode(errors="ignore") 627 | if "dubbo>" in response: 628 | return True, "Dubbo console unauthorized access" 629 | except Exception as e: 630 | return False, f"Dubbo detection failed: {str(e)}" 631 | return False, "Dubbo unauthorized access not found" 632 | 633 | def check_solr(self, target_info): 634 | """检测Solr未授权访问""" 635 | host = target_info["host"] 636 | port = target_info.get("port", 8983) 637 | scheme = target_info.get("scheme", "http") 638 | url = f"{scheme}://{host}:{port}/solr/admin/cores" 639 | try: 640 | response = self.session.get(url, timeout=self.timeout) 641 | if response.status_code == 200 and "responseHeader" in response.text: 642 | return True, "Solr unauthorized access" 643 | except Exception as e: 644 | return False, f"Solr detection failed: {str(e)}" 645 | return False, "Solr unauthorized access not found" 646 | 647 | def check_harbor(self, target_info): 648 | """检测Harbor未授权添加管理员漏洞""" 649 | host = target_info["host"] 650 | port = target_info.get("port", 80) 651 | scheme = target_info.get("scheme", "http") 652 | url = f"{scheme}://{host}:{port}/api/v2.0/users" 653 | try: 654 | response = self.session.get(url, timeout=self.timeout) 655 | if response.status_code == 200 and "username" in response.text: 656 | return True, "Harbor user API exposed (possible unauthorized admin addition)" 657 | except Exception as e: 658 | return False, f"Harbor detection failed: {str(e)}" 659 | return False, "Harbor unauthorized access not found" 660 | 661 | def check_smb(self, target_info): 662 | """检测Windows共享未授权访问""" 663 | host = target_info["host"] 664 | port = target_info.get("port", 445) 665 | try: 666 | with socket.create_connection((host, port), timeout=self.timeout) as sock: 667 | # 发送SMB协商请求 668 | sock.sendall(b"\x00\x00\x00\x85\xff\x53\x4d\x42\x72\x00\x00\x00\x00\x18\x53\xc8") 669 | response = sock.recv(1024) 670 | if response and len(response) > 0: 671 | return True, "SMB service exposed (possible unauthorized access)" 672 | except Exception as e: 673 | return False, f"SMB detection failed: {str(e)}" 674 | return False, "SMB unauthorized access not found" 675 | 676 | def check_wordpress(self, target_info): 677 | """检测WordPress未授权访问""" 678 | host = target_info["host"] 679 | port = target_info.get("port", 80) 680 | scheme = target_info.get("scheme", "http") 681 | url = f"{scheme}://{host}:{port}/wp-admin/" 682 | try: 683 | response = self.session.get(url, timeout=self.timeout) 684 | if response.status_code == 200 and "WordPress" in response.text: 685 | return True, "WordPress admin panel exposed (possible unauthorized access)" 686 | except Exception as e: 687 | return False, f"WordPress detection failed: {str(e)}" 688 | return False, "WordPress unauthorized access not found" 689 | 690 | def check_crowd(self, target_info): 691 | """检测Atlassian Crowd未授权访问""" 692 | host = target_info["host"] 693 | port = target_info.get("port", 8095) 694 | scheme = target_info.get("scheme", "http") 695 | url = f"{scheme}://{host}:{port}/crowd/admin/" 696 | try: 697 | response = self.session.get(url, timeout=self.timeout) 698 | if response.status_code == 200 and "Crowd" in response.text: 699 | return True, "Crowd management console exposed (possible unauthorized access)" 700 | except Exception as e: 701 | return False, f"Crowd detection failed: {str(e)}" 702 | return False, "Crowd unauthorized access not found" 703 | 704 | def check_uwsgi(self, target_info): 705 | """检测uWSGI未授权访问""" 706 | host = target_info["host"] 707 | port = target_info.get("port", 1717) 708 | try: 709 | with socket.create_connection((host, port), timeout=self.timeout) as sock: 710 | sock.sendall(b"add-mapping /foo /bar\n") 711 | response = sock.recv(1024).decode(errors="ignore") 712 | if "OK" in response: 713 | return True, "uWSGI unauthorized access" 714 | except Exception as e: 715 | return False, f"uWSGI detection failed: {str(e)}" 716 | return False, "uWSGI unauthorized access not found" 717 | 718 | def check_kong(self, target_info): 719 | """检测Kong未授权访问""" 720 | host = target_info["host"] 721 | port = target_info.get("port", 8001) 722 | scheme = target_info.get("scheme", "http") 723 | url = f"{scheme}://{host}:{port}/" 724 | try: 725 | response = self.session.get(url, timeout=self.timeout) 726 | if response.status_code == 200 and "kong" in response.text: 727 | return True, "Kong management API exposed (possible unauthorized access)" 728 | except Exception as e: 729 | return False, f"Kong detection failed: {str(e)}" 730 | return False, "Kong unauthorized access not found" 731 | 732 | def check_thinkadmin(self, target_info): 733 | """检测ThinkAdmin未授权访问""" 734 | host = target_info["host"] 735 | port = target_info.get("port", 80) 736 | scheme = target_info.get("scheme", "http") 737 | url = f"{scheme}://{host}:{port}/admin.html" 738 | try: 739 | response = self.session.get(url, timeout=self.timeout) 740 | if response.status_code == 200 and "ThinkAdmin" in response.text: 741 | return True, "ThinkAdmin admin panel exposed (possible unauthorized access)" 742 | except Exception as e: 743 | return False, f"ThinkAdmin detection failed: {str(e)}" 744 | return False, "ThinkAdmin unauthorized access not found" 745 | 746 | def scan_single_service(self, target_info, service_name, custom_ports=None) : 747 | # 优先使用用户自定义端口,其次默认端口 748 | # 1. 获取用户输入的目标端口(URL中自带的端口) 749 | user_input_port = target_info.get("port") 750 | 751 | # 2. 获取服务自定义端口(表格中用户设置的端口) 752 | custom_port = custom_ports.get(service_name) if custom_ports else None 753 | 754 | # 3. 确定最终使用的端口:用户输入端口优先,其次是自定义端口,最后是默认端口 755 | if user_input_port is not None : # 优先使用用户输入的目标端口 756 | port = user_input_port 757 | elif custom_port is not None : # 其次使用服务自定义端口 758 | port = custom_port 759 | else : # 最后使用服务默认端口 760 | port = self.default_ports.get(service_name) 761 | 762 | if not port : 763 | return False, f"服务{service_name}无可用端口(用户未输入端口且无默认配置)" 764 | 765 | # 使用最终确定的端口 766 | service_target = target_info.copy() 767 | service_target["port"] = port 768 | 769 | detector = self.detectors.get(service_name) 770 | if not detector : 771 | return False, f"不支持的服务: {service_name}" 772 | return detector(service_target) 773 | 774 | def detect_service(self, target_info, service_name): 775 | """检测指定服务(保留原有逻辑)""" 776 | detector = self.detectors.get(service_name) 777 | if not detector: 778 | return False, f"Unsupported service: {service_name}" 779 | return detector(target_info) 780 | 781 | def get_display_url(self, target_info, service_name, custom_ports=None) : 782 | """生成显示用的URL,包含服务实际扫描的端口""" 783 | # 获取实际使用的端口(自定义端口优先,其次默认端口) 784 | user_input_port = target_info.get("port") 785 | custom_port = custom_ports.get(service_name) if custom_ports else None 786 | 787 | if user_input_port is not None : 788 | port = user_input_port 789 | elif custom_port is not None : 790 | port = custom_port 791 | else : 792 | port = self.default_ports.get(service_name) 793 | 794 | if not port : 795 | return f"{target_info['host']} (端口未知)" 796 | 797 | # 非HTTP服务:仅返回 host:port 798 | if service_name in self.NON_HTTP_SERVICES : 799 | return f"{target_info['host']}:{port}" 800 | # HTTP服务:返回 scheme://host:port 801 | else : 802 | scheme = target_info["scheme"] or "http" 803 | return f"{scheme}://{target_info['host']}:{port}" 804 | 805 | 806 | class ScanThread(QThread): 807 | # 新增:当前扫描服务日志信号 808 | scanning_service = pyqtSignal(str, str, str) # 目标、服务、扫描路径 809 | # 新增:服务扫描结果信号 810 | service_result = pyqtSignal(str, str, bool, str) # 目标、服务、是否存在漏洞、信息 811 | progress_updated = pyqtSignal(int) # 精确进度(0-100) 812 | scan_completed = pyqtSignal(list) 813 | 814 | def __init__(self, targets, services, custom_ports=None, proxy=None, threads=20): 815 | super().__init__() 816 | self.targets = targets # 标准化后的目标列表(含host、port等) 817 | self.services = services # 选中的服务列表 818 | self.custom_ports = custom_ports or {} # 用户自定义端口 819 | self.proxy = proxy 820 | self.threads = threads 821 | self.scanner = VulnerabilityScanner(proxy=proxy) 822 | self.running = True 823 | self.futures = [] # 用于跟踪线程池任务 824 | # 计算总任务量(目标数×服务数) 825 | self.total_tasks = len(self.targets) * len(self.services) 826 | self.completed_tasks = 0 827 | 828 | def run(self): 829 | results = [] 830 | # 初始化每个目标的结果容器 831 | target_results = {t["full_url"]: {"target": t["full_url"], "vulnerabilities": []} for t in self.targets} 832 | 833 | with ThreadPoolExecutor(max_workers=self.threads) as executor: 834 | # 提交所有任务并记录futures 835 | for target in self.targets: 836 | target_url = target["full_url"] 837 | for service in self.services: 838 | if not self.running: 839 | break # 若已停止,不再提交新任务 840 | # 提交单个服务扫描任务 841 | future = executor.submit( 842 | self.scanner.scan_single_service, 843 | target_info=target, 844 | service_name=service, 845 | custom_ports=self.custom_ports 846 | ) 847 | # 绑定目标和服务信息(用于结果解析) 848 | future.target = target # 保存原始target信息 849 | future.target_url = target_url 850 | future.service = service 851 | self.futures.append(future) 852 | 853 | # 处理已完成的任务 854 | for future in as_completed(self.futures): 855 | if not self.running: 856 | break # 若已停止,不再处理结果 857 | 858 | target_url = future.target_url 859 | service = future.service 860 | try: 861 | status, message = future.result() 862 | # 获取服务实际使用的端口(自定义或默认) 863 | actual_port = self.custom_ports.get(service) or self.scanner.default_ports.get(service) 864 | # 解析原始目标的host和scheme 865 | parsed = urlparse(target_url) 866 | # 重构显示用的URL(使用服务端口) 867 | display_url = self.scanner.get_display_url( 868 | target_info=future.target, 869 | service_name=service, 870 | custom_ports=self.custom_ports 871 | ) 872 | # 发射信号时使用重构的URL 873 | self.service_result.emit(display_url, service, status, message) 874 | # 记录结果 875 | target_results[target_url]["vulnerabilities"].append({ 876 | "service": service, 877 | "status": status, 878 | "message": message 879 | }) 880 | except Exception as e: 881 | # 异常时也使用重构的URL显示 882 | actual_port = self.custom_ports.get(service) or self.scanner.default_ports.get(service) 883 | parsed = urlparse(target_url) 884 | display_url = f"{parsed.scheme}://{parsed.hostname}:{actual_port}" 885 | self.service_result.emit(display_url, service, False, f"扫描异常: {str(e)}") 886 | 887 | # 更新进度 888 | self.completed_tasks += 1 889 | progress = int((self.completed_tasks / self.total_tasks) * 100) 890 | self.progress_updated.emit(progress) 891 | 892 | # 收集非空结果 893 | final_results = [v for v in target_results.values() if v["vulnerabilities"]] 894 | self.scan_completed.emit(final_results) 895 | 896 | def stop(self): 897 | """立即停止所有扫描任务(强制终止未完成任务)""" 898 | self.running = False 899 | # 取消所有未完成的任务 900 | for future in self.futures: 901 | if not future.done(): 902 | future.cancel() 903 | # 强制关闭线程池(不等待剩余任务) 904 | self.requestInterruption() 905 | 906 | 907 | class UnauthorizedScanTab(QWidget) : 908 | def __init__(self) : 909 | super().__init__() 910 | self.init_ui() 911 | self.scan_thread = None 912 | 913 | def init_ui(self) : 914 | self.setWindowTitle("未授权访问扫描工具") 915 | self.resize(1200, 700) 916 | 917 | # 主布局:左侧服务设置 + 右侧扫描区域 918 | main_splitter = QSplitter(Qt.Orientation.Horizontal) 919 | 920 | # 左侧:服务与端口设置 921 | left_widget = QWidget() 922 | left_layout = QVBoxLayout(left_widget) 923 | left_layout.setContentsMargins(5, 5, 5, 5) 924 | left_layout.setSpacing(5) 925 | 926 | # 服务表格 927 | service_group = QGroupBox("服务与端口设置(可编辑)") 928 | service_layout = QVBoxLayout(service_group) 929 | scroll_area = QScrollArea() 930 | scroll_area.setWidgetResizable(True) 931 | table_container = QWidget() 932 | table_layout = QVBoxLayout(table_container) 933 | 934 | self.service_table = QTableWidget() 935 | self.service_table.setColumnCount(2) 936 | self.service_table.setHorizontalHeaderLabels(["服务", "端口"]) 937 | self.service_table.horizontalHeader().setStretchLastSection(True) 938 | self.service_table.setMinimumWidth(300) 939 | 940 | # 填充服务数据 941 | services = sorted(VulnerabilityScanner().default_ports.items(), key=lambda x : x[0]) 942 | self.service_table.setRowCount(len(services)) 943 | for row, (name, port) in enumerate(services) : 944 | item_name = QTableWidgetItem(name) 945 | item_name.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsUserCheckable) 946 | item_name.setCheckState(Qt.CheckState.Checked) 947 | self.service_table.setItem(row, 0, item_name) 948 | 949 | item_port = QTableWidgetItem(str(port)) 950 | item_port.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsEditable) 951 | self.service_table.setItem(row, 1, item_port) 952 | 953 | table_layout.addWidget(self.service_table) 954 | scroll_area.setWidget(table_container) 955 | service_layout.addWidget(scroll_area) 956 | 957 | # 服务表格控制按钮 958 | btn_layout = QHBoxLayout() 959 | self.select_all_btn = QPushButton("全选") 960 | self.deselect_all_btn = QPushButton("取消全选") 961 | self.select_all_btn.clicked.connect(lambda : self.set_all_check_state(Qt.CheckState.Checked)) 962 | self.deselect_all_btn.clicked.connect(lambda : self.set_all_check_state(Qt.CheckState.Unchecked)) 963 | btn_layout.addWidget(self.select_all_btn) 964 | btn_layout.addWidget(self.deselect_all_btn) 965 | service_layout.addLayout(btn_layout) 966 | left_layout.addWidget(service_group, 1) 967 | main_splitter.addWidget(left_widget) 968 | 969 | # 右侧:扫描控制与结果展示 970 | right_widget = QWidget() 971 | right_layout = QVBoxLayout(right_widget) 972 | 973 | # 目标输入区域 974 | target_group = QGroupBox("扫描目标") 975 | target_layout = QVBoxLayout() 976 | target_input_layout = QHBoxLayout() 977 | self.target_edit = QLineEdit() 978 | self.target_edit.setPlaceholderText("支持格式:IP、IP:PORT、URL、域名(多个用逗号分隔)") 979 | self.load_file_btn = QPushButton("从文件导入") 980 | self.load_file_btn.clicked.connect(self.load_targets_from_file) 981 | target_input_layout.addWidget(self.target_edit) 982 | target_input_layout.addWidget(self.load_file_btn) 983 | target_layout.addLayout(target_input_layout) 984 | target_group.setLayout(target_layout) 985 | right_layout.addWidget(target_group) 986 | 987 | # 扫描设置 988 | settings_group = QGroupBox("扫描设置") 989 | settings_layout = QHBoxLayout() 990 | self.thread_spin = QSpinBox() 991 | self.thread_spin.setRange(1, 100) # 提高线程上限 992 | self.thread_spin.setValue(20) 993 | settings_layout.addWidget(QLabel("线程数:")) 994 | settings_layout.addWidget(self.thread_spin) 995 | 996 | self.timeout_spin = QSpinBox() 997 | self.timeout_spin.setRange(1, 10) 998 | self.timeout_spin.setValue(5) 999 | self.timeout_spin.setSuffix("秒") 1000 | settings_layout.addWidget(QLabel("超时时间:")) 1001 | settings_layout.addWidget(self.timeout_spin) 1002 | 1003 | settings_layout.addSpacing(20) 1004 | self.proxy_edit = QLineEdit() 1005 | self.proxy_edit.setPlaceholderText("代理(如http(socks5)://127.0.0.1:1080)") 1006 | settings_layout.addWidget(QLabel("代理:")) 1007 | settings_layout.addWidget(self.proxy_edit) 1008 | settings_group.setLayout(settings_layout) 1009 | right_layout.addWidget(settings_group) 1010 | 1011 | # 控制按钮 1012 | control_layout = QHBoxLayout() 1013 | self.start_btn = QPushButton("开始扫描") 1014 | self.stop_btn = QPushButton("停止扫描") 1015 | self.export_btn = QPushButton("导出结果") 1016 | self.clear_btn = QPushButton("清空结果") 1017 | self.start_btn.clicked.connect(self.start_scan) 1018 | self.stop_btn.clicked.connect(self.stop_scan) 1019 | self.export_btn.clicked.connect(self.export_results) 1020 | self.clear_btn.clicked.connect(self.clear_results) 1021 | self.stop_btn.setEnabled(False) 1022 | control_layout.addWidget(self.start_btn) 1023 | control_layout.addWidget(self.stop_btn) 1024 | control_layout.addWidget(self.export_btn) 1025 | control_layout.addWidget(self.clear_btn) 1026 | right_layout.addLayout(control_layout) 1027 | 1028 | # 进度条 1029 | self.progress_bar = QProgressBar() 1030 | self.progress_bar.setTextVisible(True) 1031 | self.progress_bar.setFormat("扫描进度: %p%") 1032 | right_layout.addWidget(self.progress_bar) 1033 | 1034 | # 结果显示 1035 | result_group = QGroupBox("扫描日志与结果") 1036 | result_layout = QVBoxLayout() 1037 | self.result_display = QTextEdit() 1038 | self.result_display.setReadOnly(True) 1039 | result_layout.addWidget(self.result_display) 1040 | result_group.setLayout(result_layout) 1041 | right_layout.addWidget(result_group, 1) 1042 | 1043 | main_splitter.addWidget(right_widget) 1044 | main_splitter.setSizes([350, 850]) 1045 | 1046 | # 主窗口布局 1047 | main_layout = QVBoxLayout(self) 1048 | main_layout.addWidget(main_splitter) 1049 | 1050 | # 保存结果数据 1051 | self.scan_results = [] 1052 | 1053 | # ---------------------- UI功能方法 ---------------------- 1054 | def set_all_check_state(self, state) : 1055 | for row in range(self.service_table.rowCount()) : 1056 | item = self.service_table.item(row, 0) 1057 | item.setCheckState(state) 1058 | 1059 | def get_selected_services(self) : 1060 | services = [] 1061 | custom_ports = {} 1062 | for row in range(self.service_table.rowCount()) : 1063 | name_item = self.service_table.item(row, 0) 1064 | if name_item.checkState() == Qt.CheckState.Checked : 1065 | service_name = name_item.text() 1066 | services.append(service_name) 1067 | # 读取用户自定义端口 1068 | try : 1069 | port = int(self.service_table.item(row, 1).text()) 1070 | custom_ports[service_name] = port 1071 | except ValueError : 1072 | pass # 无效端口则使用默认 1073 | return services, custom_ports 1074 | 1075 | def load_targets_from_file(self) : 1076 | file_path, _ = QFileDialog.getOpenFileName( 1077 | self, "选择目标文件", "", "文本文件 (*.txt);;所有文件 (*)" 1078 | ) 1079 | if file_path : 1080 | try : 1081 | with open(file_path, 'r') as f : 1082 | targets = [line.strip() for line in f if line.strip()] 1083 | self.target_edit.setText(",".join(targets)) 1084 | except Exception as e : 1085 | QMessageBox.warning(self, "错误", f"加载文件失败: {str(e)}") 1086 | 1087 | def start_scan(self) : 1088 | # 1. 解析目标 1089 | target_text = self.target_edit.text().strip() 1090 | if not target_text : 1091 | QMessageBox.warning(self, "警告", "请输入目标") 1092 | return 1093 | targets_raw = [t.strip() for t in target_text.split(',') if t.strip()] 1094 | # 标准化所有目标(过滤无效目标) 1095 | targets = [] 1096 | for t in targets_raw : 1097 | normalized = normalize_target_input(t) 1098 | if normalized : 1099 | targets.append(normalized) 1100 | else : 1101 | self.result_display.append(f"[{datetime.now().strftime('%H:%M:%S')}] 无效目标: {t}") 1102 | if not targets : 1103 | QMessageBox.warning(self, "警告", "无有效目标(请检查输入格式)") 1104 | return 1105 | 1106 | # 2. 获取选中的服务 1107 | services, custom_ports = self.get_selected_services() 1108 | if not services : 1109 | QMessageBox.warning(self, "警告", "请至少选择一个服务") 1110 | return 1111 | 1112 | # 3. 初始化扫描 1113 | self.start_btn.setEnabled(False) 1114 | self.stop_btn.setEnabled(True) 1115 | self.result_display.clear() 1116 | self.scan_results = [] 1117 | self.progress_bar.setValue(0) 1118 | self.result_display.append( 1119 | f"[{datetime.now().strftime('%H:%M:%S')}] 开始扫描,目标数: {len(targets)},服务数: {len(services)}") 1120 | 1121 | # 4. 启动扫描线程 1122 | proxy = self.proxy_edit.text().strip() or None 1123 | self.scan_thread = ScanThread( 1124 | targets=targets, 1125 | services=services, 1126 | custom_ports=custom_ports, 1127 | proxy=proxy, 1128 | threads=self.thread_spin.value() 1129 | ) 1130 | # 绑定信号与槽函数 1131 | self.scan_thread.scanning_service.connect(self.log_scanning) 1132 | self.scan_thread.service_result.connect(self.log_service_result) 1133 | self.scan_thread.progress_updated.connect(self.progress_bar.setValue) 1134 | self.scan_thread.scan_completed.connect(self.on_scan_completed) 1135 | self.scan_thread.start() 1136 | 1137 | def stop_scan(self) : 1138 | if self.scan_thread and self.scan_thread.isRunning() : 1139 | self.scan_thread.stop() 1140 | self.stop_btn.setEnabled(False) 1141 | self.result_display.append(f"[{datetime.now().strftime('%H:%M:%S')}] 已强制停止所有扫描任务") 1142 | 1143 | def log_scanning(self, target, service, path) : 1144 | """记录正在扫描的服务日志""" 1145 | self.result_display.append( 1146 | f"[{datetime.now().strftime('%H:%M:%S')}] 正在扫描: {target} - {service}(路径: {path})") 1147 | self.result_display.moveCursor(QTextCursor.MoveOperation.End) 1148 | 1149 | def log_service_result(self, target, service, status, message) : 1150 | """记录服务扫描结果""" 1151 | prefix = "✅ 发现漏洞" if status else "❌ 未发现漏洞" 1152 | self.result_display.append( 1153 | f"[{datetime.now().strftime('%H:%M:%S')}] {prefix}: {target} - {service} - {message}") 1154 | self.result_display.moveCursor(QTextCursor.MoveOperation.End) 1155 | 1156 | def on_scan_completed(self, results) : 1157 | """扫描完成后汇总结果,使用服务对应端口显示目标URL""" 1158 | self.scan_results = results 1159 | self.start_btn.setEnabled(True) 1160 | self.stop_btn.setEnabled(False) 1161 | self.progress_bar.setValue(100) 1162 | 1163 | # 获取选中服务的自定义端口和scanner实例 1164 | _, custom_ports = self.get_selected_services() 1165 | scanner = VulnerabilityScanner() 1166 | 1167 | # 汇总漏洞数量 1168 | total_vulns = 0 1169 | vuln_details = [] 1170 | for result in results : 1171 | # 解析原始目标信息 1172 | target_info = normalize_target_input(result["target"]) 1173 | if not target_info : 1174 | continue 1175 | 1176 | vulns = [v for v in result["vulnerabilities"] if v["status"]] 1177 | total_vulns += len(vulns) 1178 | if vulns : 1179 | # 按服务分组显示(同一目标不同服务可能对应不同端口) 1180 | service_groups = {} 1181 | for v in vulns : 1182 | # 使用现有方法获取服务实际URL 1183 | service_url = scanner.get_display_url(target_info, v["service"], custom_ports) 1184 | if service_url not in service_groups : 1185 | service_groups[service_url] = [] 1186 | service_groups[service_url].append(v) 1187 | 1188 | # 按服务URL分组添加到汇总信息 1189 | for service_url, group_vulns in service_groups.items() : 1190 | vuln_details.append(f"目标: {service_url}") 1191 | for v in group_vulns : 1192 | vuln_details.append(f" - 服务: {v['service']},信息: {v['message']}") 1193 | 1194 | # 显示汇总信息 1195 | self.result_display.append("\n" + "=" * 50) 1196 | self.result_display.append(f"[{datetime.now().strftime('%H:%M:%S')}] 扫描完成!总漏洞数: {total_vulns}") 1197 | if vuln_details : 1198 | self.result_display.append("存在未授权访问的服务列表:") 1199 | self.result_display.append("\n".join(vuln_details)) 1200 | else : 1201 | self.result_display.append("未发现任何未授权访问漏洞") 1202 | self.result_display.append("=" * 50) 1203 | self.result_display.moveCursor(QTextCursor.MoveOperation.End) 1204 | 1205 | def export_results(self) : 1206 | if not self.scan_results : 1207 | QMessageBox.warning(self, "警告", "没有可导出的结果") 1208 | return 1209 | 1210 | # 添加Excel选项到文件对话框 1211 | file_path, _ = QFileDialog.getSaveFileName( 1212 | self, "保存结果", f"unauth_scan_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx", 1213 | "Excel文件 (*.xlsx);;JSON文件 (*.json);;文本文件 (*.txt);;所有文件 (*)" 1214 | ) 1215 | 1216 | if file_path : 1217 | try : 1218 | # 获取选中服务的自定义端口配置 1219 | _, custom_ports = self.get_selected_services() 1220 | # 创建scanner实例用于调用get_display_url 1221 | scanner = VulnerabilityScanner() 1222 | 1223 | # 导出为Excel文件 1224 | if file_path.endswith('.xlsx') : 1225 | self._export_to_excel(file_path, scanner, custom_ports) 1226 | # 导出为JSON文件 1227 | elif file_path.endswith('.json') : 1228 | with open(file_path, 'w', encoding='utf-8') as f : 1229 | json.dump(self.scan_results, f, indent=2, ensure_ascii=False) 1230 | # 导出为文本文件 1231 | else : 1232 | with open(file_path, 'w', encoding='utf-8') as f : 1233 | f.write(f"未授权访问扫描报告 - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n") 1234 | total_vulns = sum( 1235 | len([v for v in r['vulnerabilities'] if v['status']]) for r in self.scan_results) 1236 | f.write(f"目标数量: {len(self.scan_results)}, 总漏洞数: {total_vulns}\n\n") 1237 | 1238 | for result in self.scan_results : 1239 | # 解析原始目标信息 1240 | target_info = normalize_target_input(result['target']) 1241 | f.write(f"目标: {result['target']}\n") 1242 | 1243 | for vuln in result["vulnerabilities"] : 1244 | # 使用现有方法获取服务实际URL 1245 | service_url = scanner.get_display_url(target_info, vuln['service'], custom_ports) 1246 | status = "存在漏洞" if vuln["status"] else "安全" 1247 | f.write(f" - {vuln['service']} ({service_url}): {status} - {vuln['message']}\n") 1248 | f.write("\n") 1249 | 1250 | QMessageBox.information(self, "成功", f"结果已导出到 {file_path}") 1251 | except Exception as e : 1252 | QMessageBox.warning(self, "错误", f"导出失败: {str(e)}") 1253 | 1254 | def _export_to_excel(self, file_path, scanner, custom_ports) : 1255 | """将扫描结果导出为Excel格式,使用现有方法获取正确URL""" 1256 | # 创建工作簿和工作表 1257 | workbook = openpyxl.Workbook() 1258 | worksheet = workbook.active 1259 | worksheet.title = "扫描结果" 1260 | 1261 | # 定义样式 1262 | header_font = Font(bold=True, color="FFFFFF") 1263 | header_fill = PatternFill(start_color="4F81BD", end_color="4F81BD", fill_type="solid") 1264 | border = Border( 1265 | left=Side(style='thin'), 1266 | right=Side(style='thin'), 1267 | top=Side(style='thin'), 1268 | bottom=Side(style='thin') 1269 | ) 1270 | highlight_fill = PatternFill(start_color="FFC7CE", end_color="FFC7CE", fill_type="solid") # 存在漏洞的行高亮 1271 | 1272 | # 设置表头 1273 | headers = ["序号", "目标URL", "服务名称", "是否存在漏洞", "漏洞信息", "扫描时间"] 1274 | for col, header in enumerate(headers, 1) : 1275 | cell = worksheet.cell(row=1, column=col) 1276 | cell.value = header 1277 | cell.font = header_font 1278 | cell.fill = header_fill 1279 | cell.border = border 1280 | cell.alignment = Alignment(horizontal="center", vertical="center") 1281 | 1282 | # 填充数据 1283 | row_num = 2 1284 | index = 1 1285 | scan_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") 1286 | 1287 | for result in self.scan_results : 1288 | # 解析原始目标信息用于生成正确URL 1289 | target_info = normalize_target_input(result["target"]) 1290 | for vuln in result["vulnerabilities"] : 1291 | # 使用现有方法获取服务实际扫描的URL(包含正确端口) 1292 | service_url = scanner.get_display_url(target_info, vuln['service'], custom_ports) 1293 | 1294 | # 序号 1295 | cell = worksheet.cell(row=row_num, column=1) 1296 | cell.value = index 1297 | cell.border = border 1298 | cell.alignment = Alignment(horizontal="center", vertical="center") 1299 | 1300 | # 目标URL - 使用服务实际端口的URL 1301 | cell = worksheet.cell(row=row_num, column=2) 1302 | cell.value = service_url 1303 | cell.border = border 1304 | cell.alignment = Alignment(vertical="center") 1305 | 1306 | # 服务名称 1307 | cell = worksheet.cell(row=row_num, column=3) 1308 | cell.value = vuln["service"] 1309 | cell.border = border 1310 | cell.alignment = Alignment(vertical="center") 1311 | 1312 | # 是否存在漏洞 1313 | cell = worksheet.cell(row=row_num, column=4) 1314 | status_text = "是" if vuln["status"] else "否" 1315 | cell.value = status_text 1316 | cell.border = border 1317 | cell.alignment = Alignment(horizontal="center", vertical="center") 1318 | 1319 | # 如果存在漏洞,整行高亮 1320 | if vuln["status"] : 1321 | for col in range(1, len(headers) + 1) : 1322 | worksheet.cell(row=row_num, column=col).fill = highlight_fill 1323 | 1324 | # 漏洞信息 1325 | cell = worksheet.cell(row=row_num, column=5) 1326 | cell.value = vuln["message"] 1327 | cell.border = border 1328 | cell.alignment = Alignment(wrap_text=True, vertical="center") 1329 | 1330 | # 扫描时间 1331 | cell = worksheet.cell(row=row_num, column=6) 1332 | cell.value = scan_time 1333 | cell.border = border 1334 | cell.alignment = Alignment(vertical="center") 1335 | 1336 | row_num += 1 1337 | index += 1 1338 | 1339 | # 调整列宽 1340 | column_widths = [8, 35, 15, 12, 50, 20] # 增加URL列宽以显示完整地址 1341 | for col in range(1, len(headers) + 1) : 1342 | worksheet.column_dimensions[get_column_letter(col)].width = column_widths[col - 1] 1343 | 1344 | # 添加自动筛选 1345 | worksheet.auto_filter.ref = f"A1:{get_column_letter(len(headers))}{row_num - 1}" 1346 | 1347 | # 保存文件 1348 | workbook.save(file_path) 1349 | 1350 | def clear_results(self) : 1351 | self.result_display.clear() 1352 | self.scan_results = [] 1353 | self.progress_bar.setValue(0) 1354 | 1355 | 1356 | if __name__ == "__main__": 1357 | signal.signal(signal.SIGINT, signal.SIG_DFL) 1358 | app = QApplication(sys.argv) 1359 | window = UnauthorizedScanTab() 1360 | window.show() 1361 | sys.exit(app.exec()) 1362 | --------------------------------------------------------------------------------