-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscanner.py
More file actions
372 lines (325 loc) · 17.1 KB
/
Copy pathscanner.py
File metadata and controls
372 lines (325 loc) · 17.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
import requests
import time
from urllib.parse import urlparse
from typing import Dict, List, Any
def scan_headers(url: str) -> Dict[str, Any]:
"""
Улучшенное сканирование заголовков безопасности с детальным анализом
"""
start_time = time.time()
try:
results = {
'target': url,
'final_url': url,
'security_score': 0,
'scan_duration': 0,
'http_status': 0,
'present_headers': 0,
'total_headers': 11,
'headers': [],
'issues': [],
'recommendations': [],
'risk_level': 'Низкий',
'cors_analysis': {},
'redirects': False,
'server_info': '',
'scan_details': {}
}
# 1. Базовый запрос с анализом редиректов и сервера
print(f"🔍 Сканирование: {url}")
response = requests.get(
url,
timeout=10,
allow_redirects=True,
headers={'User-Agent': 'Security-Scanner/2.0'}
)
results['final_url'] = response.url
results['http_status'] = response.status_code
results['redirects'] = (url != response.url)
results['server_info'] = response.headers.get('Server', 'Не указан')
# 2. Детальный анализ security headers
security_analysis = analyze_security_headers(response)
results['headers'] = security_analysis['headers']
results['issues'] = security_analysis['issues']
results['recommendations'] = security_analysis['recommendations']
results['present_headers'] = security_analysis['present_headers']
# 3. Углубленный CORS анализ
cors_results = analyze_cors_policy(url, response)
results['cors_analysis'] = cors_results
results['issues'].extend(cors_results.get('issues', []))
results['recommendations'].extend(cors_results.get('recommendations', []))
# 4. Расчет итоговых показателей
final_scores = calculate_security_metrics(results)
results['security_score'] = final_scores['score']
results['risk_level'] = final_scores['risk_level']
results['scan_duration'] = round(time.time() - start_time, 2)
results['scan_details'] = final_scores['details']
return results
except requests.exceptions.RequestException as e:
return create_error_response(url, start_time, f"Ошибка подключения: {str(e)}")
except Exception as e:
return create_error_response(url, start_time, f"Ошибка сканирования: {str(e)}")
def analyze_security_headers(response) -> Dict[str, Any]:
"""
Детальный анализ каждого заголовка безопасности
"""
headers = []
global_issues = []
global_recommendations = []
present_headers = 0
# Конфигурация заголовков ТОЛЬКО с описаниями
headers_config = [
{
'name': 'Content-Security-Policy',
'critical': True,
'description': 'Защита от XSS и внедрения кода',
'risk_description': 'Без CSP сайт уязвим к внедрению вредоносных скриптов и XSS атакам'
},
{
'name': 'Strict-Transport-Security',
'critical': True,
'description': 'Принудительное использование HTTPS',
'risk_description': 'Без HSTS возможны downgrade атаки и перехват трафика'
},
{
'name': 'X-Frame-Options',
'critical': True,
'description': 'Защита от clickjacking',
'risk_description': 'Без защиты сайт можно встроить в iframe для обманных действий'
},
{
'name': 'X-Content-Type-Options',
'critical': True,
'description': 'Блокировка MIME-sniffing',
'risk_description': 'Браузер может неправильно определить тип контента, что приведет к уязвимостям'
},
{
'name': 'Referrer-Policy',
'critical': False,
'description': 'Контроль утечки referrer данных',
'risk_description': 'Может привести к утечке чувствительных данных в URL'
},
{
'name': 'Permissions-Policy',
'critical': False,
'description': 'Управление доступом к API браузера',
'risk_description': 'Сайт может получить доступ к камере, микрофону без согласия пользователя'
},
{
'name': 'Access-Control-Allow-Origin',
'critical': False,
'description': 'CORS политика - разрешенные домены',
'risk_description': 'Неправильная настройка может открыть API для любых доменов'
},
{
'name': 'Access-Control-Allow-Methods',
'critical': False,
'description': 'CORS разрешенные методы',
'risk_description': 'Разрешение опасных методов (PUT, DELETE) может привести к уязвимостям'
},
{
'name': 'Access-Control-Allow-Headers',
'critical': False,
'description': 'CORS разрешенные заголовки',
'risk_description': 'Избыточные разрешения могут обойти защиту'
},
{
'name': 'Access-Control-Allow-Credentials',
'critical': False,
'description': 'CORS передача учетных данных',
'risk_description': 'В сочетании с ACAO: * создает критическую уязвимость'
},
{
'name': 'X-XSS-Protection',
'critical': False,
'description': 'Защита от XSS (устаревшая)',
'risk_description': 'Устаревшая защита, не эффективна в современных браузерах'
}
]
for config in headers_config:
header_name = config['name']
header_value = response.headers.get(header_name)
is_present = header_value is not None
# Анализ качества настройки (ТОЛЬКО анализ, без рекомендаций)
analysis = analyze_header_quality(header_name, header_value, is_present)
# Формируем данные заголовка
header_data = {
'name': header_name,
'present': is_present,
'value': header_value,
'critical': config['critical'],
'description': config['description'],
'risk_description': config['risk_description'],
'risk_level': analysis['risk_level'],
'quality_score': analysis['quality_score'],
'notes': analysis['notes'] # Только заметки, без рекомендаций
}
headers.append(header_data)
# Глобальные проблемы и рекомендации (только если заголовок отсутствует)
if not is_present:
if config['critical']:
global_issues.append(f"❌ {header_name} отсутствует — {config['risk_description']}")
global_recommendations.append(generate_header_recommendation(header_name))
else:
global_issues.append(f"⚠️ {header_name} отсутствует — {config['risk_description']}")
return {
'headers': headers,
'issues': global_issues,
'recommendations': global_recommendations,
'present_headers': present_headers
}
def analyze_header_quality(header_name: str, value: str, is_present: bool) -> Dict[str, Any]:
"""
Анализ качества настройки заголовка (ТОЛЬКО анализ, без рекомендаций)
"""
analysis = {
'risk_level': 'Низкий',
'quality_score': 100 if is_present else 0,
'notes': []
}
if not is_present:
analysis['risk_level'] = 'Высокий' if header_name in ['Content-Security-Policy', 'Strict-Transport-Security',
'X-Frame-Options'] else 'Средний'
return analysis
# Анализ качества для присутствующих заголовков
if header_name == 'Content-Security-Policy':
if 'unsafe-inline' in value:
analysis['risk_level'] = 'Средний'
analysis['quality_score'] = 60
analysis['notes'].append('CSP содержит unsafe-inline - снижает безопасность')
if 'unsafe-eval' in value:
analysis['risk_level'] = 'Средний'
analysis['quality_score'] = max(analysis['quality_score'] - 20, 0)
analysis['notes'].append('CSP содержит unsafe-eval - потенциально опасно')
if "'self'" in value and not any(x in value for x in ['unsafe-inline', 'unsafe-eval', '*']):
analysis['notes'].append('CSP правильно ограничивает источники')
elif header_name == 'Strict-Transport-Security':
if 'max-age=31536000' in value:
analysis['notes'].append('HSTS настроен на год - отлично')
if 'includeSubDomains' in value:
analysis['notes'].append('HSTS включает поддомены - правильно')
else:
analysis['risk_level'] = 'Средний'
analysis['notes'].append('HSTS не включает поддомены')
elif header_name == 'X-Frame-Options':
if value == 'DENY':
analysis['notes'].append('Полная защита от clickjacking')
elif value == 'SAMEORIGIN':
analysis['risk_level'] = 'Низкий'
analysis['notes'].append('Частичная защита - разрешено встраивание с того же origin')
elif header_name == 'Access-Control-Allow-Origin':
if value == '*':
analysis['risk_level'] = 'Высокий'
analysis['quality_score'] = 30
analysis['notes'].append('CORS открыт для всех доменов - критическая уязвимость')
elif value and value != '*':
analysis['notes'].append('CORS ограничен конкретными доменами - безопасно')
elif header_name == 'Access-Control-Allow-Credentials':
if value == 'true':
analysis['risk_level'] = 'Высокий'
analysis['notes'].append('CORS разрешает передачу учетных данных')
elif header_name == 'Permissions-Policy':
if not any(feature in value for feature in ['camera', 'microphone', 'geolocation']):
analysis['notes'].append('Базовые ограничения настроены')
elif 'camera=()' in value and 'microphone=()' in value and 'geolocation=()' in value:
analysis['notes'].append('Доступ к чувствительным API правильно ограничен')
else:
analysis['risk_level'] = 'Средний'
analysis['notes'].append('Рекомендуется ограничить доступ к чувствительным API')
return analysis
def generate_header_recommendation(header_name: str) -> str:
"""
Генерация рекомендаций ТОЛЬКО для отсутствующих заголовков
"""
recommendations = {
'Content-Security-Policy': 'Настройте Content-Security-Policy для защиты от XSS атак',
'Strict-Transport-Security': 'Добавьте HSTS для принудительного использования HTTPS',
'X-Frame-Options': 'Настройте X-Frame-Options: DENY для защиты от clickjacking',
'X-Content-Type-Options': 'Добавьте X-Content-Type-Options: nosniff',
'Referrer-Policy': 'Настройте Referrer-Policy для контроля утечки данных',
'Permissions-Policy': 'Ограничьте доступ к API: camera=(), microphone=(), geolocation=()',
'Access-Control-Allow-Origin': 'Настройте CORS политику для API если необходимо',
'Access-Control-Allow-Methods': 'Ограничьте разрешенные CORS методы',
'Access-Control-Allow-Headers': 'Ограничьте разрешенные CORS заголовки',
'Access-Control-Allow-Credentials': 'Настройте CORS credentials если необходимо',
'X-XSS-Protection': 'Замените на Content-Security-Policy для современной защиты'
}
return f"💡 {recommendations.get(header_name, f'Настройте {header_name}')}"
def analyze_cors_policy(target_url: str, base_response) -> Dict[str, Any]:
"""
Расширенный анализ CORS политики
"""
cors_results = {
'simple_request': {},
'preflight_request': {},
'with_credentials': {},
'security_level': 'Высокий',
'issues': [],
'recommendations': []
}
# Анализ базовых CORS заголовков
acao = base_response.headers.get('Access-Control-Allow-Origin')
acac = base_response.headers.get('Access-Control-Allow-Credentials')
if acao == '*':
cors_results['security_level'] = 'Критический'
cors_results['issues'].append('🚨 CORS открыт для всех доменов (*)')
cors_results['recommendations'].append('🔒 Немедленно ограничьте CORS конкретными доменами')
elif acao and acac == 'true':
cors_results['security_level'] = 'Высокий'
cors_results['issues'].append('⚠️ CORS с учетными данными разрешен')
cors_results['recommendations'].append('🔐 Убедитесь в строгой проверке Origin на сервере')
elif acao:
cors_results['security_level'] = 'Низкий'
cors_results['recommendations'].append('✅ CORS правильно настроен')
return cors_results
def calculate_security_metrics(results: Dict[str, Any]) -> Dict[str, Any]:
"""
Расчет комплексных метрик безопасности
"""
total_score = 0
max_score = 100
# 1. Базовые заголовки (50%)
present_headers = results.get('present_headers', 0)
total_headers = results.get('total_headers', 11)
base_score = (present_headers / total_headers) * 50
# 2. Качество настройки (30%)
quality_score = 0
for header in results.get('headers', []):
quality_score += header.get('quality_score', 0)
quality_score = (quality_score / len(results.get('headers', [1]))) * 0.3
# 3. Критические заголовки (20%)
critical_headers = ['Content-Security-Policy', 'Strict-Transport-Security', 'X-Frame-Options']
critical_present = sum(1 for h in results.get('headers', []) if h['name'] in critical_headers and h['present'])
critical_score = (critical_present / 3) * 20
total_score = base_score + quality_score + critical_score
# Определение уровня риска
if total_score >= 80:
risk_level = 'Низкий'
elif total_score >= 60:
risk_level = 'Средний'
elif total_score >= 40:
risk_level = 'Высокий'
else:
risk_level = 'Критический'
return {
'score': min(100, int(total_score)),
'risk_level': risk_level,
'details': {
'base_headers_score': int(base_score),
'quality_score': int(quality_score),
'critical_headers_score': int(critical_score),
'critical_headers_present': f"{critical_present}/3"
}
}
def create_error_response(url: str, start_time: float, error_msg: str) -> Dict[str, Any]:
"""Создание ответа при ошибке"""
return {
'target': url,
'error': error_msg,
'security_score': 0,
'risk_level': 'Неизвестен',
'scan_duration': round(time.time() - start_time, 2),
'headers': [],
'issues': [f"❌ {error_msg}"],
'recommendations': ['🔧 Проверьте доступность сайта и повторите сканирование']
}