Skip to content

Commit 859c218

Browse files
committed
Adds base Starlette detector
1 parent 606d19a commit 859c218

1 file changed

Lines changed: 114 additions & 0 deletions

File tree

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
#!/usr/bin/env python3
2+
# __ _
3+
# \/imana 2016
4+
# [|-ramewørk
5+
#
6+
# Author: s4dhu
7+
# Email: <s4dhul4bs[at]prontonmail[dot]ch
8+
# Git: @s4dhulabs
9+
# Mastodon: @s4dhu
10+
#
11+
# This file is part of Vimana Framework Project.
12+
13+
import re
14+
from typing import Dict, List, Any, Optional
15+
from urllib.parse import urljoin
16+
17+
from .base import BaseDetector
18+
19+
class StarletteDetector(BaseDetector):
20+
"""Starlette-specific detection methods"""
21+
22+
FRAMEWORK = "Starlette"
23+
24+
COMMON_PATHS = [
25+
'/',
26+
'/about',
27+
'/api/status',
28+
'/error',
29+
]
30+
31+
CONTENT_PATTERNS = [
32+
(r'Starlette', 'Starlette reference in content', 20),
33+
(r'<title>Starlette Test App</title>', 'Starlette test app title', 25),
34+
(r'<h1>Hello from Starlette!</h1>', 'Starlette heading in content', 20),
35+
(r'Starlette Framework Detection Test', 'Starlette detection test string', 20),
36+
(r'Starlette is a lightweight ASGI framework', 'Starlette description', 15),
37+
]
38+
39+
HEADER_PATTERNS = [
40+
('server', r'uvicorn', 'Uvicorn server header (common for Starlette)', 10),
41+
('content-type', r'application/json', 'JSON API response', 5),
42+
('content-type', r'text/html', 'HTML response', 3),
43+
]
44+
45+
API_PATTERNS = [
46+
(r'"framework": ?"starlette"', 'Starlette framework in API response', 30),
47+
(r'"status": ?"running"', 'Status running in API response', 10),
48+
(r'"version": ?"[0-9.]+"', 'Version in API response', 10),
49+
]
50+
51+
def detect(self) -> None:
52+
self._check_headers()
53+
self._check_content_patterns()
54+
self._check_api_status()
55+
self._check_common_paths()
56+
self.detect_version()
57+
58+
def _add_score(self, points: int, evidence_type: str, detail: str, raw_data: Optional[Dict[str, Any]] = None) -> None:
59+
self.result_manager.add_score(self.FRAMEWORK, points, evidence_type, detail, raw_data)
60+
61+
def _add_version_hint(self, version: str, confidence: int, evidence: str) -> None:
62+
self.result_manager.add_version_hint(self.FRAMEWORK, version, confidence, evidence)
63+
64+
def _add_component(self, component: str, evidence: str) -> None:
65+
self.result_manager.add_component(self.FRAMEWORK, component, evidence)
66+
67+
def _check_headers(self) -> None:
68+
response = self.request_manager.make_request()
69+
if not response:
70+
return
71+
headers = response.headers
72+
for header_name, pattern, description, confidence in self.HEADER_PATTERNS:
73+
if header_name in headers:
74+
header_value = headers[header_name]
75+
if re.search(pattern, header_value, re.IGNORECASE):
76+
self._add_score(confidence, 'Header', f"{description}: {header_name}: {header_value}")
77+
78+
def _check_content_patterns(self) -> None:
79+
response = self.request_manager.make_request()
80+
if not response:
81+
return
82+
for pattern, description, confidence in self.CONTENT_PATTERNS:
83+
if re.search(pattern, response.text, re.IGNORECASE):
84+
self._add_score(confidence, 'Content', f"{description}: {pattern}")
85+
86+
def _check_api_status(self) -> None:
87+
base_url = self.request_manager.target_url.rstrip('/')
88+
api_url = urljoin(base_url, '/api/status')
89+
response = self.request_manager.make_request(api_url)
90+
if not response:
91+
return
92+
for pattern, description, confidence in self.API_PATTERNS:
93+
if re.search(pattern, response.text, re.IGNORECASE):
94+
self._add_score(confidence, 'API', f"{description}: {pattern}")
95+
if response.headers.get('content-type', '').startswith('application/json'):
96+
self._add_score(10, 'API', 'API status endpoint returns JSON')
97+
98+
def _check_common_paths(self) -> None:
99+
base_url = self.request_manager.target_url.rstrip('/')
100+
for path in self.COMMON_PATHS:
101+
url = urljoin(base_url, path)
102+
response = self.request_manager.make_request(url)
103+
if response and response.status_code == 200:
104+
self._add_score(5, 'Endpoint', f"{path} returns 200 OK")
105+
106+
def detect_version(self) -> None:
107+
base_url = self.request_manager.target_url.rstrip('/')
108+
api_url = urljoin(base_url, '/api/status')
109+
response = self.request_manager.make_request(api_url)
110+
if response:
111+
version_match = re.search(r'"version": ?"([0-9.]+)"', response.text)
112+
if version_match:
113+
version = version_match.group(1)
114+
self._add_version_hint(version, 80, f"Starlette version detected in API response: {version}")

0 commit comments

Comments
 (0)