├── README.md └── Unauthorized-Vul.py /README.md: -------------------------------------------------------------------------------- 1 | # -Python- 2 | 3 | 免责声明:安全工具仅用于授权的安全测试安全研究使用,违规使用产生的一切法律责任概不负责! 4 | 5 | 6 | 1.支持多线程,批量扫描 7 | 8 | 2.集成了常见的40多种未授权访问漏洞,包括不限于ollama,swagger,springboot,docker,k8s,ftp,数据库相关,组件相关未授权 9 | 10 | 11 | 3.使用方法 python Unauthorized-Vul.py -h 帮助界面 12 | python Unauthorized-Vul.py -u xxx.com 单个扫描 13 | python Unauthorized-Vul.py -f url.txt --out 批量,输出路径 14 | 15 | 1751599500229 16 | 17 | 18 | ![image](https://github.com/user-attachments/assets/90520ab9-fff2-49c9-9e8e-fa85ad5fd272) 19 | 20 | -------------------------------------------------------------------------------- /Unauthorized-Vul.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import concurrent.futures 3 | import json 4 | import os 5 | import socket 6 | import requests 7 | import urllib3 8 | from urllib.parse import urlparse 9 | from datetime import datetime 10 | 11 | # 禁用SSL警告 12 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 13 | 14 | # 漏洞检测函数 15 | class VulnerabilityScanner: 16 | def __init__(self, proxy=None, timeout=5): 17 | self.proxy = proxy 18 | self.timeout = timeout 19 | self.session = requests.Session() 20 | if proxy: 21 | self.session.proxies = {"http": proxy, "https": proxy} 22 | self.session.verify = False # 忽略SSL证书验证 23 | self.session.headers = { 24 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" 25 | } 26 | 27 | # 服务检测器映射表 28 | self.detectors = { 29 | "ftp": self.check_ftp, 30 | "redis": self.check_redis, 31 | "docker": self.check_docker, 32 | "docker_registry": self.check_docker_registry, 33 | "elasticsearch": self.check_elasticsearch, 34 | "jenkins": self.check_jenkins, 35 | "kibana": self.check_kibana, 36 | "zookeeper": self.check_zookeeper, 37 | "mongodb": self.check_mongodb, 38 | "kubernetes": self.check_kubernetes, 39 | "jupyter": self.check_jupyter, 40 | "nacos": self.check_nacos, 41 | "ollama": self.check_ollama, 42 | "rsync": self.check_rsync, 43 | "swagger": self.check_swagger, 44 | "springboot": self.check_springboot, 45 | "druid": self.check_druid, 46 | "ldap": self.check_ldap, 47 | "vnc": self.check_vnc, 48 | "couchdb": self.check_couchdb, 49 | "spark": self.check_spark, 50 | "weblogic": self.check_weblogic, 51 | "hadoop": self.check_hadoop, 52 | "jboss": self.check_jboss, 53 | "activemq": self.check_activemq, 54 | "zabbix": self.check_zabbix, 55 | "memcached": self.check_memcached, 56 | "rabbitmq": self.check_rabbitmq, 57 | "nfs": self.check_nfs, 58 | "dubbo": self.check_dubbo, 59 | "solr": self.check_solr, 60 | "harbor": self.check_harbor, 61 | "smb": self.check_smb, 62 | "wordpress": self.check_wordpress, 63 | "crowd": self.check_crowd, 64 | "uwsgi": self.check_uwsgi, 65 | "kong": self.check_kong, 66 | "thinkadmin": self.check_thinkadmin 67 | } 68 | 69 | # 服务默认端口映射 70 | self.default_ports = { 71 | "ftp": 21, 72 | "redis": 6379, 73 | "docker": 2375, 74 | "docker_registry": 5000, 75 | "elasticsearch": 9200, 76 | "jenkins": 8080, 77 | "kibana": 5601, 78 | "zookeeper": 2181, 79 | "mongodb": 27017, 80 | "kubernetes": 8080, 81 | "jupyter": 8888, 82 | "nacos": 8848, 83 | "ollama": 11434, 84 | "rsync": 873, 85 | "swagger": None, 86 | "springboot": None, 87 | "druid": None, 88 | "ldap": 389, 89 | "vnc": 5900, 90 | "couchdb": 5984, 91 | "spark": 6066, 92 | "weblogic": 7001, 93 | "hadoop": 8088, 94 | "jboss": 8080, 95 | "activemq": 8161, 96 | "zabbix": 10051, 97 | "memcached": 11211, 98 | "rabbitmq": 15672, 99 | "nfs": 2049, 100 | "dubbo": 28096, 101 | "solr": 8983, 102 | "harbor": 80, 103 | "smb": 445, 104 | "wordpress": 80, 105 | "crowd": 8095, 106 | "uwsgi": 1717, 107 | "kong": 8001, 108 | "thinkadmin": 80 109 | } 110 | 111 | def scan_target(self, target, services=None): 112 | """扫描单个目标的所有漏洞或指定服务""" 113 | # 解析目标 114 | parsed = urlparse(target) 115 | host = parsed.hostname if parsed.hostname else target 116 | port = parsed.port 117 | scheme = parsed.scheme or "http" 118 | 119 | target_info = { 120 | "host": host, 121 | "port": port, 122 | "scheme": scheme, 123 | "full_url": target 124 | } 125 | 126 | results = {"target": target, "vulnerabilities": []} 127 | 128 | # 确定要扫描的服务 129 | if services is None: 130 | services = list(self.detectors.keys()) 131 | 132 | # 扫描所有指定服务 133 | for service in services: 134 | status, message = self.detect_service(target_info, service) 135 | results["vulnerabilities"].append({ 136 | "service": service, 137 | "status": status, 138 | "message": message 139 | }) 140 | 141 | return results 142 | 143 | def detect_service(self, target_info, service_name): 144 | """检测指定服务是否存在未授权访问""" 145 | # 获取服务检测函数 146 | detector = self.detectors.get(service_name) 147 | if not detector: 148 | return False, f"Unsupported service: {service_name}" 149 | 150 | # 调用检测函数 151 | return detector(target_info) 152 | 153 | def check_ftp(self, target_info): 154 | """检测FTP未授权访问""" 155 | host = target_info["host"] 156 | port = target_info.get("port", 21) 157 | try: 158 | with socket.create_connection((host, port), timeout=self.timeout) as sock: 159 | sock.sendall(b"USER anonymous\r\n") 160 | response = sock.recv(1024).decode(errors="ignore") 161 | if "331" in response: 162 | sock.sendall(b"PASS anonymous@example.com\r\n") 163 | response = sock.recv(1024).decode(errors="ignore") 164 | if "230" in response: 165 | return True, "FTP anonymous login successful" 166 | except Exception as e: 167 | return False, f"FTP detection failed: {str(e)}" 168 | return False, "FTP unauthorized access not found" 169 | 170 | def check_redis(self, target_info): 171 | """检测Redis未授权访问""" 172 | host = target_info["host"] 173 | port = target_info.get("port", 6379) 174 | try: 175 | with socket.create_connection((host, port), timeout=self.timeout) as sock: 176 | sock.sendall(b"INFO\r\n") 177 | response = sock.recv(1024).decode(errors="ignore") 178 | if "redis_version" in response: 179 | return True, "Redis unauthorized access" 180 | except Exception as e: 181 | return False, f"Redis detection failed: {str(e)}" 182 | return False, "Redis unauthorized access not found" 183 | 184 | def check_docker(self, target_info): 185 | """检测Docker未授权访问""" 186 | host = target_info["host"] 187 | port = target_info.get("port", 2375) 188 | scheme = target_info.get("scheme", "http") 189 | url = f"{scheme}://{host}:{port}/version" 190 | try: 191 | response = self.session.get(url, timeout=self.timeout) 192 | if response.status_code == 200 and "ApiVersion" in response.text: 193 | return True, "Docker unauthorized access" 194 | except Exception as e: 195 | return False, f"Docker detection failed: {str(e)}" 196 | return False, "Docker unauthorized access not found" 197 | 198 | def check_docker_registry(self, target_info): 199 | """检测Docker Registry未授权访问""" 200 | host = target_info["host"] 201 | port = target_info.get("port", 5000) 202 | scheme = target_info.get("scheme", "http") 203 | url = f"{scheme}://{host}:{port}/v2/_catalog" 204 | try: 205 | response = self.session.get(url, timeout=self.timeout) 206 | if response.status_code == 200 and "repositories" in response.text: 207 | return True, "Docker Registry unauthorized access" 208 | except Exception as e: 209 | return False, f"Docker Registry detection failed: {str(e)}" 210 | return False, "Docker Registry unauthorized access not found" 211 | 212 | def check_elasticsearch(self, target_info): 213 | """检测Elasticsearch未授权访问""" 214 | host = target_info["host"] 215 | port = target_info.get("port", 9200) 216 | scheme = target_info.get("scheme", "http") 217 | url = f"{scheme}://{host}:{port}/_cat/indices" 218 | try: 219 | response = self.session.get(url, timeout=self.timeout) 220 | if response.status_code == 200 and "green" in response.text: 221 | return True, "Elasticsearch unauthorized access" 222 | except Exception as e: 223 | return False, f"Elasticsearch detection failed: {str(e)}" 224 | return False, "Elasticsearch unauthorized access not found" 225 | 226 | def check_jenkins(self, target_info): 227 | """检测Jenkins未授权访问""" 228 | host = target_info["host"] 229 | port = target_info.get("port", 8080) 230 | scheme = target_info.get("scheme", "http") 231 | url = f"{scheme}://{host}:{port}/api/json" 232 | try: 233 | response = self.session.get(url, timeout=self.timeout) 234 | if response.status_code == 200 and "jobs" in response.text: 235 | return True, "Jenkins unauthorized access" 236 | except Exception as e: 237 | return False, f"Jenkins detection failed: {str(e)}" 238 | return False, "Jenkins unauthorized access not found" 239 | 240 | def check_kibana(self, target_info): 241 | """检测Kibana未授权访问""" 242 | host = target_info["host"] 243 | port = target_info.get("port", 5601) 244 | scheme = target_info.get("scheme", "http") 245 | url = f"{scheme}://{host}:{port}/api/status" 246 | try: 247 | response = self.session.get(url, timeout=self.timeout) 248 | if response.status_code == 200 and "status" in response.text: 249 | return True, "Kibana unauthorized access" 250 | except Exception as e: 251 | return False, f"Kibana detection failed: {str(e)}" 252 | return False, "Kibana unauthorized access not found" 253 | 254 | def check_zookeeper(self, target_info): 255 | """检测Zookeeper未授权访问""" 256 | host = target_info["host"] 257 | port = target_info.get("port", 2181) 258 | try: 259 | with socket.create_connection((host, port), timeout=self.timeout) as sock: 260 | sock.sendall(b"stat\r\n") 261 | response = sock.recv(1024).decode(errors="ignore") 262 | if "Zookeeper version" in response: 263 | return True, "Zookeeper unauthorized access" 264 | except Exception as e: 265 | return False, f"Zookeeper detection failed: {str(e)}" 266 | return False, "Zookeeper unauthorized access not found" 267 | 268 | def check_mongodb(self, target_info): 269 | """检测MongoDB未授权访问""" 270 | host = target_info["host"] 271 | port = target_info.get("port", 27017) 272 | try: 273 | with socket.create_connection((host, port), timeout=self.timeout) as sock: 274 | sock.sendall(b"db.adminCommand('ping')\r\n") 275 | response = sock.recv(1024).decode(errors="ignore") 276 | if "ok" in response: 277 | return True, "MongoDB unauthorized access" 278 | except Exception as e: 279 | return False, f"MongoDB detection failed: {str(e)}" 280 | return False, "MongoDB unauthorized access not found" 281 | 282 | def check_kubernetes(self, target_info): 283 | """检测Kubernetes未授权访问""" 284 | host = target_info["host"] 285 | port = target_info.get("port", 8080) 286 | scheme = target_info.get("scheme", "http") 287 | url = f"{scheme}://{host}:{port}/api/v1/namespaces/default/pods" 288 | try: 289 | response = self.session.get(url, timeout=self.timeout) 290 | if response.status_code == 200 and "items" in response.text: 291 | return True, "Kubernetes unauthorized access" 292 | except Exception as e: 293 | return False, f"Kubernetes detection failed: {str(e)}" 294 | return False, "Kubernetes unauthorized access not found" 295 | 296 | def check_jupyter(self, target_info): 297 | """检测Jupyter Notebook未授权访问""" 298 | host = target_info["host"] 299 | port = target_info.get("port", 8888) 300 | scheme = target_info.get("scheme", "http") 301 | url = f"{scheme}://{host}:{port}/api/kernels" 302 | try: 303 | response = self.session.get(url, timeout=self.timeout) 304 | if response.status_code == 200 and "kernels" in response.text: 305 | return True, "Jupyter Notebook unauthorized access" 306 | except Exception as e: 307 | return False, f"Jupyter detection failed: {str(e)}" 308 | return False, "Jupyter unauthorized access not found" 309 | 310 | def check_nacos(self, target_info): 311 | """检测Nacos未授权访问""" 312 | host = target_info["host"] 313 | port = target_info.get("port", 8848) 314 | scheme = target_info.get("scheme", "http") 315 | url = f"{scheme}://{host}:{port}/nacos/v1/auth/users?pageNo=1&pageSize=10" 316 | try: 317 | response = self.session.get(url, timeout=self.timeout) 318 | if response.status_code == 200 and "username" in response.text: 319 | return True, "Nacos unauthorized access" 320 | except Exception as e: 321 | return False, f"Nacos detection failed: {str(e)}" 322 | return False, "Nacos unauthorized access not found" 323 | 324 | def check_ollama(self, target_info): 325 | """检测Ollama未授权访问""" 326 | host = target_info["host"] 327 | port = target_info.get("port", 11434) 328 | scheme = target_info.get("scheme", "http") 329 | url = f"{scheme}://{host}:{port}/api/tags" 330 | try: 331 | response = self.session.get(url, timeout=self.timeout) 332 | if response.status_code == 200 and "models" in response.text: 333 | return True, "Ollama unauthorized access" 334 | except Exception as e: 335 | return False, f"Ollama detection failed: {str(e)}" 336 | return False, "Ollama unauthorized access not found" 337 | 338 | def check_rsync(self, target_info): 339 | """检测Rsync未授权访问""" 340 | host = target_info["host"] 341 | port = target_info.get("port", 873) 342 | try: 343 | with socket.create_connection((host, port), timeout=self.timeout) as sock: 344 | sock.sendall(b"@RSYNCD: 31.0\n") 345 | response = sock.recv(1024).decode(errors="ignore") 346 | if "RSYNCD" in response: 347 | return True, "Rsync unauthorized access" 348 | except Exception as e: 349 | return False, f"Rsync detection failed: {str(e)}" 350 | return False, "Rsync unauthorized access not found" 351 | 352 | def check_swagger(self, target_info): 353 | """Swagger UI未授权访问检测""" 354 | host = target_info["host"] 355 | port = target_info.get("port", 80) 356 | scheme = target_info.get("scheme", "http") 357 | 358 | # 常见的Swagger UI路径 359 | paths = [ 360 | "/swagger-ui.html", 361 | "/swagger/index.html", 362 | "/swagger/ui/index", 363 | "/swagger", 364 | "/api-docs", 365 | "/v2/api-docs", 366 | "/swagger-resources", 367 | "/swagger-ui", 368 | "/api/swagger-ui.html", 369 | "/docs", 370 | "/swagger-ui/index.html" 371 | ] 372 | 373 | # 尝试所有可能的路径 374 | for path in paths: 375 | url = f"{scheme}://{host}:{port}{path}" if port else f"{scheme}://{host}{path}" 376 | try: 377 | response = self.session.get(url, timeout=self.timeout) 378 | if response.status_code == 200: 379 | # 使用更精确的识别方法 380 | if any(keyword in response.text for keyword in ["Swagger UI", "swagger-ui", "swagger.json", "swagger.yaml"]): 381 | return True, f"Swagger UI unauthorized access (path: {path})" 382 | except Exception: 383 | continue 384 | 385 | return False, "Swagger unauthorized access not found" 386 | 387 | def check_springboot(self, target_info): 388 | """检测SpringBoot Actuator未授权访问""" 389 | host = target_info["host"] 390 | port = target_info.get("port", 80) 391 | scheme = target_info.get("scheme", "http") 392 | 393 | # 常见的Actuator路径 394 | paths = [ 395 | "/actuator", 396 | "/actuator/health", 397 | "/actuator/env", 398 | "/actuator/metrics", 399 | "/actuator/beans", 400 | "/actuator/mappings" 401 | ] 402 | 403 | for path in paths: 404 | url = f"{scheme}://{host}:{port}{path}" if port else f"{scheme}://{host}{path}" 405 | try: 406 | response = self.session.get(url, timeout=self.timeout) 407 | if response.status_code == 200 and "actuator" in response.text: 408 | return True, f"SpringBoot Actuator unauthorized access (path: {path})" 409 | except Exception: 410 | continue 411 | 412 | return False, "SpringBoot unauthorized access not found" 413 | 414 | def check_druid(self, target_info): 415 | """检测Druid未授权访问""" 416 | host = target_info["host"] 417 | port = target_info.get("port", 80) 418 | scheme = target_info.get("scheme", "http") 419 | 420 | # 常见的Druid路径 421 | paths = [ 422 | "/druid/index.html", 423 | "/druid/login.html", 424 | "/druid/weburi.html", 425 | "/druid/websession.html", 426 | "/druid/sql.html" 427 | ] 428 | 429 | for path in paths: 430 | url = f"{scheme}://{host}:{port}{path}" if port else f"{scheme}://{host}{path}" 431 | try: 432 | response = self.session.get(url, timeout=self.timeout) 433 | if response.status_code == 200 and "Druid" in response.text: 434 | return True, f"Druid unauthorized access (path: {path})" 435 | except Exception: 436 | continue 437 | 438 | return False, "Druid unauthorized access not found" 439 | 440 | # 以下是新增的检测函数 441 | def check_ldap(self, target_info): 442 | """检测LDAP未授权访问""" 443 | host = target_info["host"] 444 | port = target_info.get("port", 389) 445 | try: 446 | with socket.create_connection((host, port), timeout=self.timeout) as sock: 447 | # LDAP匿名绑定尝试 448 | bind_request = bytes.fromhex("30 0c 02 01 01 60 07 02 01 03 04 00 80 00") 449 | sock.sendall(bind_request) 450 | response = sock.recv(1024) 451 | if response and len(response) > 0: 452 | return True, "LDAP unauthorized access (anonymous bind possible)" 453 | except Exception as e: 454 | return False, f"LDAP detection failed: {str(e)}" 455 | return False, "LDAP unauthorized access not found" 456 | 457 | def check_vnc(self, target_info): 458 | """检测VNC未授权访问""" 459 | host = target_info["host"] 460 | port = target_info.get("port", 5900) 461 | try: 462 | with socket.create_connection((host, port), timeout=self.timeout) as sock: 463 | # 读取VNC协议响应 464 | response = sock.recv(1024) 465 | if b"RFB" in response: 466 | return True, "VNC service exposed (possible unauthorized access)" 467 | except Exception as e: 468 | return False, f"VNC detection failed: {str(e)}" 469 | return False, "VNC unauthorized access not found" 470 | 471 | def check_couchdb(self, target_info): 472 | """检测CouchDB未授权访问""" 473 | host = target_info["host"] 474 | port = target_info.get("port", 5984) 475 | scheme = target_info.get("scheme", "http") 476 | url = f"{scheme}://{host}:{port}/_all_dbs" 477 | try: 478 | response = self.session.get(url, timeout=self.timeout) 479 | if response.status_code == 200 and "[" in response.text: 480 | return True, "CouchDB unauthorized access" 481 | except Exception as e: 482 | return False, f"CouchDB detection failed: {str(e)}" 483 | return False, "CouchDB unauthorized access not found" 484 | 485 | def check_spark(self, target_info): 486 | """检测Apache Spark未授权访问""" 487 | host = target_info["host"] 488 | port = target_info.get("port", 6066) 489 | scheme = target_info.get("scheme", "http") 490 | url = f"{scheme}://{host}:{port}/" 491 | try: 492 | response = self.session.get(url, timeout=self.timeout) 493 | if response.status_code == 200 and "Spark" in response.text: 494 | return True, "Apache Spark unauthorized access" 495 | except Exception as e: 496 | return False, f"Apache Spark detection failed: {str(e)}" 497 | return False, "Apache Spark unauthorized access not found" 498 | 499 | def check_weblogic(self, target_info): 500 | """检测Weblogic未授权访问""" 501 | host = target_info["host"] 502 | port = target_info.get("port", 7001) 503 | scheme = target_info.get("scheme", "http") 504 | url = f"{scheme}://{host}:{port}/console/login/LoginForm.jsp" 505 | try: 506 | response = self.session.get(url, timeout=self.timeout) 507 | if response.status_code == 200 and "WebLogic Server" in response.text: 508 | return True, "Weblogic console exposed (possible unauthorized access)" 509 | except Exception as e: 510 | return False, f"Weblogic detection failed: {str(e)}" 511 | return False, "Weblogic unauthorized access not found" 512 | 513 | def check_hadoop(self, target_info): 514 | """检测Hadoop YARN未授权访问""" 515 | host = target_info["host"] 516 | port = target_info.get("port", 8088) 517 | scheme = target_info.get("scheme", "http") 518 | url = f"{scheme}://{host}:{port}/ws/v1/cluster/apps" 519 | try: 520 | response = self.session.get(url, timeout=self.timeout) 521 | if response.status_code == 200 and "apps" in response.text: 522 | return True, "Hadoop YARN unauthorized access" 523 | except Exception as e: 524 | return False, f"Hadoop detection failed: {str(e)}" 525 | return False, "Hadoop unauthorized access not found" 526 | 527 | def check_jboss(self, target_info): 528 | """检测JBoss未授权访问""" 529 | host = target_info["host"] 530 | port = target_info.get("port", 8080) 531 | scheme = target_info.get("scheme", "http") 532 | url = f"{scheme}://{host}:{port}/jmx-console/" 533 | try: 534 | response = self.session.get(url, timeout=self.timeout) 535 | if response.status_code == 200 and "JBoss" in response.text: 536 | return True, "JBoss JMX console exposed (possible unauthorized access)" 537 | except Exception as e: 538 | return False, f"JBoss detection failed: {str(e)}" 539 | return False, "JBoss unauthorized access not found" 540 | 541 | def check_activemq(self, target_info): 542 | """检测ActiveMQ未授权访问""" 543 | host = target_info["host"] 544 | port = target_info.get("port", 8161) 545 | scheme = target_info.get("scheme", "http") 546 | url = f"{scheme}://{host}:{port}/admin/" 547 | try: 548 | response = self.session.get(url, timeout=self.timeout) 549 | if response.status_code == 200 and "ActiveMQ" in response.text: 550 | return True, "ActiveMQ management console exposed (possible unauthorized access)" 551 | except Exception as e: 552 | return False, f"ActiveMQ detection failed: {str(e)}" 553 | return False, "ActiveMQ unauthorized access not found" 554 | 555 | def check_zabbix(self, target_info): 556 | """检测Zabbix未授权访问""" 557 | host = target_info["host"] 558 | port = target_info.get("port", 10051) 559 | scheme = target_info.get("scheme", "http") 560 | url = f"{scheme}://{host}:{port}/" 561 | try: 562 | response = self.session.get(url, timeout=self.timeout) 563 | if response.status_code == 200 and "Zabbix" in response.text: 564 | return True, "Zabbix service exposed (possible unauthorized access)" 565 | except Exception as e: 566 | return False, f"Zabbix detection failed: {str(e)}" 567 | return False, "Zabbix unauthorized access not found" 568 | 569 | def check_memcached(self, target_info): 570 | """检测Memcached未授权访问""" 571 | host = target_info["host"] 572 | port = target_info.get("port", 11211) 573 | try: 574 | with socket.create_connection((host, port), timeout=self.timeout) as sock: 575 | sock.sendall(b"stats\r\n") 576 | response = sock.recv(1024).decode(errors="ignore") 577 | if "STAT" in response: 578 | return True, "Memcached unauthorized access" 579 | except Exception as e: 580 | return False, f"Memcached detection failed: {str(e)}" 581 | return False, "Memcached unauthorized access not found" 582 | 583 | def check_rabbitmq(self, target_info): 584 | """检测RabbitMQ未授权访问""" 585 | host = target_info["host"] 586 | port = target_info.get("port", 15672) 587 | scheme = target_info.get("scheme", "http") 588 | url = f"{scheme}://{host}:{port}/api/overview" 589 | try: 590 | response = self.session.get(url, timeout=self.timeout) 591 | if response.status_code == 200 and "management_version" in response.text: 592 | return True, "RabbitMQ management API unauthorized access" 593 | except Exception as e: 594 | return False, f"RabbitMQ detection failed: {str(e)}" 595 | return False, "RabbitMQ unauthorized access not found" 596 | 597 | def check_nfs(self, target_info): 598 | """检测NFS未授权访问""" 599 | host = target_info["host"] 600 | port = target_info.get("port", 2049) 601 | try: 602 | with socket.create_connection((host, port), timeout=self.timeout) as sock: 603 | sock.sendall(b"\x80\x00\x00\x00") 604 | response = sock.recv(1024) 605 | if response and len(response) > 0: 606 | return True, "NFS service exposed (possible unauthorized access)" 607 | except Exception as e: 608 | return False, f"NFS detection failed: {str(e)}" 609 | return False, "NFS unauthorized access not found" 610 | 611 | def check_dubbo(self, target_info): 612 | """检测Dubbo未授权访问""" 613 | host = target_info["host"] 614 | port = target_info.get("port", 28096) 615 | try: 616 | with socket.create_connection((host, port), timeout=self.timeout) as sock: 617 | sock.sendall(b"ls\r\n") 618 | response = sock.recv(1024).decode(errors="ignore") 619 | if "dubbo>" in response: 620 | return True, "Dubbo console unauthorized access" 621 | except Exception as e: 622 | return False, f"Dubbo detection failed: {str(e)}" 623 | return False, "Dubbo unauthorized access not found" 624 | 625 | def check_solr(self, target_info): 626 | """检测Solr未授权访问""" 627 | host = target_info["host"] 628 | port = target_info.get("port", 8983) 629 | scheme = target_info.get("scheme", "http") 630 | url = f"{scheme}://{host}:{port}/solr/admin/cores" 631 | try: 632 | response = self.session.get(url, timeout=self.timeout) 633 | if response.status_code == 200 and "responseHeader" in response.text: 634 | return True, "Solr unauthorized access" 635 | except Exception as e: 636 | return False, f"Solr detection failed: {str(e)}" 637 | return False, "Solr unauthorized access not found" 638 | 639 | def check_harbor(self, target_info): 640 | """检测Harbor未授权添加管理员漏洞""" 641 | host = target_info["host"] 642 | port = target_info.get("port", 80) 643 | scheme = target_info.get("scheme", "http") 644 | url = f"{scheme}://{host}:{port}/api/v2.0/users" 645 | try: 646 | response = self.session.get(url, timeout=self.timeout) 647 | if response.status_code == 200 and "username" in response.text: 648 | return True, "Harbor user API exposed (possible unauthorized admin addition)" 649 | except Exception as e: 650 | return False, f"Harbor detection failed: {str(e)}" 651 | return False, "Harbor unauthorized access not found" 652 | 653 | def check_smb(self, target_info): 654 | """检测Windows共享未授权访问""" 655 | host = target_info["host"] 656 | port = target_info.get("port", 445) 657 | try: 658 | with socket.create_connection((host, port), timeout=self.timeout) as sock: 659 | # 发送SMB协商请求 660 | sock.sendall(b"\x00\x00\x00\x85\xff\x53\x4d\x42\x72\x00\x00\x00\x00\x18\x53\xc8") 661 | response = sock.recv(1024) 662 | if response and len(response) > 0: 663 | return True, "SMB service exposed (possible unauthorized access)" 664 | except Exception as e: 665 | return False, f"SMB detection failed: {str(e)}" 666 | return False, "SMB unauthorized access not found" 667 | 668 | def check_wordpress(self, target_info): 669 | """检测WordPress未授权访问""" 670 | host = target_info["host"] 671 | port = target_info.get("port", 80) 672 | scheme = target_info.get("scheme", "http") 673 | url = f"{scheme}://{host}:{port}/wp-admin/" 674 | try: 675 | response = self.session.get(url, timeout=self.timeout) 676 | if response.status_code == 200 and "WordPress" in response.text: 677 | return True, "WordPress admin panel exposed (possible unauthorized access)" 678 | except Exception as e: 679 | return False, f"WordPress detection failed: {str(e)}" 680 | return False, "WordPress unauthorized access not found" 681 | 682 | def check_crowd(self, target_info): 683 | """检测Atlassian Crowd未授权访问""" 684 | host = target_info["host"] 685 | port = target_info.get("port", 8095) 686 | scheme = target_info.get("scheme", "http") 687 | url = f"{scheme}://{host}:{port}/crowd/admin/" 688 | try: 689 | response = self.session.get(url, timeout=self.timeout) 690 | if response.status_code == 200 and "Crowd" in response.text: 691 | return True, "Crowd management console exposed (possible unauthorized access)" 692 | except Exception as e: 693 | return False, f"Crowd detection failed: {str(e)}" 694 | return False, "Crowd unauthorized access not found" 695 | 696 | def check_uwsgi(self, target_info): 697 | """检测uWSGI未授权访问""" 698 | host = target_info["host"] 699 | port = target_info.get("port", 1717) 700 | try: 701 | with socket.create_connection((host, port), timeout=self.timeout) as sock: 702 | sock.sendall(b"add-mapping /foo /bar\n") 703 | response = sock.recv(1024).decode(errors="ignore") 704 | if "OK" in response: 705 | return True, "uWSGI unauthorized access" 706 | except Exception as e: 707 | return False, f"uWSGI detection failed: {str(e)}" 708 | return False, "uWSGI unauthorized access not found" 709 | 710 | def check_kong(self, target_info): 711 | """检测Kong未授权访问""" 712 | host = target_info["host"] 713 | port = target_info.get("port", 8001) 714 | scheme = target_info.get("scheme", "http") 715 | url = f"{scheme}://{host}:{port}/" 716 | try: 717 | response = self.session.get(url, timeout=self.timeout) 718 | if response.status_code == 200 and "kong" in response.text: 719 | return True, "Kong management API exposed (possible unauthorized access)" 720 | except Exception as e: 721 | return False, f"Kong detection failed: {str(e)}" 722 | return False, "Kong unauthorized access not found" 723 | 724 | def check_thinkadmin(self, target_info): 725 | """检测ThinkAdmin未授权访问""" 726 | host = target_info["host"] 727 | port = target_info.get("port", 80) 728 | scheme = target_info.get("scheme", "http") 729 | url = f"{scheme}://{host}:{port}/admin.html" 730 | try: 731 | response = self.session.get(url, timeout=self.timeout) 732 | if response.status_code == 200 and "ThinkAdmin" in response.text: 733 | return True, "ThinkAdmin admin panel exposed (possible unauthorized access)" 734 | except Exception as e: 735 | return False, f"ThinkAdmin detection failed: {str(e)}" 736 | return False, "ThinkAdmin unauthorized access not found" 737 | 738 | def parse_targets(target_input): 739 | """解析目标输入,支持多种格式""" 740 | targets = [] 741 | 742 | # 如果是文件路径 743 | if os.path.isfile(target_input): 744 | with open(target_input, 'r') as f: 745 | for line in f: 746 | line = line.strip() 747 | if line: 748 | targets.append(line) 749 | return targets 750 | 751 | # 如果是逗号分隔的列表 752 | if ',' in target_input: 753 | return [t.strip() for t in target_input.split(',') if t.strip()] 754 | 755 | # 单个目标 756 | return [target_input] 757 | 758 | def main(): 759 | parser = argparse.ArgumentParser(description="Unauthorized access vulnerability scanner") 760 | parser.add_argument("-u", "--url", help="Single target URL or multiple targets (comma separated)") 761 | parser.add_argument("-f", "--file", help="File containing multiple targets") 762 | parser.add_argument("-s", "--services", help="Specify services to scan (comma separated)") 763 | parser.add_argument("-t", "--threads", type=int, default=10, help="Number of threads (default: 10)") 764 | parser.add_argument("--proxy", help="Proxy settings (e.g., http://127.0.0.1:8080)") 765 | parser.add_argument("--out", help="Output file path") 766 | 767 | args = parser.parse_args() 768 | 769 | if not args.url and not args.file: 770 | parser.print_help() 771 | print("\nError: Must provide -u/--url or -f/--file parameter") 772 | return 773 | 774 | # 解析目标 775 | targets = [] 776 | if args.url: 777 | targets.extend(parse_targets(args.url)) 778 | if args.file: 779 | targets.extend(parse_targets(args.file)) 780 | 781 | # 去重 782 | targets = list(set(targets)) 783 | 784 | scanner = VulnerabilityScanner(proxy=args.proxy) 785 | 786 | # 解析要扫描的服务 787 | services = None 788 | if args.services: 789 | services = [s.strip().lower() for s in args.services.split(',')] 790 | valid_services = set(scanner.detectors.keys()) 791 | invalid_services = set(services) - valid_services 792 | 793 | if invalid_services: 794 | print(f"Warning: The following services are not supported and will be ignored: {', '.join(invalid_services)}") 795 | services = list(set(services) - invalid_services) 796 | 797 | results = [] 798 | 799 | print(f"[*] Starting scan for {len(targets)} targets using {args.threads} threads") 800 | if services: 801 | print(f"[*] Specified services: {', '.join(services)}") 802 | 803 | # 使用线程池执行扫描 804 | with concurrent.futures.ThreadPoolExecutor(max_workers=args.threads) as executor: 805 | future_to_target = {executor.submit(scanner.scan_target, target, services): target for target in targets} 806 | 807 | for future in concurrent.futures.as_completed(future_to_target): 808 | target = future_to_target[future] 809 | try: 810 | result = future.result() 811 | results.append(result) 812 | # 打印结果 813 | print(f"\n[+] Scan completed: {target}") 814 | for vuln in result["vulnerabilities"]: 815 | status = "Vulnerable" if vuln["status"] else "Secure" 816 | print(f" - {vuln['service']}: {status} ({vuln['message']})") 817 | except Exception as e: 818 | print(f"[-] Scan failed: {target}, error: {str(e)}") 819 | 820 | # 输出结果到文件 821 | if args.out: 822 | output_format = os.path.splitext(args.out)[1].lower() 823 | if output_format == ".json": 824 | with open(args.out, 'w') as f: 825 | json.dump(results, f, indent=2) 826 | print(f"\n[*] Results saved as JSON file: {args.out}") 827 | else: 828 | with open(args.out, 'w') as f: 829 | f.write("Unauthorized Access Vulnerability Scan Report\n") 830 | f.write(f"Scan time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n") 831 | f.write(f"Targets scanned: {len(targets)}\n\n") 832 | 833 | for result in results: 834 | f.write(f"Target: {result['target']}\n") 835 | for vuln in result["vulnerabilities"]: 836 | status = "Vulnerable" if vuln["status"] else "Secure" 837 | f.write(f" - {vuln['service']}: {status} ({vuln['message']})\n") 838 | f.write("\n") 839 | print(f"\n[*] Results saved as text file: {args.out}") 840 | 841 | print("\n[+] Scan completed") 842 | 843 | if __name__ == "__main__": 844 | main() --------------------------------------------------------------------------------