Skip to content

Commit d534300

Browse files
committed
Updates framework detectors for Tornado
1 parent 721ef81 commit d534300

File tree

2 files changed

+165
-1
lines changed

2 files changed

+165
-1
lines changed
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
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 TornadoDetector(BaseDetector):
20+
"""Tornado-specific detection methods"""
21+
22+
FRAMEWORK = "Tornado"
23+
24+
# Common Tornado paths to check
25+
COMMON_PATHS = [
26+
'/api/info',
27+
'/about',
28+
'/status',
29+
'/error',
30+
'/static/',
31+
]
32+
33+
# Tornado error patterns
34+
ERROR_PATTERNS = [
35+
(r'tornado/web\.py', 'Tornado error traceback', 30),
36+
(r'Exception: Test error for Tornado framework detection', 'Tornado test error', 25),
37+
(r'Traceback \(most recent call last\):', 'Python traceback in error', 10),
38+
]
39+
40+
# Tornado content patterns
41+
CONTENT_PATTERNS = [
42+
(r'Hello from Tornado!', 'Tornado greeting in content', 20),
43+
(r'<title>Tornado Test App</title>', 'Tornado test app title', 25),
44+
(r'<h1>Hello from Tornado!</h1>', 'Tornado heading in content', 20),
45+
(r'This is a minimal Tornado application', 'Tornado app description', 15),
46+
(r'<a href="/api/info">API Info</a>', 'Tornado API info link', 10),
47+
(r'<a href="/about">About</a>', 'Tornado about link', 10),
48+
(r'<a href="/status">Status</a>', 'Tornado status link', 10),
49+
]
50+
51+
# Tornado header patterns
52+
HEADER_PATTERNS = [
53+
('Server', r'TornadoServer/\d+\.\d+\.\d+', 'Tornado server header with version', 40),
54+
('Server', r'TornadoServer', 'Tornado server header', 30),
55+
('Content-Type', r'text/plain', 'Tornado plain text response', 5),
56+
]
57+
58+
def detect(self) -> None:
59+
"""Run Tornado detection methods"""
60+
self._check_headers()
61+
self._check_content_patterns()
62+
self._check_error_patterns()
63+
self._check_common_paths()
64+
self.detect_version()
65+
66+
def _add_score(self,
67+
points: int,
68+
evidence_type: str,
69+
detail: str,
70+
raw_data: Optional[Dict[str, Any]] = None) -> None:
71+
self.result_manager.add_score(self.FRAMEWORK, points, evidence_type, detail, raw_data)
72+
73+
def _add_version_hint(self,
74+
version: str,
75+
confidence: int,
76+
evidence: str) -> None:
77+
self.result_manager.add_version_hint(self.FRAMEWORK, version, confidence, evidence)
78+
79+
def _add_component(self,
80+
component: str,
81+
evidence: str) -> None:
82+
self.result_manager.add_component(self.FRAMEWORK, component, evidence)
83+
84+
def _check_headers(self) -> None:
85+
response = self.request_manager.make_request()
86+
if not response:
87+
return
88+
headers = response.headers
89+
for header_name, pattern, description, confidence in self.HEADER_PATTERNS:
90+
if header_name in headers:
91+
header_value = headers[header_name]
92+
if re.search(pattern, header_value, re.IGNORECASE):
93+
self._add_score(
94+
confidence,
95+
'Header',
96+
f"{description}: {header_name}: {header_value}"
97+
)
98+
if response.status_code == 405:
99+
self._add_score(
100+
10,
101+
'Header',
102+
"405 Method Not Allowed response (common in Tornado)"
103+
)
104+
105+
def _check_content_patterns(self) -> None:
106+
response = self.request_manager.make_request()
107+
if not response:
108+
return
109+
for pattern, description, confidence in self.CONTENT_PATTERNS:
110+
if re.search(pattern, response.text, re.IGNORECASE):
111+
self._add_score(
112+
confidence,
113+
'Content',
114+
f"{description}: {pattern}"
115+
)
116+
117+
def _check_error_patterns(self) -> None:
118+
base_url = self.request_manager.target_url.rstrip('/')
119+
error_url = urljoin(base_url, '/error')
120+
response = self.request_manager.make_request(error_url)
121+
if not response:
122+
return
123+
for pattern, description, confidence in self.ERROR_PATTERNS:
124+
if re.search(pattern, response.text, re.IGNORECASE):
125+
self._add_score(
126+
confidence,
127+
'Error',
128+
f"{description}: {pattern}"
129+
)
130+
131+
def _check_common_paths(self) -> None:
132+
base_url = self.request_manager.target_url.rstrip('/')
133+
for path in self.COMMON_PATHS:
134+
url = urljoin(base_url, path)
135+
response = self.request_manager.make_request(url)
136+
if response:
137+
if response.status_code == 200:
138+
self._add_score(
139+
10,
140+
'Endpoint',
141+
f"{path} returns 200 OK"
142+
)
143+
elif response.status_code == 405:
144+
self._add_score(
145+
8,
146+
'Endpoint',
147+
f"{path} returns 405 Method Not Allowed (Tornado behavior)"
148+
)
149+
150+
def detect_version(self) -> None:
151+
response = self.request_manager.make_request()
152+
if not response:
153+
return
154+
server_header = response.headers.get('Server', '')
155+
version_match = re.search(r'TornadoServer/(\d+\.\d+\.\d+)', server_header)
156+
if version_match:
157+
version = version_match.group(1)
158+
self._add_version_hint(
159+
version,
160+
80,
161+
f"Tornado version detected in Server header: {version}"
162+
)

siddhis/framewalk/orchestrator/fwalk_orchestrator.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ def _init_detectors(self) -> None:
244244
from ..detectors.bottle import BottleDetector
245245
from ..detectors.web2py import Web2pyDetector
246246
from ..detectors.sanic import SanicDetector
247+
from ..detectors.tornado import TornadoDetector
247248

248249
# Clear any existing detectors
249250
self.detectors = []
@@ -264,7 +265,8 @@ def _init_detectors(self) -> None:
264265
'pyramid': PyramidDetector,
265266
'bottle': BottleDetector,
266267
'web2py': Web2pyDetector,
267-
'sanic': SanicDetector
268+
'sanic': SanicDetector,
269+
'tornado': TornadoDetector
268270
}
269271

270272
for framework_name, detector_class in detector_classes.items():

0 commit comments

Comments
 (0)