Защита серверных событий RedM: каждый TriggerServerEvent это ввод атакующего
Как защитить серверные события RedM: почему каждый TriggerServerEvent это ввод атакующего, какие правила проверки применять и что добавляет античит.
Если вы хотите организовать защиту серверных событий RedM, начните с одного факта: игрок контролирует клиент, а значит, контролирует каждый аргумент каждого TriggerServerEvent. Деньги и предметы, исчезающие с ролевых серверов, редко уходят через хитрый взлом памяти. Они уходят через обработчик, поверивший переданному ему числу.
Это руководство для разработчиков ресурсов и владельцев серверов, которые проверяют скрипты. Примеры на нейтральном к фреймворку Lua. Те же правила применимы к любому ресурсу VORP, RSG или самописному, и именно они заставляют типичный эксплойт триггеров в RedM провалиться, а не выплатить награду.
Почему событие, вызванное клиентом, это ввод под контролем атакующего
Серверное событие это открытая дверь. Ваш клиентский скрипт вызывает TriggerServerEvent("shop:sell", item, count, price), а сервер запускает обработчик. Ничто в этом вызове не доказывает, что запрос пришёл из вашего скрипта. Чит-меню или внедрённый ресурс может вызвать то же событие с тем же именем и положить в аргументы что угодно.
Скрывать имя события бесполезно. Клиентский Lua отправляется каждому игроку, и имена событий в нём видны. Считайте имя публичным, а аргументы враждебными, как веб-разработчик относится к полю формы.
Это весь класс эксплойтов VORP в одном предложении: обработчик, применяющий то, что сказал клиент. Остальная часть руководства о том, как перестать так делать.
Уязвимый обработчик и исправленный
Вот форма, которую мы видим чаще всего. Клиент присылает предмет, количество и цену, а сервер платит.
RegisterNetEvent('shop:sell', function(item, count, price)
local src = source
-- takes the client's word for everything
removeItem(src, item, count)
addMoney(src, count * price)
end)Каждая строка это ошибка. Цену выбирает вызывающий, поэтому один вызов с огромным price печатает деньги. Отрицательный count превращает списание в подарок. Наличие у игрока предмета может вообще не проверяться. Обработчик также не спрашивает, стоит ли игрок у магазина.
Исправленная версия держит цены на сервере, проверяет типы и диапазоны, сверяет игрока с миром и спокойно отказывает, если что-то не так. removeItem, addMoney и getItemCount обозначают собственные функции вашего фреймворка.
local PRICES = { apple = 2, bread = 3 } -- authority lives here
local SHOP = vector3(-324.0, 803.0, 117.0) -- example coordinates
local lastCall = {}
local function refuse(src, why)
print(('[shop:sell] refused src=%s reason=%s'):format(src, why))
end
RegisterNetEvent('shop:sell', function(item, count)
local src = source -- never trust an id from the client
local now = GetGameTimer()
if lastCall[src] and now - lastCall[src] < 1000 then
return refuse(src, 'rate')
end
lastCall[src] = now
if type(item) ~= 'string' or type(count) ~= 'number'
or count % 1 ~= 0 or count < 1 or count > 50 then
return refuse(src, 'bad_args')
end
local price = PRICES[item]
if not price then return refuse(src, 'unknown_item') end
local ped = GetPlayerPed(src)
if #(GetEntityCoords(ped) - SHOP) > 5.0 then
return refuse(src, 'too_far')
end
if getItemCount(src, item) < count then
return refuse(src, 'not_owned')
end
removeItem(src, item, count)
addMoney(src, count * price) -- the server computes the reward
end)
AddEventHandler('playerDropped', function() lastCall[source] = nil end)Теперь клиент присылает два значения, и сервер считает оба лишь предложениями. Каждое правило в этом обработчике заслуживает отдельного абзаца.
Правила, обеспечивающие защиту серверных событий RedM
1. Определяйте игрока через source, и только через source
Внутри обработчика серверного события source это игрок, вызвавший его, и значение задаёт платформа, а не клиент. Сохраните его в локальную переменную в самом начале (local src = source), потому что оно может измениться после yield. Никогда не принимайте аргумент «id игрока» или «id цели», чтобы решить, кому платить или кому причинить вред.
2. Никогда не принимайте суммы, цены, названия предметов и цели на веру
Если клиент должен сообщить, чего он хочет, ищите это в справочнике. Цены, награды и списки предметов принадлежат таблице на стороне сервера. Если аргумент это цель (другой игрок, сущность, координата), проверьте, что она существует и что вызывающему разрешено с ней работать. Самые безопасные события почти не имеют аргументов.
3. Проверяйте типы и диапазоны
Lua охотно передаст таблицу там, где вы ждали строку, или -1, или дробь, или число настолько большое, что арифметика ведёт себя странно. Проверяйте type(), требуйте целые числа там, где имеете в виду целые, и задавайте жёсткую верхнюю границу, соответствующую тому, что функция может делать законно. Отклоняйте, а не обрезайте: обрезанный запрос всё равно ничего не говорит о том, кто его отправил.
4. Проверяйте расстояние, работу и состояние на сервере
Законная продажа происходит в магазине, законное изготовление у верстака, законный арест выполняет представитель закона. Проверяйте это по данным сервера: позиции игрока (с OneSync работают серверные нативы вроде GetPlayerPed и GetEntityCoords), его работе из вашего фреймворка, его текущему состоянию из ваших таблиц. Скрытая клиентским интерфейсом кнопка не является проверкой.
5. Пусть награду считает сервер
Самый сильный шаблон и самый простой: клиент говорит «я закончил», а сервер решает, чего это стоило. Для более долгих занятий начинайте занятие на сервере и записывайте время. Когда клиент сообщает о завершении, проверьте, что прошло достаточно времени и что сессия существует, затем выплатите сумму, определённую сервером.
6. Ограничивайте частоту каждого события, которое двигает ценность
Обработчик, корректный при одном вызове, всё равно можно вызвать двести раз в секунду. Храните для каждого игрока метку времени или счётчик и отбрасывайте всё, что превышает возможности человека. Очищайте таблицу на playerDropped, как в примере. Повторы и потоки запросов ещё и нагружают сервер, поэтому важен дешёвый ранний выход.
7. Не выставляйте админские события клиентам
Событие admin:giveMoney, которое ничего не проверяет, это подарок. Если инструменты персонала обязаны использовать события, проверяйте вызывающего на сервере каждый раз, через систему прав фреймворка или ACE, а не через флаг на клиенте. Ещё лучше использовать команды, зарегистрированные на сервере, или события, которые вызываются только между серверами.
8. Логируйте отказы
Функция refuse выше не украшение. Отказ это сигнал: либо ошибка в вашем собственном клиентском скрипте, либо кто-то прощупывает. Строка на каждый отказ с игроком и причиной превращает ваши логи в список зацепок. Одна оговорка: один отказ ничего не доказывает, поэтому проверяйте, прежде чем банить.
Где это ломается на практике
Унаследованные скрипты. Платный или утёкший ресурс, доверяющий своим аргументам, остаётся уязвимым, пока его кто-нибудь не прочитает. Найдите в ресурсах сервера
RegisterNetEventиRegisterServerEventи прочитайте каждый обработчик, затрагивающий деньги, предметы, работы или инвентарь.«Временные» отладочные события. Событие, добавленное для тестов и так и не удалённое.
Доверие флагу, выставленному клиентом.
isAdmin,isOnDutyи подобные значения, присланные аргументами, это просто ещё один ввод атакующего.Исправление одного обработчика. Если три ресурса платят из одной функции инвентаря, безопасность определяет самый слабый вызывающий.
Когда ущерб уже нанесён, картина в данных выглядит как дюп или внезапный выброс богатства. Наш разбор дюпа денег на RedM рассказывает, как заметить это со стороны экономики.
Что добавляет античит к защите событий RedM
Аккуратные обработчики устраняют уязвимость. Античит следит за теми, кто всё равно пытается, и за обработчиками, которые вы ещё не проверили. Baobab AC делает это на стороне сервера, и в его каталоге четыре модуля, важных здесь. Полный список в разделе все возможности.
Anti Blacklist Trigger не даёт чит-меню вызывать скрытые серверные события для таких действий, как несанкционированная смена работы или обход экономических правил.
Anti Spam Trigger блокирует флуд запросами, нацеленный на перегрузку бэкенда сервера.
Anti Economy Trigger Exploit не даёт читерам внедрять поддельные скрытые команды, чтобы выдать себе деньги, золото или ценные предметы.
Anti Native Injection перехватывает нативы, создающие сущности и меняющие состояние игрока, поэтому создание или смена состояния, которых не просил ни один серверный ресурс, фиксируются в момент, когда происходят.
В тарифах Pro и Network экономическая экспертиза (Economy Forensics) добавляет аудит вашей игровой базы данных в режиме только чтения, сопоставленный с игровым временем, которое известно только античиту: денежная масса во времени, выбросы богатства, дюп предметов, невозможные стаки. Каждый результат это зацепка для проверки человеком, а не автоматическая санкция. Детекция не зависит от фреймворка, а VORP Core служит эталоном для экономических модулей. Руководство по античиту для VORP подробно разбирает эту настройку, а как ловят чит-меню объясняет сторону детекции той же истории.
Как выглядит этот вывод, можно посмотреть на демо-дашборде, а тарифы сравнить на странице цен.
Чего античит не умеет
Стоит назвать ещё три ограничения. Детекция вероятностная, поэтому поставляемый режим это observe: сомнительная детекция стоит строки в списке, а не кика. Economy Forensics сообщает, что что-то выглядит неправильно, но не почему, и читать результат должен человек. И античит видит клиента и трафик, а не вашу бизнес-логику, так что только вы знаете, что продажа на 400 долларов в этом магазине невозможна.
Два слоя дополняют друг друга. Авторитет на стороне сервера делает эксплойт невозможным. Античит ловит попытку, а аудит показывает, что просочилось до исправления. Более широкий аргумент, почему доверие должно находиться на сервере, читайте в серверный и клиентский античит.
Чек-лист: защитите серверные события RedM сегодня
- 1
Составьте список каждого
RegisterNetEventиRegisterServerEventв ваших ресурсах. - 2
Для каждого, затрагивающего деньги, предметы, работы или инвентарь, прочитайте обработчик.
- 3
Замените каждую присланную клиентом цену, награду и id игрока поиском на сервере или
source. - 4
Добавьте проверки типов и диапазонов, затем проверку позиции или состояния.
- 5
Добавьте ограничение частоты для каждого игрока и журнал отказов.
- 6
Удалите отладочные и админские события, пропускающие проверки.
- 7
Только после этого включайте модули античита, чтобы закрыть то, что упустил обзор. Начните в режиме observe, читайте оповещения и ужесточайте постепенно. Как устроен конвейер, объясняет статья как работает античит RedM.
Доверчивый обработчик дёшево написать и дорого найти. Час чтения ваших серверных событий сейчас стоит меньше, чем одна ночь украденной экономики.