66import asyncio
77import aiohttp
88from typing import Dict , Set , List , Tuple , Optional
9- from Wappalyzer import Wappalyzer , WebPage
109import random
1110from rich .console import Console
1211import warnings
1312import socket
14- # Wappalyzer 경고 무시
15- warnings .filterwarnings ('ignore' , module = 'Wappalyzer' )
13+
14+ # Wappalyzer 로드 시도
15+ try :
16+ warnings .filterwarnings ('ignore' , module = 'Wappalyzer' )
17+ from Wappalyzer import Wappalyzer , WebPage
18+ HAS_WAPPALYZER = True
19+ except Exception :
20+ HAS_WAPPALYZER = False
21+
22+ # Wappalyzer 사용 불가 시 webtech fallback
23+ try :
24+ import webtech
25+ HAS_WEBTECH = True
26+ except Exception :
27+ HAS_WEBTECH = False
1628console = Console ()
1729class WebScanner :
1830 """웹 서비스 스캐너"""
@@ -30,7 +42,12 @@ def __init__(self, domain: str, ports: List[int] = None, verbose: int = 0, silen
3042 self .default_ports = [80 , 443 ] # 기본 포트는 별도 저장
3143 self .verbose = verbose # verbose 저장
3244 self .silent = silent # silent 모드 저장
33- self .wappalyzer = Wappalyzer .latest ()
45+ self .wappalyzer = None
46+ if HAS_WAPPALYZER :
47+ try :
48+ self .wappalyzer = Wappalyzer .latest ()
49+ except Exception :
50+ pass
3451 self .user_agents = [
3552 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' ,
3653 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36' ,
@@ -60,28 +77,69 @@ async def check_web_server(self, subdomain: str, port: int = None) -> Tuple[bool
6077 """웹 서버 여부 확인"""
6178 headers = {'User-Agent' : self ._get_random_user_agent ()}
6279 protocols = ['https' , 'http' ]
63-
80+
6481 for protocol in protocols :
6582 url = f"{ protocol } ://{ subdomain } "
6683 if port and port not in [80 , 443 ]:
6784 url = f"{ url } :{ port } "
68-
69- try :
70- webpage = await WebPage .new_from_url_async (
71- url ,
72- verify = False ,
73- timeout = 2 ,
74- aiohttp_client_session = self .session
75- )
76- analysis = self .wappalyzer .analyze_with_versions_and_categories (webpage )
77- if port : # 포트 스캔 결과 저장
78- self .all_urls [subdomain ] = self .all_urls .get (subdomain , [])
79- self .all_urls [subdomain ].append ((url , port ))
80- return True , url , analysis
81- except :
82- continue
83-
85+
86+ # Wappalyzer 우선 시도
87+ if self .wappalyzer :
88+ try :
89+ webpage = await WebPage .new_from_url_async (
90+ url ,
91+ verify = False ,
92+ timeout = 2 ,
93+ aiohttp_client_session = self .session
94+ )
95+ analysis = self .wappalyzer .analyze_with_versions_and_categories (webpage )
96+ if port :
97+ self .all_urls [subdomain ] = self .all_urls .get (subdomain , [])
98+ self .all_urls [subdomain ].append ((url , port ))
99+ return True , url , analysis
100+ except :
101+ pass
102+
103+ # Wappalyzer 사용 불가 또는 실패 시 webtech fallback
104+ if HAS_WEBTECH :
105+ try :
106+ wt = webtech .WebTech (options = {'json' : True })
107+ report = wt .start_from_url (url )
108+ analysis = self ._parse_webtech_report (report )
109+ if port :
110+ self .all_urls [subdomain ] = self .all_urls .get (subdomain , [])
111+ self .all_urls [subdomain ].append ((url , port ))
112+ return True , url , analysis
113+ except :
114+ pass
115+
116+ # Wappalyzer, webtech 모두 사용 불가 시 HTTP 연결 여부만 확인
117+ if not self .wappalyzer and not HAS_WEBTECH :
118+ try :
119+ async with self .session .get (url , headers = headers , timeout = aiohttp .ClientTimeout (total = 2 ), ssl = False ) as resp :
120+ if resp .status :
121+ if port :
122+ self .all_urls [subdomain ] = self .all_urls .get (subdomain , [])
123+ self .all_urls [subdomain ].append ((url , port ))
124+ return True , url , {}
125+ except :
126+ pass
127+
84128 return False , "" , {}
129+
130+ def _parse_webtech_report (self , report : dict ) -> Dict :
131+ """webtech 결과를 Wappalyzer 형식으로 변환"""
132+ analysis = {}
133+ if isinstance (report , dict ) and 'tech' in report :
134+ for tech in report ['tech' ]:
135+ name = tech .get ('name' , '' )
136+ version = tech .get ('version' , None )
137+ if name :
138+ analysis [name ] = {
139+ 'versions' : [version ] if version else [],
140+ 'categories' : []
141+ }
142+ return analysis
85143
86144 def _is_host_active (self , subdomain : str ) -> bool :
87145 """호스트 활성화 여부 확인"""
0 commit comments