7 ошибок при создании самописной CMS

Kuznya Studio 18.09.2026 4

Все, кто пишет свою 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-атаки. Схема:

  1. Атакующий делает страницу со скрытой формой «удалить всё»
  2. Ты залогинен в админке своего сайта
  3. Ты открываешь страницу атакующего
  4. Браузер отправляет форму с твоей кукой — сайт удаляет данные

Решение — токен в каждой форме:

<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 пунктов из коробки.