Skip to content

Commit e6af465

Browse files
committed
[UPDATE] pkg_resource Wappalyzer Error check
pkg_resource Wappalyzer Error check
1 parent 504e600 commit e6af465

4 files changed

Lines changed: 83 additions & 23 deletions

File tree

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Pygments==2.18.0
2626
pytest==8.3.4
2727
pytest-asyncio==0.25.0
2828
python-Wappalyzer==0.3.1
29+
webtech>=1.3.2
2930
PyYAML==6.0.2
3031
requests==2.32.5
3132
rich==13.9.4

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
setup(
44
name="subsurfer",
5-
version="1.2.6",
5+
version="1.2.7",
66
description="Red Teaming and Web Bug Bounty Fast Asset Identification Tool",
77
long_description=open('README.md').read(),
88
long_description_content_type="text/markdown",
@@ -25,6 +25,7 @@
2525
'pytest>=7.4.3',
2626
'pytest-asyncio>=0.23.2',
2727
'python-Wappalyzer>=0.3.1',
28+
'webtech>=1.3.2',
2829
'setuptools>=78.1.1'
2930
],
3031
entry_points={

subsurfer/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
SubSurfer - Red Teaming and Web Bug Bounty Fast Asset Identification Tool
33
"""
44

5-
__version__ = "1.2.6"
5+
__version__ = "1.2.7"

subsurfer/core/handler/web/web_scanner.py

Lines changed: 79 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,25 @@
66
import asyncio
77
import aiohttp
88
from typing import Dict, Set, List, Tuple, Optional
9-
from Wappalyzer import Wappalyzer, WebPage
109
import random
1110
from rich.console import Console
1211
import warnings
1312
import 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
1628
console = Console()
1729
class 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

Comments
 (0)