2026年5月20日
5 分で読了
ZeroTrust Team

イベントを安全に保護する:FiveM Luaイベントセキュリティガイド

チートにより、悪意のあるクライアントが任意のコンテキストでイベントをトリガーできるようになります。クライアント・サーバー間の通信を保護し、サーバー側の検証を実装し、FXServerセキュリティConvarを設定する方法を学びます。

アンチチートチームは常にアンチチートの改善に努めていますが、時としてすり抜けてしまうことがあります。

このガイドでは、イベントを適切にロックダウンすることで、サーバーのセキュリティを向上させるために実行できる一般的な方法をいくつか紹介します。

FiveMにおけるネットワークイベントの理解

チートを使用すると、クライアントは任意のコンテキストでイベントをトリガーできるようになります。

ここでコンテキストと言うのは、クライアント->サーバー(TriggerServerEvent経由)、またはクライアントリソース->クライアントリソース(TriggerEvent経由)でイベントを実行できることを意味します。

Luaにおけるイベントハンドラーの正しい使用法

Luaでイベントを処理する場合、イベントがクライアントとサーバーのどちらから呼び出されるかに基づいて、イベントを正しく登録することが非常に重要です。

よくある間違いは、クライアントから呼び出されるべきではないサーバーイベントを登録したり、あるいはその逆を行ったりすることであり、これは重大なセキュリティ脆弱性につながる可能性があります。

AddEventHandler

イベントが同じコンテキスト内(クライアント・クライアント間、またはサーバー・サーバー間)でトリガーされるように設計されている場合は、AddEventHandlerを使用します。これにより、イベントがネットワーク化されず、反対側から呼び出すことができなくなります。

AddEventHandler("eventName", function(eventParam1, eventParam2)
    -- このコードは、イベントが同じコンテキスト内でトリガーされたときに実行されます。
end)

RegisterNetEvent

クライアントからサーバー、サーバーからクライアントのように、異なるコンテキスト間でイベントをトリガーする必要がある場合は、RegisterNetEventを使用します。

仕組みについて

注意:これは同じコンテキストからの実行をブロックしません。本質的にRegisterNetEventは単なるラッパーであり、以下の処理を実行しているだけです: RegisterNetEvent("eventName") AddEventHandler("eventName", function() ... end)
RegisterNetEvent("eventName", function(eventParam1, eventParam2)
    -- このコードは、イベントが異なるコンテキスト間でトリガーされたときに実行されます。
end)

この例はクライアント向けであり、クライアント側の他のものと同様に、万全ではなく、チートクライアントによって操作される可能性があります。

同じコンテキストからの実行をブロックしたい場合(サーバー間ネットイベントがクライアントによってトリガーされるのを防ぐなど)は、イベントを登録し、送信元ソースを検証する必要があります。

RegisterNetEvent("eventName", function(eventParam1, eventParam2)
    -- サーバーはサーバーからのイベントに対してネットID `65535`を送信します
    if source ~= 65535 then return end
end)

チェックと検証の追加

強力なアンチチートを構築した場合でも、サーバーイベントにチェックを追加することで、セキュリティが大幅に向上します。これはすべてを防ぐわけではありませんが、非常にお勧めするプラクティスです。以下にいくつかの良いヒントを紹介します。

  • プレイヤーの所持金:サーバー側で残高と取引を検証します。
  • プレイヤーのState bags:アクティブなセッションで状態データを検証します。
  • プレイヤーのインベントリアイテム:アイテム名と数量を確認します。
  • プレイヤーの位置:範囲と距離を検証します。
  • プレイヤーの経験値とレベル:ステータスがサーバー側で計算されていることを確認します。
  • プレイヤーの権限とロール:サーバー側のACLを検証します。

鉄則

プレイヤーが値を指定したり変更したりできないように、必ずサーバー側のメソッドを使用してすべての値を取得してください。クライアント側のチェックはUX(ユーザー体験)にとっても良いプラクティスですが、簡単にバイパスされてしまう可能性があることに注意してください。

これにより、ゲーム環境の整合性とセキュリティが保証されます。

一般的なセキュリティパターンの例

以下のすべての例は、何らかのフレームワーク(ESX、QB-Coreなど)を想定しています。

脆弱なセキュリティ(絶対にしないでください)

これはイベントの間違った処理方法を示すためのものであり、絶対に行わないでください。ユーザー独自の入力から直接アイテムを追加することは常に脆弱な設計です。必ずユーザー入力を検証してください。

RegisterNetEvent("job:givePlayerItem", function(item, count)
    local ply = FX.GetPlayerFromSource(source)
    -- ユーザー独自の入力から直接アイテムを追加することは安全ではありません!
    ply.addItem(item, count)
end)

強固なセキュリティ(推奨)

