20 de mayo de 2026
5 min read
ZeroTrust Team

Asegura tus eventos: Guía de seguridad de eventos de Lua para FiveM

Los cheats permiten a los clientes maliciosos activar eventos en cualquier contexto. Aprende a proteger la comunicación cliente-servidor, implementar validación del lado del servidor y configurar convars de seguridad de FXServer.

El equipo anti-cheat siempre está intentando mejorar el sistema, pero a veces las cosas se filtran.

En esta guía intentaremos ayudarte a cubrir algunas prácticas comunes que puedes realizar para hacer que tu servidor sea más seguro bloqueando adecuadamente tus eventos.

Entendiendo los eventos de red en FiveM

Los cheats pueden permitir al cliente activar eventos en cualquier contexto.

Cuando decimos contexto nos referimos a que pueden ejecutar cliente->servidor (a través de TriggerServerEvent) o recurso del cliente->recurso del cliente (a través de TriggerEvent).

Uso correcto de manejadores de eventos en Lua

Al trabajar con eventos en Lua, es crucial registrarlos correctamente según si son llamados por el cliente o por el servidor.

Un error común es registrar eventos del servidor que no deben ser llamados por el cliente, o viceversa, lo que puede provocar graves vulnerabilidades de seguridad.

AddEventHandler

Utiliza AddEventHandler cuando el evento esté deba activarse dentro del mismo contexto, ya sea cliente-cliente o servidor-servidor. Esto asegura que los eventos no se transmitan por red y no puedan ser llamados por el lado opuesto.

AddEventHandler("eventName", function(eventParam1, eventParam2)
    -- El código aquí se ejecutará una vez que el evento se active dentro del mismo contexto.
end)

RegisterNetEvent

Utiliza RegisterNetEvent cuando el evento deba activarse en diferentes contextos, como del cliente al servidor o del servidor al cliente.

Bajo el capó

NOTA: Esto no bloquea la ejecución desde el mismo contexto. Bajo el capó, RegisterNetEvent es un contenedor que es simplemente lo siguiente: RegisterNetEvent("eventName") AddEventHandler("eventName", function() ... end)
RegisterNetEvent("eventName", function(eventParam1, eventParam2)
    -- El código aquí se ejecutará una vez que el evento se active a través de diferentes contextos.
end)

Este ejemplo es para el cliente y, como todo en el cliente, no es infalible y puede ser manipulado por clientes con trampas.

Si deseas bloquear la ejecución desde el mismo contexto (como evitar que un evento de red de servidor a servidor sea activado por el cliente), debes registrar tu evento y verificar la fuente del remitente:

RegisterNetEvent("eventName", function(eventParam1, eventParam2)
    -- El servidor enviará el ID de red `65535` para eventos del servidor
    if source ~= 65535 then return end
end)

Añadiendo comprobaciones y verificación

Incluso si construyes un sistema anti-cheat sólido, añadir comprobaciones en los eventos del servidor los hace significativamente más seguros. Esta es una práctica muy recomendada, aunque no lo previene todo. A continuación, compartimos algunos buenos consejos.

  • Dinero del jugador: Valida el saldo y las transacciones en el servidor.
  • State bags del jugador: Verifica los datos de estado en las sesiones activas.
  • Objetos del inventario del jugador: Comprueba los nombres y cantidades de los objetos.
  • Posición del jugador: Verifica rangos y distancias.
  • Experiencia y nivel del jugador: Asegúrate de que las estadísticas se calculen en el servidor.
  • Permisos y roles del jugador: Valida las ACL en el servidor.

Regla de oro

Asegúrate de obtener todos los valores utilizando métodos del lado del servidor, sin permitir que los jugadores proporcionen o cambien los valores. Ten en cuenta que las comprobaciones en el cliente también pueden ser una buena práctica para la experiencia de usuario (UX), pero pueden anularse fácilmente.

Esto asegura la integridad y seguridad de tu entorno de juego.

Ejemplos de patrones de seguridad comunes

Todos los ejemplos a continuación asumen algún tipo de framework (como ESX, QB-Core, etc.).

Seguridad mala (Nunca hagas esto)

Esto está pensado para mostrarte formas incorrectas de hacer eventos, nunca deberías hacer esto. Añadir objetos directamente al usuario a partir de su propia entrada es siempre una mala práctica: siempre debes validar la entrada del usuario.

RegisterNetEvent("job:givePlayerItem", function(item, count)
    local ply = FX.GetPlayerFromSource(source)
    -- ¡Añadir objetos directamente al usuario desde su propia entrada es peligroso!
    ply.addItem(item, count)
end)

Seguridad buena (Recomendado)

Aquí tienes un patrón de seguridad robusto. El servidor realiza un seguimiento del estado y las coordenadas del jugador, y verifica las acciones mediante ticks en el servidor en lugar de depender únicamente de los activadores del cliente.

