Безопасность и CSRF защита
Полное руководство по обеспечению безопасности в MNRFY Framework
Основы безопасности
Автоматическое экранирование
MNRFY автоматически экранирует все данные, выводимые через {{ }}, что предотвращает XSS атаки:
<!-- Безопасный вывод - данные автоматически экранируются -->
{{ user.name }}
{{ comment.text }}
{{ request.input('search') }}
<!-- Результат для "<script>alert('XSS')</script>" -->
<!-- Будет показано: <script>alert('XSS')</script> -->
<!-- Небезопасный сырой вывод - используйте только для доверенных данных! -->
{!! admin_content !!}
{!! page.html_content !!}
Важно! Никогда не используйте
{!! !!} для пользовательских данных. Это может привести к XSS атакам.
Валидация входных данных
<!-- Проверка email -->
{% set email = request.input('email') %}
{% if email and filter_var(email, FILTER_VALIDATE_EMAIL) %}
<p>Email корректный: {{ email }}</p>
{% else %}
<p class="error">Некорректный email</p>
{% endif %}
<!-- Проверка числовых данных -->
{% set age = request.input('age') %}
{% if is_numeric(age) and age >= 18 and age <= 120 %}
<p>Возраст: {{ age }} лет</p>
{% else %}
<p class="error">Некорректный возраст</p>
{% endif %}
<!-- Очистка строк -->
{% set username = request.input('username') | trim | striptags %}
{% if username | length >= 3 and username | length <= 20 %}
<p>Имя пользователя: {{ username }}</p>
{% else %}
<p class="error">Имя пользователя должно быть от 3 до 20 символов</p>
{% endif %}
CSRF защита
Основы CSRF защиты
Cross-Site Request Forgery (CSRF) - это атака, когда злоумышленник заставляет пользователя выполнить нежелательные действия на сайте, где он авторизован.
Использование CSRF токенов
<!-- В HTML формах -->
<form method="POST" action="/users/update">
{{ csrf() }}
<div class="form-group">
<label>Имя</label>
<input type="text" name="name" value="{{ user.name }}" required>
</div>
<button type="submit">Сохранить</button>
</form>
<!-- Альтернативный способ -->
<form method="POST" action="/posts/create">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
<textarea name="content" required></textarea>
<button type="submit">Создать пост</button>
</form>
CSRF для AJAX запросов
<!-- Мета-тег с CSRF токеном -->
<meta name="csrf-token" content="{{ csrf_token() }}">
<script>
// Получение CSRF токена
function getCSRFToken() {
return document.querySelector('meta[name="csrf-token"]').getAttribute('content');
}
// Пример AJAX запроса с CSRF токеном
async function updateProfile(data) {
try {
const response = await fetch('/api/profile', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': getCSRFToken(),
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
showNotification('Профиль обновлен', 'success');
} else {
showNotification(result.error || 'Ошибка обновления', 'error');
}
} catch (error) {
showNotification('Ошибка соединения', 'error');
}
}
// Автоматическое добавление CSRF токена ко всем формам
document.addEventListener('DOMContentLoaded', function() {
const token = getCSRFToken();
// Добавляем токен ко всем AJAX формам
document.querySelectorAll('form[data-ajax="true"]').forEach(form => {
form.addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(form);
formData.append('_token', token);
fetch(form.action, {
method: form.method,
headers: {
'X-CSRF-Token': token,
'X-Requested-With': 'XMLHttpRequest'
},
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
showNotification(data.message, 'success');
} else {
showNotification(data.error, 'error');
}
})
.catch(error => {
showNotification('Ошибка запроса', 'error');
});
});
});
// Обновление токена при истечении сессии
setInterval(() => {
fetch('/api/csrf-token')
.then(response => response.json())
.then(data => {
if (data.token) {
document.querySelector('meta[name="csrf-token"]').setAttribute('content', data.token);
}
});
}, 600000); // Каждые 10 минут
});
function showNotification(message, type) {
// Реализация уведомлений
const notification = document.createElement('div');
notification.className = `notification notification-${type}`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => notification.remove(), 5000);
}
</script>
CSRF в пользовательских обработчиках
<!-- Форма с пользовательским обработчиком -->
<form method="POST" action="/">
{{ csrf() }}
<input type="hidden" name="__handler" value="delete-account">
<div class="warning-message">
<h3>⚠️ Удаление аккаунта</h3>
<p>Это действие нельзя отменить. Все ваши данные будут удалены навсегда.</p>
</div>
<div class="form-group">
<label>Подтвердите удаление, введя "УДАЛИТЬ"</label>
<input type="text" name="confirmation" required
pattern="УДАЛИТЬ" title="Введите точно: УДАЛИТЬ">
</div>
<div class="form-group">
<label>Введите ваш пароль</label>
<input type="password" name="password" required>
</div>
<button type="submit" class="btn btn-danger"
onclick="return confirm('Вы действительно хотите удалить аккаунт?')">
Удалить аккаунт навсегда
</button>
</form>
Безопасность баз данных
Предотвращение SQL инъекций
MNRFY автоматически использует подготовленные запросы, что предотвращает SQL инъекции:
<!-- Безопасные запросы через API -->
{% set users = db('1752665380840')->getItems('users', {
'name': request.input('name'),
'email': request.input('email')
}) %}
<!-- Безопасный сырой SQL запрос -->
{% set searchResults = db('1752665380840')->query('
SELECT * FROM users
WHERE name LIKE ? AND email = ? AND created_at > ?
', ['%' ~ request.input('search') ~ '%', request.input('email'), strtotime('-30 days')]) %}
Автоматическая защита: Все параметры в запросах MNRFY автоматически экранируются.
Валидация данных перед запросами
<!-- Безопасное обновление пользователя -->
{% if request.isMethod('POST') %}
{% set userId = request.input('user_id') %}
{% set newEmail = request.input('email') %}
{% set newName = request.input('name') %}
{% set errors = [] %}
<!-- Валидация ID пользователя -->
{% if not is_numeric(userId) or userId <= 0 %}
{% set errors = errors | merge(['Некорректный ID пользователя']) %}
{% endif %}
<!-- Валидация email -->
{% if not filter_var(newEmail, FILTER_VALIDATE_EMAIL) %}
{% set errors = errors | merge(['Некорректный email']) %}
{% endif %}
<!-- Валидация имени -->
{% set cleanName = newName | trim | striptags %}
{% if cleanName | length < 2 or cleanName | length > 50 %}
{% set errors = errors | merge(['Имя должно быть от 2 до 50 символов']) %}
{% endif %}
{% if errors | length == 0 %}
<!-- Проверяем права доступа -->
{% if auth() and (auth().id == userId or auth().role == 'admin') %}
{% set updated = db('1752665380840')->editItem('users',
{'id': userId},
{'email': newEmail, 'name': cleanName, 'updated_at': time()}
) %}
{% if updated %}
<div class="alert alert-success">Данные успешно обновлены</div>
{% else %}
<div class="alert alert-error">Ошибка обновления данных</div>
{% endif %}
{% else %}
<div class="alert alert-error">Недостаточно прав для выполнения операции</div>
{% endif %}
{% else %}
<div class="alert alert-error">
{% foreach errors as error %}
<p>{{ error }}</p>
{% endforeach %}
</div>
{% endif %}
{% endif %}
Контроль доступа
Проверка авторизации
<!-- Базовая проверка авторизации -->
{% if not auth() %}
<div class="auth-required">
<h3>🔒 Требуется авторизация</h3>
<p>Для доступа к этой странице необходимо войти в систему.</p>
<a href="/login" class="btn btn-primary">Войти</a>
<a href="/register" class="btn btn-secondary">Регистрация</a>
</div>
{% else %}
<!-- Контент для авторизованных пользователей -->
<div class="user-content">
<h2>Добро пожаловать, {{ auth().name }}!</h2>
<!-- Остальной контент -->
</div>
{% endif %}
Проверка ролей и разрешений
<!-- Проверка роли администратора -->
{% if auth() and auth().role == 'admin' %}
<div class="admin-panel">
<h3>👑 Панель администратора</h3>
<a href="/admin/users" class="btn btn-danger">Управление пользователями</a>
<a href="/admin/settings" class="btn btn-warning">Настройки системы</a>
</div>
{% endif %}
<!-- Проверка множественных ролей -->
{% if auth() and auth().role in ['admin', 'moderator'] %}
<div class="moderation-tools">
<h3>🛡️ Инструменты модерации</h3>
<a href="/moderate/comments" class="btn btn-info">Модерировать комментарии</a>
<a href="/moderate/posts" class="btn btn-info">Модерировать посты</a>
</div>
{% endif %}
<!-- Проверка прав доступа -->
{% if can('edit_posts') %}
<a href="/posts/{{ post.id }}/edit" class="btn btn-primary">Редактировать</a>
{% endif %}
{% if can('delete_posts') %}
<button onclick="deletePost({{ post.id }})" class="btn btn-danger">Удалить</button>
{% endif %}
Проверка владения ресурсом
<!-- Редактирование только своих постов -->
{% if auth() and (post.user_id == auth().id or auth().role == 'admin') %}
<div class="post-controls">
<a href="/posts/{{ post.id }}/edit" class="btn btn-primary">Редактировать</a>
<button onclick="deletePost({{ post.id }})" class="btn btn-danger">Удалить</button>
</div>
{% endif %}
<!-- Просмотр приватного профиля -->
{% if user.is_private and auth() and user.id != auth().id %}
<div class="private-profile">
<h3>🔒 Приватный профиль</h3>
<p>Этот пользователь скрыл свой профиль от посторонних.</p>
</div>
{% else %}
<!-- Показываем профиль -->
<div class="user-profile">
<h2>{{ user.name }}</h2>
<p>{{ user.bio }}</p>
</div>
{% endif %}
Защита от атак
Защита от перебора (Brute Force)
<!-- Ограничение попыток входа -->
{% set maxAttempts = 5 %}
{% set lockoutTime = 900 %} <!-- 15 минут -->
{% set clientIP = request.ip() %}
{% set attempts = session('login_attempts_' ~ clientIP, 0) %}
{% set lastAttempt = session('last_attempt_' ~ clientIP, 0) %}
{% if attempts >= maxAttempts and (time() - lastAttempt) < lockoutTime %}
<div class="alert alert-danger">
<h3>🚫 Аккаунт временно заблокирован</h3>
<p>Слишком много неудачных попыток входа с вашего IP адреса.</p>
<p>Попробуйте снова через {{ ((lockoutTime - (time() - lastAttempt)) / 60) | round }} минут.</p>
</div>
{% else %}
<form method="POST" action="/login">
{{ csrf() }}
<div class="form-group">
<label>Email или логин</label>
<input type="text" name="login" required autocomplete="username">
</div>
<div class="form-group">
<label>Пароль</label>
<input type="password" name="password" required autocomplete="current-password">
</div>
{% if attempts > 0 %}
<div class="alert alert-warning">
Неудачных попыток: {{ attempts }} из {{ maxAttempts }}
</div>
{% endif %}
<button type="submit" class="btn btn-primary">Войти</button>
</form>
{% endif %}
Защита от ботов (CAPTCHA)
<!-- Простая математическая CAPTCHA -->
{% set num1 = rand(1, 10) %}
{% set num2 = rand(1, 10) %}
{% set captchaAnswer = num1 + num2 %}
<form method="POST" action="/contact">
{{ csrf() }}
<div class="form-group">
<label>Имя</label>
<input type="text" name="name" required>
</div>
<div class="form-group">
<label>Email</label>
<input type="email" name="email" required>
</div>
<div class="form-group">
<label>Сообщение</label>
<textarea name="message" required></textarea>
</div>
<!-- CAPTCHA -->
<div class="form-group captcha-group">
<label>Решите пример: {{ num1 }} + {{ num2 }} = ?</label>
<input type="number" name="captcha" required min="2" max="20">
<input type="hidden" name="captcha_answer" value="{{ captchaAnswer | base64_encode }}">
</div>
<button type="submit" class="btn btn-primary">Отправить</button>
</form>
Защита конфиденциальных данных
Хеширование паролей
<!-- В обработчике регистрации (PHP код) -->
{% php %}
// Пример безопасного хеширования пароля
$password = $_POST['password'] ?? '';
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
// Сохранение в БД
$userId = db('1752665380840')->addItem('users', [
'login' => $_POST['login'],
'email' => $_POST['email'],
'password_hash' => $passwordHash, // Храним хеш, а не пароль
'created_at' => time()
]);
{% endphp %}
<!-- Проверка пароля при входе -->
{% php %}
$loginInput = $_POST['login'] ?? '';
$passwordInput = $_POST['password'] ?? '';
$user = db('1752665380840')->getItem('users', [
'login' => $loginInput
]);
if ($user && password_verify($passwordInput, $user['password_hash'])) {
// Пароль правильный
$_SESSION['user_id'] = $user['id'];
$loginSuccess = true;
} else {
// Неправильный пароль
$loginError = 'Неверный логин или пароль';
}
{% endphp %}
Защита персональных данных
<!-- Маскирование чувствительных данных -->
<div class="user-info">
<h3>Информация о пользователе</h3>
<p>Имя: {{ user.name }}</p>
<!-- Маскируем email -->
{% set emailParts = user.email | split('@') %}
{% set maskedEmail = emailParts[0] | slice(0, 2) ~ '***@' ~ emailParts[1] %}
<p>Email: {{ maskedEmail }}</p>
<!-- Маскируем телефон -->
{% if user.phone %}
{% set maskedPhone = user.phone | slice(0, 2) ~ '***-**-' ~ user.phone | slice(-2) %}
<p>Телефон: {{ maskedPhone }}</p>
{% endif %}
<!-- Показываем полные данные только владельцу -->
{% if auth() and auth().id == user.id %}
<div class="full-info">
<h4>Полная информация</h4>
<p>Полный email: {{ user.email }}</p>
{% if user.phone %}
<p>Полный телефон: {{ user.phone }}</p>
{% endif %}
</div>
{% endif %}
</div>
Комплексная форма с защитой
Безопасная форма регистрации
<form method="POST" action="/" class="secure-registration-form">
{{ csrf() }}
<input type="hidden" name="__handler" value="secure-register">
<h2>🔒 Безопасная регистрация</h2>
<div class="form-group">
<label>Логин *</label>
<input type="text" name="login" required
minlength="3" maxlength="20"
pattern="[a-zA-Z0-9_-]+"
title="Только буквы, цифры, дефисы и подчеркивания"
value="{{ old('login') }}">
<div class="field-requirements">
3-20 символов, только буквы, цифры, дефисы и подчеркивания
</div>
</div>
<div class="form-group">
<label>Email *</label>
<input type="email" name="email" required
value="{{ old('email') }}">
</div>
<div class="form-group">
<label>Пароль *</label>
<input type="password" name="password" required
minlength="8"
pattern="(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}"
title="Минимум 8 символов, включая строчные и заглавные буквы, цифры и спецсимволы"
onkeyup="checkPasswordStrength(this.value)">
<div id="password-strength" class="password-strength"></div>
<div class="field-requirements">
Минимум 8 символов: строчные и заглавные буквы, цифры, спецсимволы
</div>
</div>
<div class="form-group">
<label>Подтверждение пароля *</label>
<input type="password" name="password_confirmation" required>
</div>
<!-- Математическая CAPTCHA -->
{% set captchaNum1 = rand(5, 15) %}
{% set captchaNum2 = rand(1, 10) %}
{% set captchaAnswer = captchaNum1 + captchaNum2 %}
<div class="form-group captcha-group">
<label>Подтвердите, что вы не робот: {{ captchaNum1 }} + {{ captchaNum2 }} = ?</label>
<input type="number" name="captcha_answer" required min="6" max="25">
<input type="hidden" name="captcha_correct" value="{{ hash('sha256', captchaAnswer ~ session_id()) }}">
</div>
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" name="agree_terms" required>
Я согласен с <a href="/terms" target="_blank">условиями использования</a>
и <a href="/privacy" target="_blank">политикой конфиденциальности</a> *
</label>
</div>
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" name="newsletter">
Получать новости и обновления по email
</label>
</div>
<!-- Показываем ошибки валидации -->
{% if flash('errors') %}
<div class="alert alert-danger">
<h4>Ошибки валидации:</h4>
<ul>
{% foreach flash('errors') as error %}
<li>{{ error }}</li>
{% endforeach %}
</ul>
</div>
{% endif %}
<button type="submit" class="btn btn-primary btn-block">
🔒 Создать аккаунт
</button>
<p class="form-footer">
Уже есть аккаунт? <a href="/login">Войти</a>
</p>
</form>
<script>
function checkPasswordStrength(password) {
const strengthDiv = document.getElementById('password-strength');
let score = 0;
let feedback = [];
if (password.length >= 8) score++;
else feedback.push('Минимум 8 символов');
if (/[a-z]/.test(password)) score++;
else feedback.push('Строчные буквы');
if (/[A-Z]/.test(password)) score++;
else feedback.push('Заглавные буквы');
if (/\d/.test(password)) score++;
else feedback.push('Цифры');
if (/[@$!%*?&]/.test(password)) score++;
else feedback.push('Спецсимволы (@$!%*?&)');
const strength = ['Очень слабый', 'Слабый', 'Средний', 'Хороший', 'Отличный'];
const colors = ['#ff4444', '#ff8800', '#ffaa00', '#88cc00', '#00cc44'];
strengthDiv.innerHTML = `
<div class="strength-bar" style="background-color: ${colors[score]}; width: ${score * 20}%;"></div>
<div class="strength-text">Сила пароля: ${strength[score]}</div>
${feedback.length > 0 ? `<div class="missing">Не хватает: ${feedback.join(', ')}</div>` : ''}
`;
}
</script>
Безопасность обеспечена! Вы изучили все аспекты безопасности в MNRFY Framework. Теперь вы можете создавать безопасные веб-приложения.
Следующие шаги:
- Изучите работу с базой данных
- Посмотрите практические примеры
- Изучите пользовательские обработчики