こちらは堅牢なセキュリティパターンです。サーバーがプレイヤーの状態と座標を追跡し、クライアントのトリガーだけに頼るのではなく、サーバー側のティックを使用してアクションを検証します。

-- ランダムな座標
local VALID_JOB_COORD = vector3(125.0, 111.1, 35.83)
local MAX_ITEM_COUNT = 10

-- ランダムな座標
local VALID_TURNIN_COORD = vector3(1888.0, 1254.1, 48.0)

local ITEM_NAME = 'log'

-- アクティブなジョブを持つプレイヤーのリスト
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

-- ジョブのティックを処理し、サーバー側でアイテム付与数をインクリメントする
CreateThread(function()
    while true do
        for src, data in pairs(activeJobs) do
            local ped = GetPlayerPed(src)
            -- 範囲内にいない場合、アイテムを付与したくない
            if isPedWithinRange(ped, VALID_JOB_COORD) then
                -- アイテムを与えるが、MAX_ITEM_COUNTに制限する
                data.itemCount = math.min(data.itemCount + 1, MAX_ITEM_COUNT)
            end
        end
        -- 1秒に1回ジョブのティックを処理する
        Wait(1000)
    end
end)

RegisterNetEvent("job:startJob", function()
    local ped = GetPlayerPed(source)
    -- 15ユニット以内にいる場合、彼らはジョブを実行している
    if isPedWithinRange(ped, VALID_JOB_COORD) then
        activeJobs[source] = {
            itemCount = 0,
        }
    end
end)

RegisterNetEvent("job:givePlayerItem", function()
    local ply = FX.GetPlayerFromSource(source)
    -- アクティブなジョブがない場合、これに到達するはずがありません!
    local jobData = activeJobs[source]
    if not jobData then return end

    local ped = GetPlayerPed(source)
    -- 納品座標の範囲内にいないため、変更を拒否します
    if not isPedWithinRange(ped, VALID_TURNIN_COORD) then return end

    -- 複数回トリガーできないようにアイテムデータをリセットします
    activeJobs[source] = nil

    -- サーバー側で検証されたアイテムをユーザーに追加します
    ply.addItem(ITEM_NAME, jobData.itemCount)
end)

サーバー所有者向けオプション (Convar)

何をしているかを正確に理解していない限り、以下の設定を変更すべきではないことに注意してください。Cfx.re / Adhesiveチームはチーターを防ぐために常に懸命に取り組んでいます。FXServer 8450ビルドバージョン以降では、これらの機能のほとんどがデフォルトで有効になっています。

  • sv_kick_players_cnl_timeout_sec:サーバーがプレイヤーをキックするまでのタイムアウト時間です(例:これが600の場合、CnL接続がない状態が10分間続いた後にキックします)。
  • sv_kick_players_cnl_update_rate_sec:CnLがプレイヤーリストを照会する頻度です。
  • sv_pure_verify_client_settings:クライアントのinfo.jsonへの定期的なリクエストを置き換えます。adhesiveとsvadhesiveの間に安全な接続を確立し、pureLevelやscripthookなどのいくつかのsv_settings構成を検証します。
  • sv_kick_players_cnl_consecutive_failures:キックするために、timeout_secを超えて連続して発生する必要があるエラー数です。デフォルトは2に設定されており、これはプレイヤーが10分間接続確認を失敗し、その次のチェックイン更新も逃した場合にキックされることを示します。これはフェイルセーフメカニズムとして機能します。
  • sv_authMaxVariance:分散(variance)とは、特定のプロバイダー('steam'、'ip'、'license'など)でユーザーのIDが変更される可能性の高さを示します。
  • sv_authMinTrust:信頼(trust)とは、悪意のあるクライアントによってユーザーのIDが偽装される可能性の低さを示します。
  • sv_filterRequestControl:設定可能なポリシーに基づいてREQUEST_CONTROL_EVENTルーティングをブロックするために使用されるコンソール変数です。
  • sv_disableClientReplays:これを有効にすると、チートオプションが発生する可能性を減らすことができます。これによりRockstar Editorが無効になりますのでご注意ください。

プレイヤー側の表示

これらのConvarが有効になっていると、プレイヤーは以下の理由でキックされる可能性が高くなります。 Connection to CNL timed out.

キックされたからといって、プレイヤーが自動的にグローバルバン(Global Ban)されるわけではありません。しかし、プレイヤーの信頼性を示す強力な指標となり、信頼レベルの評価に非常に役立ちます。

知っておくべき重要なこと

提供されたコードはコピー&ペーストでそのまま動作するように設計されていません。これらはサーバーで発生する可能性のあるいくつかの攻撃を防ぐためのヒントにすぎません。これには一定のプログラミング知識が必要です。さらにサポートが必要な場合は、いつでも私たちのDiscordに参加してください。