-- coordenada ficticia
local VALID_JOB_COORD = vector3(125.0, 111.1, 35.83)
local MAX_ITEM_COUNT = 10

// coordenada ficticia
local VALID_TURNIN_COORD = vector3(1888.0, 1254.1, 48.0)

local ITEM_NAME = 'log'

-- lista de jugadores con trabajos activos
local activeJobs = {}

AddEventHandler("playerDropped", function ()
    if not activeJobs[source] then return end
    activeJobs[source] = nil
end)

function isPedWithinRange(ped, tgtCoords)
    return #(GetEntityCoords(ped) - tgtCoords) < 15.0
end

-- procesar el tick de trabajo e incrementar la cantidad de objetos del lado del servidor
CreateThread(function()
    while true do
        for src, data in pairs(activeJobs) do
            local ped = GetPlayerPed(src)
            -- si no están dentro del rango, no queremos darles el objeto
            if isPedWithinRange(ped, VALID_JOB_COORD) then
                -- darles el objeto, pero limitarlo a MAX_ITEM_COUNT
                data.itemCount = math.min(data.itemCount + 1, MAX_ITEM_COUNT)
            end
        end
        -- procesar el tick de trabajo una vez por segundo
        Wait(1000)
    end
end)

RegisterNetEvent("job:startJob", function()
    local ped = GetPlayerPed(source)
    -- si están dentro de 15 unidades, están haciendo el trabajo
    if isPedWithinRange(ped, VALID_JOB_COORD) then
        activeJobs[source] = {
            itemCount = 0,
        }
    end
end)

RegisterNetEvent("job:givePlayerItem", function()
    local ply = FX.GetPlayerFromSource(source)
    -- si no tienen un trabajo activo, ¡no deberían llegar a esto!
    local jobData = activeJobs[source]
    if not jobData then return end

    local ped = GetPlayerPed(source)
    -- no están dentro del rango de las coordenadas de entrega, rechaza sus cambios
    if not isPedWithinRange(ped, VALID_TURNIN_COORD) then return end

    -- restablecer los datos del objeto para que no puedan activarlo varias veces
    activeJobs[source] = nil

    -- añadir objetos al usuario validados en el servidor
    ply.addItem(ITEM_NAME, jobData.itemCount)
end)

Opciones del propietario del servidor (Convars)

Ten en cuenta que las siguientes configuraciones no deben modificarse a menos que sepas exactamente lo que estás haciendo. El equipo de Cfx.re / Adhesive siempre está trabajando arduamente para evitar trampas. La mayoría de estas características estarán habilitadas de forma predeterminada con la versión de compilación de FXServer 8450 y superior.

  • sv_kick_players_cnl_timeout_sec: Es el tiempo de espera tras el cual el servidor expulsará al jugador (por ejemplo, si es 600, los expulsa después de 10 minutos sin conexión CnL).
  • sv_kick_players_cnl_update_rate_sec: Es la frecuencia con la que se consulta a CnL con la lista de jugadores.
  • sv_pure_verify_client_settings: Reemplaza la petición periódica a info.json en el cliente. Establece una conexión segura entre adhesive y svadhesive y verifica algunas configuraciones de sv_settings como pureLevel, scripthook y otras.
  • sv_kick_players_cnl_consecutive_failures: Cuántos fallos consecutivos se necesitan ver por encima del timeout_sec para expulsar al jugador. Por defecto está establecido en 2, lo que indica que si un jugador no se conecta durante 10 minutos y luego se pierde la siguiente actualización de conexión, será expulsado. Esto sirve como mecanismo de seguridad.
  • sv_authMaxVariance: La varianza indica qué tan probable es que el ID del usuario cambie para un proveedor dado (es decir, 'steam', 'ip' o 'license').
  • sv_authMinTrust: La confianza indica qué tan improbable es que la identidad del usuario sea suplantada por un cliente malicioso.
  • sv_filterRequestControl: Una variable de consola utilizada para bloquear el enrutamiento de REQUEST_CONTROL_EVENT basándose en una política configurable.
  • sv_disableClientReplays: Habilitar esto tiene como objetivo reducir las posibilidades de opciones de trampas. Ten en cuenta que esto desactivará Rockstar Editor.

Resultados en el jugador

Tener estas convars activas probablemente hará que el jugador sea expulsado con la siguiente razón: Connection to CNL timed out.

Ser expulsado no significa que el jugador sea automáticamente baneado globalmente (Global Banned). Sin embargo, proporciona una fuerte indicación de la fiabilidad del jugador, lo cual es extremadamente útil para evaluar su nivel de confianza.

Importante saber

Los códigos proporcionados no están pensados para funcionar mediante copiar y pegar. Son solo algunos consejos para evitar acciones que podrían ocurrir en el servidor. Esto requiere conocimientos de programación. Siempre eres libre de unirte a nuestro Discord para obtener ayuda adicional.