Все, кто пишет свою CMS, повторяют одни и те же ошибки. Я прошёл этот путь — от «сделаю за неделю» до «через полгода всё переписал». Разберу 7 главных грабель, чтобы вы их не наступили.
Ошибка 1. Нет prepared statements
Самая дорогая ошибка. Классика:
// ПЛОХО — SQL-инъекция $id = $_GET['id']; $sql = "SELECT * FROM posts WHERE id = $id";
Даже если «нет злого умысла» — атакующий всё равно найдёт. Всегда используйте PDO:
// ХОРОШО
$stmt = $pdo->prepare("SELECT * FROM posts WHERE id = ?");
$stmt->execute([$id]);
И обязательно PDO::ATTR_EMULATE_PREPARES => false, иначе подготовленные запросы эмулируются на стороне PHP и остаются уязвимы.
Ошибка 2. Хранение паролей в MD5/SHA1
MD5 — это не шифрование. Это хеш, который ломается за минуты. Любой сервис типа ******on.net взломает его за секунды.
Правильно — password_hash() и password_verify():
$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
Ещё лучше — использовать Argon2id (PASSWORD_ARGON2ID), если PHP скомпилирован с поддержкой.
Ошибка 3. Вывод без htmlspecialchars()
Ещё одна частая дыра — XSS:
// ПЛОХО echo $user_comment;
Если пользователь напишет <script>alert(1)</script> — скрипт выполнится. Всегда экранируйте:
echo htmlspecialchars($user_comment, ENT_QUOTES, 'UTF-8');
Для rich-text полей (статьи с HTML) — используйте HTMLPurifier. Он пропустит только безопасные теги и атрибуты.
Ошибка 4. Свой парсер BB-кодов вместо готовых решений
«Зачем HTMLPurifier, я напишу свой санитайзер регулярками». Не пишите. Санитайзеры — сложная тема. Регулярки не покрывают краевые случаи, а злоумышленники их обходят.
HTMLPurifier — 5 МБ, но работает из коробки. Другие варианты: OWASP AntiSamy, HTMLawed.
Ошибка 5. Нет CSRF-защиты
Любая форма на сайте — потенциальная цель CSRF-атаки. Схема:
- Атакующий делает страницу со скрытой формой «удалить всё»
- Ты залогинен в админке своего сайта
- Ты открываешь страницу атакующего
- Браузер отправляет форму с твоей кукой — сайт удаляет данные
Решение — токен в каждой форме:
<input type="hidden" name="csrf_token" value="<?= $token ?>">
И проверка на сервере: токен из формы должен совпадать с токеном в сессии.
Ошибка 6. Всё в одном файле
Файл index.php на 3000 строк «потому что так проще». Через месяц ты не найдёшь в нём точку, куда вставить новую функцию. Через год — перепишешь.
Разбивайте:
├── includes/ │ ├── config.php ← настройки │ ├── db.php ← работа с БД │ ├── auth.php ← авторизация │ ├── helpers.php ← утилиты │ └── router.php ← роутинг
Ошибка 7. Файлы в открытом доступе
Классика: в корне сайта лежит config.php с паролем от базы. Открываешь в браузере site.ru/config.php — видишь ошибку PHP, но при отключённом выводе ошибок — видишь пустую страницу. А что если атакующий добавит ?debug?
Решение — .htaccess:
<Files "config.php">
Require all denied
</Files>
Плюс — закрыть папки: logs, cache, backups, vendor.
Что делать правильно
Собрать чеклист и пройти по нему:
- ☐ PDO с EMULATE_PREPARES = false
- ☐ password_hash + password_verify
- ☐ htmlspecialchars на выводе
- ☐ HTMLPurifier для rich-text
- ☐ CSRF-токены на всех формах
- ☐ Модульная структура
- ☐ .htaccess на служебные папки
Если не хочешь изобретать велосипед — Kuznya CMS уже включает все 7 пунктов из коробки.