Первый небольшой аудит
В сентябре 2026 года один из пользователей прислал нам два документа: разбор того, как можно обмануть нашего Telegram-бота, и отчёт о проверке сайта и личного кабинета. Мы проверили каждый пункт, закрыли всё, что подтвердилось, и нашли по ходу ещё несколько слабых мест, о которых в отчётах не было ни слова.
Здесь — что именно нашлось, как это работает и как мы защитились. Специальных знаний не нужно: каждый термин объясняется по ходу.
Зачем вообще нужны аудиты
Кто делает сервис, тот видит его изнутри. Он знает, как всё задумано, и именно поэтому перестаёт замечать, как это можно использовать не по назначению. Замок на двери лучше всех проверяет не хозяин, а тот, кто пытается её открыть.
Аудит безопасности — это взгляд со стороны. Кто-то смотрит на сервис глазами человека, который ищет лазейку, и записывает всё подозрительное. Бывает два вида такого взгляда:
| Вид аудита | Что видит проверяющий | Что может найти |
|---|---|---|
| Снаружи | только то, что доступно любому посетителю: сайт, бота, публичные файлы | ошибки, которыми может воспользоваться кто угодно |
| Изнутри | ещё и код, настройки серверов, базу | ошибки, которые снаружи пока никто не заметил |
Оба наших документа — взгляд снаружи. У них есть важное свойство, о котором стоит помнить: находка аудита — это гипотеза, а не приговор. Проверяющий видит симптом и предполагает причину. Правильная она или нет, можно узнать только проверкой.
Поэтому с каждой находкой мы шли по одному и тому же пути:
- Проверить, есть ли проблема на самом деле: командой на живом сервисе, а не рассуждением.
- Понять, как она работает, и поискать соседние слабости того же рода.
- Исправить и снова проверить на живом сервисе.
- Защитить исправление тестом. Чтобы убедиться, что тест не пустышка, мы нарочно ломаем исправленный код и смотрим, заметит ли тест поломку. Если не заметил, тест переписываем.
Хитрый пример: как можно обмануть Telegram-бота
Первый документ интереснее второго. В нём нет ни одного взлома в привычном смысле: никто не подбирает пароли от серверов и не ищет ошибки в коде. Все четыре приёма используют то, как устроен сам Telegram, и то, что бот слишком доверяет людям, с которыми разговаривает.
Приём 1. Бота «сносят» чужим ником
Это самый неочевидный и самый опасный приём из четырёх. Он работает так:
- Покупается дешёвый аккаунт Telegram. В разборе названа цена около 20 рублей.
- В ник или имя аккаунта ставится то, что запрещено правилами Telegram: слова, связанные с насилием над детьми, мошенничеством, оскорблениями.
- С этого аккаунта пишется
/startнашему боту. - Бот вежливо здоровается и показывает профиль, а в шапке профиля пишет ник собеседника.
- На это сообщение бота с нескольких аккаунтов отправляются жалобы. В разборе говорится, что хватает пяти. Точный порог Telegram не публикует.
Хитрость в том, что запрещённые слова написал не бот, а человек. Но в чате они стоят внутри сообщения бота, и для того, кто разбирает жалобу, это выглядит так, будто бот сам рассылает запрещённое. Нашему боту до этого дня такая жалоба грозила: он действительно показывал ник в шапке профиля.
Как мы защитились:
- В шапке профиля больше нет ни ника, ни имени. Шапке они не нужны по смыслу: человек и так знает, как его зовут. Если чужой текст не нужен, надёжнее всего его просто не показывать.
- Имя в подарке проходит проверку. Когда вы дарите гигабайты другу, бот пишет ему, от кого подарок, и тут имя действительно нужно. Перед тем как показать имя, бот проверяет его на запрещённые слова, ссылки, упоминания вида @ник и номера телефонов. Не прошло проверку — вместо имени будет «Ваш друг».
Проверку нельзя обмануть привычными уловками: слово, разбитое точками («p.o.r.n»), написанное цифрами вместо букв («p0rn»), смесью латиницы и кириллицы или разделённое невидимым символом, она узнаёт так же, как обычное. Обычные имена, в том числе с инициалами вроде «A.Ivanov», проходят.
Всё, что бот пишет, он пишет от своего имени, даже если слова придумал кто-то другой. Поэтому любой чужой текст в сообщении бота — ник, имя, подпись — это риск, и показывать его стоит только тогда, когда без него не обойтись.
Приём 2. Бота приводят в чужую группу
Любого бота в Telegram можно добавить в любую группу, и владелец бота об этом не узнает. Бот, который написан для личной переписки, в группе ведёт себя так же, как в личке. Из этого получаются две неприятности.
Спам от имени бота. Бота добавляют в десятки групп и вызывают там командой /start@имя_бота. Участники видят незнакомого бота со ссылками и жалуются — на бота, а не на того, кто его привёл.
Личное при всех. Если кто-то напишет боту в группе, бот ответит тем же экраном, что и в личке: профиль, остаток гигабайтов LTE, кнопки. А кнопку под сообщением бота в группе может нажать любой участник, и бот покажет уже его данные прямо в общем чате. На этом легко построить ловушку в духе «нажмите здесь, чтобы получить бонус».
Мьют навсегда — неприятно, но не опасно. В чужой группе бот — такой же участник, как все остальные, и прав администратора ему никто не даёт. Значит, для него действуют те же правила: администратор группы может запретить ему писать, в том числе навсегда (Telegram считает ограничение вечным, если оно установлено больше чем на 366 дней). Но такой мьют живёт только внутри этой группы. В личных сообщениях и в любых других чатах бот продолжает работать как обычно, поэтому сам по себе мьют ему не вредит. Опасность этого приёма в другом — в спаме от имени бота и жалобах на него.
У всех трёх наших ботов — основного, поддержки и вакансий — добавление в группы было разрешено, а сам код бота не проверял, откуда пришло сообщение. Теперь защита стоит в три слоя:
| Слой | Что делает | Зачем он нужен, если есть остальные |
|---|---|---|
| Настройка в BotFather | бота нельзя добавить в новую группу | главный замок на входе |
| Проверка в коде | сообщения и нажатия кнопок из групп и каналов до бота не доходят | настройка не выгоняет бота из групп, куда его добавили раньше |
| Выход из группы | если бота всё же добавили, он сразу выходит — даже если его успели замьютить | не оставляет бота там, где его не ждали |
Нажавший кнопку в группе увидит короткое «Бот работает только в личных сообщениях», а сам бот тут же покинет этот чат.
Приём 3. Невидимая буква и флуд командой /start
Многие боты защищаются от флуда просто: если одно и то же сообщение приходит слишком часто, лишние повторы отбрасываются. Эта защита обходится одним символом, которого не видно на экране.
Сообщение /start ㅤ выглядит как обычная команда с пробелом в конце. Но в конце стоит не пробел, а символ U+3164 — служебная «пустая» буква корейского алфавита. Для компьютера это буква, а не пробел. Поэтому фильтр повторов считает сообщение новым, а обработчик команд видит обычный /start с каким-то дополнительным словом и отвечает как на обычный старт.
| Символ | Как выглядит | Чем является для компьютера |
|---|---|---|
| Обычный пробел | пустое место | пробел, его легко отрезать |
| U+3164 HANGUL FILLER | пустое место | буква |
| U+2800 BRAILLE PATTERN BLANK | пустое место | символ |
| U+200B ZERO WIDTH SPACE | ничего | служебный невидимый знак |
У нашего бота фильтра повторов по тексту не было, поэтому и обходить было нечего. Но проверка этого приёма привела к находке поважнее. Мы посмотрели, как часто люди отправляют боту /start, и увидели две совершенно разные картины:
- обычные люди за последние 30 дней ни разу не нажали
/startчаще 5 раз за минуту; - 16 сентября в 12:42 15 аккаунтов без подписки отправили примерно по 50 команд
/startкаждый — и всё это за 31 секунду. Около 735 запросов, по 24 в секунду.
Почему это важно, если бот всё равно ответил? У ответов бота есть общий лимит со стороны Telegram. Когда бот его превышает, Telegram заставляет его ждать, и в это время медленнее отвечают все — в том числе люди, которые просто хотели открыть свой профиль. К тому же каждый /start — это запрос к нашей базе за данными профиля.
Как мы защитились:
- Считаем не текст, а человека. Все варианты
/start— с параметром, с именем бота, с невидимой буквой — идут в один счётчик для каждого человека. Обмануть его сменой текста нельзя. - Два лимита: короткий и длинный. Короткий гасит всплеск, длинный — медленный флуд. Обычных людей они не задевают: оба выше того, что мы видели за месяц.
- Отбитые попытки тоже считаются. Кто продолжает слать команды, остаётся под лимитом, пока не остановится. Человек, который случайно нажал лишний раз, подождёт совсем недолго.
- Предупреждение приходит один раз. Бот напишет «Слишком часто. Подождите минуту и нажмите /start снова» и дальше будет молчать. Отвечать на каждое лишнее сообщение значило бы тратить тот самый лимит, который мы защищаем.
Каждый такой случай записывается, поэтому мы увидим, если лимит вдруг начнёт задевать живых людей.
Приём 4. Бесконечный пробный период через группу
Последний приём рассчитан на ботов, которые дают бесплатный пробный период новым пользователям. Схема такая: создать группу, добавить туда бота, получить пробный период, удалить группу, создать новую и повторить.
Работает это из-за путаницы между двумя номерами. В каждом сообщении Telegram передаёт боту два разных идентификатора:
| Номер | Что это | В личной переписке | В группе |
|---|---|---|---|
| Номер отправителя | кто написал | номер человека | номер человека |
| Номер чата | где написали | тот же номер человека | номер группы, у каждой группы свой |
В личке оба номера совпадают, поэтому ошибка годами может оставаться незаметной. Но если бот узнаёт клиента по номеру чата, то в каждой новой группе он видит нового клиента — и снова выдаёт ему пробный период.
Наш бот узнаёт человека только по номеру отправителя. Для проверки мы посмотрели базу: среди 7 918 человек нет ни одного номера группы или служебного аккаунта Telegram. Этот приём у нас не работал никогда, а после защиты от групп из приёма 2 бот в группе и вовсе не ответит.
Итог по боту
| Приём | Работал ли у нас | Что сделали |
|---|---|---|
| Бота «сносят» чужим ником | да, ник был в шапке профиля | убрали ник из шапки, имя в подарке проверяется |
| Бота приводят в чужую группу | да, все три бота можно было добавить | запрет в BotFather, проверка в коде, выход из группы |
| Невидимая буква против антифлуда | нет, но нашёлся флуд 16 сентября | лимит /start на человека |
| Пробный период через группу | нет | ничего не требовалось |
Аудит сайта и личного кабинета
Второй документ устроен иначе. Это отчёт, составленный по публичным файлам сайта и кабинета: 12 находок, из них 6 «высокой» важности, и общий индекс защищённости 52 из 100.
Мы проверили каждую находку на живом сайте. Подтвердилась одна, и та низкой важности. Большая часть остальных оказалась предположениями вида «если сервер не делает X, то возможна атака Y» — а сервер X делает. Зато, пока мы проверяли, нашлись настоящие слабости, о которых в отчёте прямо не говорилось. Два пункта отчёта указывали в правильную сторону, хотя описанный в них сценарий был другим.
Что не подтвердилось
Отчёт от этого не становится бесполезным: каждую такую гипотезу всё равно нужно было проверить, и проверка заняла минуты. Вот главные пункты:
| В отчёте | Что оказалось |
|---|---|
| Нет защитных заголовков: HSTS, запрета встраивания, запрета «угадывания» типа файлов | все эти заголовки уже были; не хватало только одной опции у HSTS, о ней ниже |
| Список скрытых разделов в коде сайта ведёт к админке | список действительно виден, но это нормально для таких сайтов; права проверяет сервер, и постороннему он отказывает |
| Хранимая XSS-атака через комментарии | текст комментариев выводится как текст, а не как код, а политика безопасности сайта вдобавок запрещает запускать чужие скрипты |
| Утечка адресов серверов через карточки нод | адресов в ответе нет, список серверов виден только после входа |
| Подмена запросов сервера через «диагностику соединения» | пользователь не передаёт серверу никаких адресов, подменять нечего |
| Слабая секретная фраза для входа | фразу из 8 слов создаёт сервер надёжным генератором случайных чисел, и сейчас этот способ входа выключен |
| Повторное использование задачи «доказательства работы» | каждая задача одноразовая и подписана сервером |
| Устаревшие версии TLS, открытые файлы настроек и бэкапы | TLS 1.0 и 1.1 отключены, служебные файлы и бэкапы наружу не отдаются |
Настоящая находка 1: чужой вход через Google
Представьте гостиницу, где ключ-карту выдают по предъявлению брони. Злоумышленник бронирует номер на своё имя, получает карту и незаметно подкладывает её вам. Вы заходите в номер, раскладываете вещи — и не замечаете, что номер его, а не ваш.
С входом через Google было похоже. Когда вы нажимаете «Войти через Google», сайт отправляет вас к Google, а Google возвращает вас обратно по особой ссылке. Эта ссылка подписана нашим сервером, поэтому подделать её нельзя. Но она не была привязана к браузеру, в котором вход начинали.
Значит, злоумышленник мог начать вход со своим Google-аккаунтом, остановиться на последнем шаге и отправить ссылку возврата вам. Вы открываете ссылку — и оказываетесь в его кабинете, не замечая подмены. Всё, что вы там сделаете, например привяжете свой Telegram, окажется в его аккаунте. Такая атака называется CSRF-входом (подделкой межсайтового запроса при входе).
Как мы защитились. Теперь в момент нажатия «Войти через Google» сайт кладёт в ваш браузер одноразовую метку, а при возвращении проверяет, что метка на месте и совпадает со ссылкой. Чужая ссылка в вашем браузере метки не найдёт, и вход не состоится. Меток хранится несколько, поэтому вход не ломается, даже если вы нажали кнопку дважды или открыли вход в двух вкладках.
Настоящая находка 2: подбор кода входа из Telegram
В кабинет можно войти кодом, который присылает бот. У этого кода есть особенность: сайт не знает заранее, чей это код. Человек просто вводит код, а сайт ищет его среди всех кодов, выданных за последние 5 минут.
Значит, тот, кто перебирает коды наугад, угадывает не код конкретного человека, а любой из кодов, выданных в эти минуты. Кто-то войдёт в кабинет — и перебирающий окажется в его аккаунте. Сайт ограничивал число попыток с одного адреса, но у злоумышленника с тысячами адресов этот барьер слабее.
Код состоял из 6 символов, и каждый символ — один из 32 вариантов (латинские буквы и цифры без похожих, вроде O и 0). Мы увеличили длину до 8 символов. Вот что это меняет:
| Длина кода | Сколько всего вариантов | Сколько в среднем перебирать до первого попадания* |
|---|---|---|
| 6 символов | 1 073 741 824 | около 2,5 суток |
| 8 символов | 1 099 511 627 776 | около 7 лет |
*Если бы кто-то смог проверять 1000 кодов в секунду, а живых кодов одновременно было бы 5. В реальности сайт такой скорости не позволит — это оценка сверху.
Два лишних символа делают перебор примерно в тысячу раз дольше. Кроме того, мы завели тревогу: если неверных кодов вдруг становится заметно больше обычного, мы узнаём об этом сразу. Обычно их единицы — это опечатки.
Панель управления почтой
Почта Akonit работает на своём почтовом сервере, и у этого сервера есть веб-панель управления ящиками. Проверяя отчёт, мы заметили, что панель открыта всему интернету. В тот же день в логах нашлись попытки войти в неё под разными именами — они упёрлись во встроенный лимит попыток, и ни одна не удалась.
Панель нужна редко, поэтому мы её полностью закрыли: теперь по её адресу сервер отвечает «не найдено». Почта при этом работает как прежде, а ящиками мы управляем прямо на сервере.
Мелочи, которые тоже поправили
- HSTS для всех поддоменов. HSTS — это указание браузеру: «на этот сайт заходи только по защищённому соединению». Оно было, но касалось только самого сайта. Теперь оно распространяется и на все поддомены, например на кабинет. Перед включением мы проверили, что все наши поддомены работают по HTTPS, иначе браузеры перестали бы их открывать.
- Сервер не называет себя. Раньше ответы сайта сообщали, какой программой он обслуживается. Прямой опасности в этом нет, но и пользы тоже: лишняя подсказка для того, кто ищет уязвимости под конкретную программу.
- Кеши знают, что главная зависит от входа. Главная страница показывает разное тому, кто вошёл, и тому, кто нет. Теперь сайт прямо говорит об этом любому промежуточному кешу, чтобы тот никогда не показал одному человеку страницу, предназначенную другому.
- Появился файл security.txt. Это стандартный адрес
/.well-known/security.txt, где исследователи безопасности ищут, куда сообщать о найденной уязвимости. Теперь там указаны наш адрес и бот поддержки. - Из кода сайта убраны служебные комментарии. В публичных файлах были заметки разработчиков с именами наших внутренних серверов. Сами по себе они ничего не открывают, но рассказывают о внутреннем устройстве больше, чем нужно посетителю.
Почему нашли так мало
Из двенадцати находок отчёта подтвердилась одна, и та мелкая. Дело не в везении и не в невнимательности проверяющего. Отчёт по публичным файлам первым делом ищет самые распространённые виды атак: подмену запросов к базе данных, подбор паролей, флуд, отсутствие защитных заголовков. Всё это мы проверяем сами и закрыли задолго до того, как кто-то пришёл снаружи.
Две настоящие находки из этого документа устроены иначе: код входа не был привязан к человеку, а ссылка возврата от Google — к браузеру. Такие ошибки не видны по списку типовых проверок, их находишь, только разобравшись, как именно работает конкретный вход. Именно поэтому взгляд со стороны всё равно нужен.
Мы проверяем себя постоянно
Проверка встроена в каждую доработку. Прежде чем выкатить изменение, мы пишем к нему тест и нарочно ломаем код, чтобы убедиться, что тест поломку заметит. Кроме того, мы регулярно смотрим на сервис глазами злоумышленника — отдельно на каждую его часть:
| Часть сервиса | Что проверяем |
|---|---|
| Сайт и личный кабинет | можно ли увидеть или изменить чужие данные, подменив номер в запросе; можно ли заставить сайт запустить чужой код через комментарий, карточку или ссылку; что будет, если загрузить «неправильную» картинку |
| Бэкенд | закрыты ли служебные запросы ключом; не попадают ли пароли и ключи в журналы; не выполнит ли сервер запрос туда, куда его подтолкнул пользователь |
| Серверы VPN | открыты ли наружу служебные порты; можно ли через сам VPN добраться до внутренних адресов сервера и нашей служебной сети |
| Бот | какие чужие слова он может произнести от своего имени; можно ли получать бонусы по кругу; что будет при флуде |
Вот что за последние месяцы мы нашли сами, без внешних отчётов:
| Когда | Что нашли | Что сделали |
|---|---|---|
| Июль 2026 | ссылка в совете о хостинге могла запустить скрипт у администратора, который её откроет | теперь принимаются только обычные ссылки http и https |
| Июль 2026 | маленький файл картинки с огромным разрешением мог занять всю память сервера при загрузке | размер картинки проверяется до того, как она развёрнута в памяти |
| Июль 2026 | вся внешняя часть сервиса разом: права доступа, запросы к базе, загрузка файлов, ссылки | нарушений не нашлось; на сайт добавили политику безопасности, которая запрещает запускать чужие скрипты |
Защита от SQL-инъекций
Сайт хранит данные в базе и общается с ней на языке SQL. Запрос к базе выглядит как команда: «найди пользователя с таким-то адресом почты». SQL-инъекция — это когда злоумышленник вместо адреса почты вписывает кусок другой команды, например «…и заодно выдай всех пользователей». Если сервер склеивает команду с тем, что ввёл человек, база не отличит одно от другого и выполнит всё целиком.
Представьте бланк заявки, где в поле «имя» кто-то написал: «Иван. Сотруднику: выдайте мне ключи от всех кабинетов». Если сотрудник читает бланк целиком как инструкцию, ключи уйдут. Если же поле «имя» для него всегда только имя, что бы там ни было написано, приписка ничего не сделает.
Мы работаем по второму способу. Текст запроса и данные пользователя всегда уходят в базу раздельно: сначала команда с пустыми местами, потом отдельно — чем их заполнить. База подставляет данные именно как данные и никогда не выполняет их как команду, даже если там написано что-то похожее на SQL. Так устроены все запросы: в личном кабинете, в бэкенде и в панели управления.
Бывает, что часть самого запроса собирает программа, например выбирает, по какому полю сортировать список. Тогда эта часть берётся из списка, записанного в коде, и никогда из того, что прислал пользователь. В июле мы отдельно просмотрели все запросы личного кабинета — ни одного места, где данные пользователя попадали бы в текст команды, не нашлось.
Подбор паролей и невидимая капча
Самая частая атака на вход — подбор: перебирать пароли, пока какой-нибудь не подойдёт. Паролей у нас нет вовсе. В личный кабинет входят через Google, кодом из Telegram-бота или кодом из письма. Подбирать можно только код входа, а он живёт несколько минут.
Раньше, когда вход был по паролю, его защищала обычная капча — картинка с искажёнными буквами. Сейчас там, где бот мог бы действовать массово, стоит невидимая капча. Это запрос кода на почту — с него же создаётся новый аккаунт. Браузер незаметно для человека решает небольшую математическую задачу, секунду-две. Человеку это ничего не стоит, а тому, кто хочет отправить тысячу запросов, придётся решить тысячу задач. Каждая задача одноразовая и подписана сервером, поэтому решить одну и повторять её не получится. Заодно невидимая капча не даёт использовать наш сайт для рассылки писем на чужие адреса.
Сам перебор кода сдерживают лимиты, о которых ниже. Главный из них считает попытки не по адресу злоумышленника, а по почтовому ящику, который пытаются взломать: сколько бы у злоумышленника ни было адресов, число попыток на один ящик всё равно ограничено.
Лимиты — везде
Лимит — это правило вида «не больше стольких-то действий за такое-то время». Он не мешает живому человеку и делает бесполезным всё, что держится на количестве: перебор, флуд, массовые регистрации, спам.
Главное в лимитах — считать правильную вещь. Если считать только по адресу, злоумышленник с тысячей адресов получит в тысячу раз больше попыток. Поэтому у нас лимиты стоят слоями, и каждый слой считает своё:
Вот какие лимиты стоят в разных частях сервиса:
| Где | Что ограничено |
|---|---|
| Сеть и веб-сервер | число запросов входа и обычных запросов с одного адреса |
| Вход в кабинет | попытки ввести код — с одного адреса, с одной подсети и на один почтовый ящик; письма с кодом — на один ящик |
| Тяжёлые операции | у сервера ограниченное число «мест» для дорогих вычислений и отправки писем: лишние запросы получают «попробуйте позже», а сервер не падает |
| Анонимные запросы | у запросов без входа своя доля ресурсов: толпа незнакомцев не может вытеснить тех, кто уже вошёл |
| Комментарии, предложения, жалобы, фото | число публикаций на человека и на адрес |
| Бот | сколько раз человек может отправить /start и сколько вложений прислать в поддержку |
| Поддержка в кабинете | число сообщений в поддержку |
Лимит сам по себе не последняя линия. Там, где перебор возможен в принципе, стоит ещё и тревога: если кто-то упирается в лимиты или вводит подозрительно много неверных кодов, мы узнаём об этом сразу, а не из жалоб.
Ни одна проверка не доказывает, что ошибок не осталось. Она показывает только, что не нашлось тех, которые искали. Поэтому мы рады каждому внешнему отчёту — даже если подтвердится из него немного.
Напишите на security@akonit.tech или в бот поддержки @akonit_support_bot. Мы проверим каждую находку, даже если она окажется гипотезой, — так, как проверили эти.
Что стоит запомнить
- Находка аудита — это гипотеза. Её ценность в том, что она заставляет проверить, а не в том, что она всегда верна.
- Самые хитрые атаки на ботов не взламывают сервер: они заставляют бота сказать чужие слова от своего имени или раздавать бонусы по кругу.
- Защита от флуда должна считать людей, а не текст сообщений: текст легко менять невидимыми символами.
- Бот, написанный для личной переписки, должен отвечать только в личной переписке.
- Ссылка входа, подписанная сервером, ещё не доказывает, что её открыл тот же человек, который начинал вход.
- Два лишних символа в коде делают его перебор в тысячу раз дольше.
- Лимит должен считать то, что защищает: попытки на один почтовый ящик нельзя размножить сменой адресов.
- Данные пользователя и текст запроса к базе должны идти раздельно — тогда SQL-инъекции негде случиться.
Источники
- Telegram: режим приватности ботов в группах
- Telegram Bot API: getMe и настройка can_join_groups
- Telegram Bot API: событие добавления бота в чат (ChatMemberUpdated)
- Символ U+3164 HANGUL FILLER
- OWASP: защита от подделки межсайтовых запросов (CSRF)
- MDN: заголовок Strict-Transport-Security (HSTS)
- RFC 9116: файл security.txt
- OWASP: защита от SQL-инъекций
- OWASP: как блокировать подбор паролей
- OWASP: подделка запросов со стороны сервера (SSRF)