٢٠ مايو ٢٠٢٦
5 min read
ZeroTrust Team

أمن أحداثك: دليل أمان أحداث FiveM Lua

تسمح عمليات الغش للعملاء الخبيثين بتشغيل الأحداث في أي سياق. تعرف على كيفية تأمين الاتصال بين العميل والخادم، وتطبيق التحقق من جانب الخادم، وتكوين متغيرات أمان FXServer.

يحاول فريق مكافحة الغش دائمًا تحسين مكافحة الغش، ولكن في بعض الأحيان تتسلل بعض الأشياء.

في هذا الدليل، سنحاول المساعدة في تغطية بعض الممارسات الشائعة التي يمكنك القيام بها لجعل خادمك أكثر أمانًا من خلال قفل أحداثك بشكل صحيح.

فهم أحداث الشبكة في 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)
    -- سيرسل الخادم معرف الشبكة `65535` للأحداث القادمة من الخادم
    if source ~= 65535 then return end
end)

إضافة الفحوصات والتحقق

حتى إذا قمت ببناء نظام قوي لمكافحة الغش، فإن إضافة الفحوصات على أحداث الخادم تجعلها أكثر أمانًا بشكل كبير. هذه ممارسة موصى بها بشدة، على الرغم من أنها لا تمنع كل شيء. أدناه، نشارك بعض النصائح الجيدة.

  • أموال اللاعب: التحقق من الرصيد والمعاملات من جانب الخادم.
  • حقائب حالة اللاعب (State Bags): التحقق من بيانات الحالة في الجلسات النشطة.
  • عناصر مخزن اللاعب: التحقق من أسماء العناصر وأعدادها.
  • موقع اللاعب: التحقق من النطاقات والمسافات.
  • خبرة اللاعب ومستواه: ضمان حساب الإحصائيات من جانب الخادم.
  • أذونات اللاعب وأدواره: التحقق من قائمة التحكم في الوصول (ACL) من جانب الخادم.

قاعدة أساسية

تأكد من استرداد جميع القيم باستخدام طرق من جانب الخادم، وعدم السماح للاعبين بتزويد القيم أو تغييرها. يرجى ملاحظة أن فحوصات العميل يمكن أن تكون أيضًا ممارسة جيدة لتجربة المستخدم ولكن يمكن تجاوزها بسهولة.

يضمن ذلك سلامة وأمن بيئة اللعب الخاصة بك.

أمثلة على الأنماط الأمنية الشائعة

تفترض جميع الأمثلة أدناه وجود نوع من إطارات العمل (مثل 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
        -- معالجة تكتك الوظيفة مرة واحدة في الثانية
        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)

خيارات مالك الخادم (Convars)

يرجى ملاحظة أنه لا ينبغي تعديل الإعدادات التالية إلا إذا كنت تعرف بالضبط ما تفعله. يعمل فريق Cfx.re / Adhesive بجد دائمًا لمنع المخادعين. ستكون معظم هذه الميزات ممكّنة افتراضيًا مع إصدار بناء FXServer 8450 وما فوق.

  • sv_kick_players_cnl_timeout_sec: هذا هو المهلة التي سيقوم الخادم بعدها بطرد اللاعب (على سبيل المثال، إذا كان هذا 600، فسيتم طردهم بعد 10 دقائق من عدم وجود اتصال CnL).
  • sv_kick_players_cnl_update_rate_sec: هذا هو معدل تكرار استعلام CnL بقائمة اللاعبين.
  • sv_pure_verify_client_settings: يستبدل الطلب الدوري لـ info.json في العميل. ينشئ اتصالاً آمنًا بين adhesive و svadhesive ويتحقق من بعض إعدادات sv_settings مثل pureLevel و scripthook وتكوينات أخرى.
  • sv_kick_players_cnl_consecutive_failures: كم عدد الإخفاقات المتتالية المطلوبة بعد timeout_sec لطرد اللاعب. القيمة الافتراضية هي 2، مما يعني أنه إذا فشل لاعب في تسجيل الدخول لمدة 10 دقائق ثم فاته تحديث تسجيل الدخول التالي، فسيتم طرده. هذا بمثابة آلية أمان.
  • sv_authMaxVariance: التباين هو مدى احتمالية تغيير معرف المستخدم لموفر معين (مثل 'steam' أو 'ip' أو 'license').
  • sv_authMinTrust: الثقة هي مدى عدم احتمالية انتحال هوية المستخدم بواسطة عميل خبيث.
  • sv_filterRequestControl: متغير وحدة تحكم يُستخدم لحظر توجيه REQUEST_CONTROL_EVENT استنادًا إلى سياسة قابلة للتكوين.
  • sv_disableClientReplays: يهدف تمكين هذا إلى تقليل فرص خيارات الغش. يرجى ملاحظة أن هذا سيؤدي إلى تعطيل Rockstar Editor.

النتائج على اللاعب

سيؤدي تفعيل هذه المتغيرات على الأرجح إلى طرد اللاعب بالسبب التالي: Connection to CNL timed out.

لا يعني الطرد حظر اللاعب تلقائيًا على المستوى العالمي (Global Ban). ومع ذلك، فإنه يوفر مؤشرًا قويًا على موثوقية اللاعب، وهو أمر مفيد للغاية لتقييم الجدارة بالثقة.

من المهم معرفته

الأكواد المقدمة ليست مخصصة للعمل بطريقة النسخ واللصق المباشر. هذه مجرد نصائح لمنع بعض الإجراءات التي قد تحدث في الخادم. يتطلب ذلك بعض المعرفة البرمجية. يمكنك دائمًا الانضمام إلى Discord الخاص بنا للحصول على مساعدة إضافية.