วิธีป้องกัน TriggerServerEvent โดนโกง

วิธีป้องกัน TriggerServerEvent() โดนโกงที่สำคัญที่สุด คืออย่าเชื่อข้อมูลที่ Client ส่งเข้ามา และให้ Server เป็นผู้ตรวจสอบทุก Action ที่มีผลต่อเงิน ไอเทม รางวัล สิทธิ์ ตำแหน่ง และสถานะของผู้เล่น

TriggerServerEvent() มีหน้าที่ส่ง Network Event จาก Client ไปยัง Server เช่น

TriggerServerEvent(
    'shop:buy',
    'water',
    2
)

ฝั่ง Server รับด้วย

RegisterNetEvent(
    'shop:buy',
    function(itemName, amount)
        local src = source

        -- validation
    end
)

ปัญหาคือ Server ไม่ควรคิดว่า Event นี้จะถูกเรียกเฉพาะจาก Menu หรือ Gameplay Flow ที่เราเขียนไว้เท่านั้น

Cfx.re เตือนชัดเจนว่า Client ที่ถูกดัดแปลงสามารถพยายาม Trigger Client → Server Events ได้ ดังนั้นแนวคิดที่ถูกต้องคือ

Client
→ ส่ง Request

Server
→ ตรวจสอบ

Server
→ ตัดสินผล

Server
→ เปลี่ยนข้อมูลจริง

ไม่ใช่

Client
→ ส่งค่ามา

Server
→ เชื่อทั้งหมด

① TriggerServerEvent โดนโกงได้อย่างไร

สมมติ Script ปกติทำงานแบบนี้

Player รับงาน
↓
เดินทางไปจุดหมาย
↓
ทำภารกิจ
↓
กดส่งงาน
↓
TriggerServerEvent
↓
Server ให้เงิน

Developer มือใหม่อาจคิดว่า Player ต้องผ่านทุกขั้นตอนก่อน Event สุดท้ายจะถูกเรียก

แต่ Server ไม่ควรพึ่งสมมติฐานนี้

Client ที่ถูกดัดแปลงอาจพยายามเรียก Event สุดท้ายโดยตรงโดยไม่ผ่าน Flow ปกติ

ดังนั้น Server ต้องตรวจว่าเงื่อนไขทั้งหมดเกิดขึ้นจริงหรือไม่

② TriggerServerEvent เป็นเพียง Request

ให้จำประโยคนี้ไว้

TriggerServerEvent
=
Client Request

ไม่ใช่

TriggerServerEvent
=
Server Command ที่ต้องทำตาม

ตัวอย่าง Client ขอซื้อสินค้า

TriggerServerEvent(
    'shop:buy',
    'water',
    2
)

ความหมายที่ Server ควรตีความคือ

Player คนนี้กำลัง “ขอ” ซื้อ water จำนวน 2 ชิ้น

Server ยังต้องตรวจทุกอย่างก่อนอนุมัติ

③ Server ต้องเป็น Authority

ข้อมูลสำคัญควรให้ Server เป็น Source of Truth เช่น

Money
Inventory
Reward
Job
Grade
Permission
XP
Level
Mission State
Vehicle Ownership
Transaction State

Client มีหน้าที่

Input
UI
Animation
Gameplay Presentation
Request

แต่ไม่ควรเป็นผู้ตัดสินข้อมูลสำคัญเหล่านี้

④ ตัวอย่าง Event ที่อันตราย

Client

TriggerServerEvent(
    'money:add',
    1000000
)

Server

RegisterNetEvent(
    'money:add',
    function(amount)

        AddMoney(
            source,
            amount
        )

    end
)

ช่องโหว่คือ

amount

ถูกกำหนดโดย Client

ถ้า Client เปลี่ยนจาก

500

เป็น

1000000

Server ก็ทำตามทันที

นี่เป็น Pattern ที่ควรหลีกเลี่ยงอย่างเด็ดขาด

⑤ Reward ต้องคำนวณฝั่ง Server

แทนที่จะส่ง Reward

TriggerServerEvent(
    'delivery:complete',
    500
)

ให้ Client ส่งเพียง Action

TriggerServerEvent(
    'delivery:complete'
)

Server

RegisterNetEvent(
    'delivery:complete',
    function()

        local src = source

        -- validate

        local reward = 500

        AddMoney(
            src,
            reward
        )

    end
)

Reward 500 มาจาก Server

Client ไม่มีสิทธิ์กำหนดจำนวนเงิน

⑥ อย่าส่ง Price จาก Client

Shop ที่ไม่ดี

TriggerServerEvent(
    'shop:buy',
    'water',
    2,
    1
)

Server รับ

water
amount = 2
price = 1

แล้วคิดราคาตาม Client

นี่มีความเสี่ยง

ควรให้ Serverเก็บ Price

local Items = {
    water = {
        price = 10
    }
}

จากนั้น

local item =
    Items[itemName]

local totalPrice =
    item.price * amount

⑦ อย่าส่ง Money Balance จาก Client

ไม่ควรออกแบบ

TriggerServerEvent(
    'shop:buy',
    item,
    amount,
    clientMoney
)

แล้วตรวจ

clientMoney >= price

Server ต้องอ่าน Balance จากระบบ Economy ฝั่ง Serverเอง

เช่น Framework Player Data หรือ Server-side Data Store

⑧ อย่าส่ง Inventory Count เป็น Authority

Client อาจแสดง Inventory เพื่อ UI ได้

แต่ไม่ควรส่ง

I have 50 iron

แล้ว Serverเชื่อเพื่อ Craft Item

Server ควรอ่านจำนวน Item จาก Inventory System ฝั่ง Serverเอง

Flow ที่ถูกต้องคือ

Client ขอ Craft
↓
Server อ่าน Inventory
↓
ตรวจ Material
↓
Server Remove Material
↓
Server Give Item

⑨ อย่าให้ Client บอก Job ของตัวเอง

ตัวอย่างไม่ดี

TriggerServerEvent(
    'police:armory',
    'police'
)

Server

if job == 'police' then
    GiveWeapon(...)
end

Client เป็นคนส่ง Job

จึงไม่ควรเชื่อ

Server ควรอ่าน Job จาก Server-side Player Data

⑩ อย่าให้ Client บอก Grade

หลักเดียวกัน

ไม่ควร

TriggerServerEvent(
    'police:getWeapon',
    'police',
    10
)

แล้ว Server ใช้ Grade 10 จาก Client

ควร

Server Player Data
↓
job = police
grade = 10
↓
Permission Decision

⑪ อย่าให้ Client บอกว่าเป็น Admin

ตัวอย่างอันตราย

TriggerServerEvent(
    'admin:kick',
    target,
    true
)

โดย

true = isAdmin

Server ต้องตรวจ Admin Permission เอง เช่น

  • ACE

  • Framework Permission

  • Admin Resource

  • Server-side Role

Client Boolean ไม่ใช่ Permission

⑫ source คือหัวใจของ Server Event

เมื่อ Client Trigger Event

TriggerServerEvent(
    'shop:buy',
    'water',
    2
)

Server

RegisterNetEvent(
    'shop:buy',
    function(itemName, amount)

        local src = source

    end
)

source คือ Player Source ของ Client ที่ Trigger Event

จึงควรใช้ source ระบุ Actor ของ Request

ไม่จำเป็นต้องให้ Client ส่ง ID ของตัวเองมา

⑬ อย่าให้ Client ส่ง Actor ID เอง

ไม่ควร

TriggerServerEvent(
    'bank:withdraw',
    myServerId,
    5000
)

แล้ว Server ทำ

RemoveMoney(
    myServerId,
    5000
)

ควรใช้

local src = source

Actor ของ Event จึงมาจาก Network Context ที่ Serverได้รับ

⑭ Target Player ยังต้องตรวจ

บาง Event มี Player เป้าหมาย เช่น

give item
revive
kick
transfer money

Client อาจส่ง

targetId

ได้ แต่ Server ต้อง Validate ว่า

  • Target มีอยู่จริง

  • Action นี้อนุญาต

  • Actor มี Permission

  • Actor สามารถทำกับ Target นี้ได้หรือไม่

อย่าใช้ Target ID แบบไม่ตรวจ

⑮ ตรวจ Type ก่อน

ตัวอย่าง

RegisterNetEvent(
    'shop:buy',
    function(itemName, amount)

        local src = source

        if type(itemName) ~= 'string' then
            return
        end

        if type(amount) ~= 'number' then
            return
        end

    end
)

นี่ช่วย Reject Input รูปแบบผิดตั้งแต่ต้น

⑯ ตรวจ Range ต่อจาก Type

Number ที่ถูก Type อาจยังผิด

ตัวอย่าง

-1
0
999999999

จึงควรตรวจ

if amount < 1 then
    return
end

if amount > 100 then
    return
end

ค่า Maximum ต้องกำหนดตามระบบจริง

⑰ ตรวจ Integer เมื่อจำเป็น

ถ้า Item ต้องซื้อเป็นจำนวนเต็ม

if amount % 1 ~= 0 then
    return
end

ช่วย Reject

1.5
2.75

ในระบบที่ไม่รองรับ Fractional Amount

⑱ ตรวจ String Length

หาก Event รับข้อความจาก Client เช่น

name
label
message
reason

ควรจำกัดความยาว

ตัวอย่าง

if #message > 200 then
    return
end

ช่วยลด Input ผิดปกติและ Payload ที่ไม่จำเป็น

⑲ ใช้ Allowlist

สำหรับ Item

local Items = {
    water = {
        price = 10
    },

    bread = {
        price = 20
    }
}

หลังรับ

local item =
    Items[itemName]

if not item then
    return
end

Client จึงเลือกได้เฉพาะ Item ที่ Server อนุญาต

⑳ ใช้ Allowlist กับ Action

ถ้ามี Event ที่รับ Action เช่น

TriggerServerEvent(
    'garage:action',
    action
)

ควรจำกัด Action

local allowedActions = {
    store = true,
    retrieve = true
}

if not allowedActions[action] then
    return
end

อย่างไรก็ตาม ถ้าแต่ละ Action มี Security Rule ต่างกันมาก การแยก Event มักอ่านและ Audit ง่ายกว่า

㉑ อย่าสร้าง Generic Execute Event

ตัวอย่างที่ไม่ควรมี

RegisterNetEvent(
    'server:execute',
    function(action, data)
        -- ทำอะไรก็ได้
    end
)

แล้วใช้ Event เดียวสำหรับ

Give Money
Give Item
Set Job
Delete Vehicle
Kick Player

Event ที่กว้างมากทำให้

  • Validate ยาก

  • Permission ยาก

  • Audit ยาก

  • Attack Surface ใหญ่

ควรแยกตาม Responsibility

㉒ ตรวจ Player State

สมมติ Delivery Job ต้องเริ่มก่อนจบ

Server เก็บ

local activeJobs = {}

ตอน Start

activeJobs[src] = {
    started = true
}

ตอน Complete

local job =
    activeJobs[src]

if not job then
    return
end

ถ้า Player ไม่เคย Start Job ก็ไม่ได้ Reward

㉓ Server State ดีกว่า Client State อย่างไร

Client State อาจถูก Manipulate ได้

แต่ Server State เช่น

activeJobs[src]

อยู่ใน Server Runtime

ผู้เล่นไม่ได้เปลี่ยน Table นี้โดยตรงจาก Client

จึงเหมาะกับการเก็บ

  • Mission Progress

  • Cooldown

  • Reward Eligibility

  • Transaction Lock

  • Session

㉔ ตรวจลำดับของ Mission

Mission สามารถใช้ State Machine

IDLE
↓
STARTED
↓
COLLECTED
↓
READY
↓
COMPLETED

Event แต่ละตัวต้องทำงานเฉพาะ State ที่ถูกต้อง

ตัวอย่าง

if job.state ~= 'READY' then
    return
end

ก่อน Complete

ช่วยป้องกันการข้าม Stage

㉕ ตรวจ Position ฝั่ง Server

Cfx.re ยก Player Position เป็นหนึ่งในข้อมูลที่ควรตรวจผ่าน Server-side Methods สำหรับ Network Event

ตัวอย่าง

local ped =
    GetPlayerPed(src)

if ped == 0 then
    return
end

local coords =
    GetEntityCoords(ped)

แล้วตรวจ Distance จากจุด Action

㉖ ตัวอย่าง Distance Check

local SHOP_COORDS =
    vector3(
        25.7,
        -1345.2,
        29.5
    )

local ped =
    GetPlayerPed(src)

local coords =
    GetEntityCoords(ped)

if #(coords - SHOP_COORDS) > 10.0 then
    return
end

Player ต้องอยู่ในระยะที่สมเหตุสมผลก่อน Server ทำ Action

ค่า 10.0 เป็นเพียงตัวอย่าง ต้องปรับตาม Gameplay จริง

㉗ ทำไม Client Position Check ไม่พอ

Client อาจมี Code

ถ้าอยู่ไกล
→ ห้ามกด E

ช่วย UX ได้

แต่ Client ที่ถูกดัดแปลงอาจไม่ใช้ UI/Control Flow นั้นเลย

ดังนั้น

Client Distance Check
→ UX

Server Distance Check
→ Security / Authority

สำหรับ Action สำคัญ

㉘ ตรวจ Money ฝั่ง Server

Shop Example

local balance =
    GetPlayerMoney(src)

if balance < totalPrice then
    return
end

จากนั้น Server เป็นผู้หักเงิน

RemoveMoney(
    src,
    totalPrice
)

ไม่ให้ Client เป็นผู้บอกว่า “หักแล้ว”

㉙ ตรวจ Inventory ฝั่ง Server

Craft Example

Client:
ขอ Craft repairkit

Server:
มี iron ไหม?
มี plastic ไหม?
มีจำนวนครบไหม?
Inventory รับผลลัพธ์ได้ไหม?

จากนั้น Server

Remove Materials
↓
Add Crafted Item

ตาม Inventory API ของ Server

㉚ ตรวจ Permission ฝั่ง Server

Admin Event

RegisterNetEvent(
    'admin:action',
    function(target)

        local src = source

        if not IsAllowedAdmin(src) then
            return
        end

        -- action

    end
)

ฟังก์ชัน IsAllowedAdmin() ต้องเชื่อมกับ Permission System ที่ Server ใช้จริง

㉛ ตรวจ Job และ Role ฝั่ง Server

ตัวอย่าง Armory

Server Player Data
↓
job == police?
↓
onDuty?
↓
grade สูงพอ?
↓
ให้ Item

อย่าให้ Client ส่ง Job/Grade แล้วใช้โดยตรง

㉜ ตรวจ Level หรือ XP

หาก Feature ต้อง Level 20

ไม่ควร

TriggerServerEvent(
    'mission:start',
    20
)

แล้ว Serverคิดว่า 20 คือ Level จริง

Server ต้องอ่าน XP/Level จาก Server Player Data

㉝ Rate Limit TriggerServerEvent

แม้ Event ถูก Validate ถูกต้อง ผู้เล่นยังอาจ Spam Request

เช่น

shop:buy
100 ครั้ง/วินาที

Server ควรพิจารณา Rate Limit สำหรับ Event ที่มี Cost หรือ Value สูง

㉞ ตัวอย่าง Cooldown ง่าย ๆ

local cooldowns = {}

RegisterNetEvent(
    'job:complete',
    function()

        local src = source
        local now = os.time()

        local last =
            cooldowns[src] or 0

        if now - last < 5 then
            return
        end

        cooldowns[src] = now

        -- validation + logic

    end
)

นี่เป็นตัวอย่าง Concept เท่านั้น

ระบบจริงต้องเลือก Window ให้เหมาะกับ Gameplay

㉟ Rate Limit ควรทำก่อน Database Query

ถ้า Event สามารถ Reject ได้จาก Rate Limit หรือ Type Validation

ควรทำก่อน Operation ราคาแพง เช่น

SQL
HTTP
Large Loop
Entity Scan

Flow ที่ดีเช่น

Event
↓
Type Check
↓
Range Check
↓
Rate Limit
↓
State/Permission
↓
Database

ช่วยลด Abuse-induced Load

㊱ Cooldown ฝั่ง Client อย่างเดียวไม่พอ

แม้ Client มี

if cooldown then
    return
end

ก็เป็นเพียง UX/Normal Client Protection

ผู้โจมตีอาจพยายามเรียก Network Eventตรง ๆ

Server-side Cooldown จึงยังสำคัญ

㊲ ป้องกัน Double Reward

สมมติ Job State ยังอยู่ตอน Event สอง Request เข้ามาเกือบพร้อมกัน

ไม่ควรทำ

ตรวจ job
↓
Give Reward
↓
ค่อยลบ job

ถ้ามีโอกาส Request ซ้อน

ควรคิดเรื่อง State Lock/Consume Eligibility ให้เร็ว

ตัวอย่าง

local job =
    activeJobs[src]

if not job then
    return
end

activeJobs[src] = nil

GiveReward(
    src,
    job.reward
)

㊳ Race Condition ต้องระวัง

Race Condition คือ Request หลายตัวเข้าถึง State เดียวกันในช่วงใกล้กันจน Transaction ถูกทำซ้ำ

เช่น

Request A
→ reward available

Request B
→ reward available

A ให้เงิน
B ให้เงิน

ระบบ Economy สำคัญอาจต้องใช้

  • State Lock

  • Atomic Operation

  • Database Transaction

  • Unique Transaction ID

ตาม Complexity

㊴ ใช้ Database Transaction เมื่อเหมาะสม

ตัวอย่างการซื้อสินค้าที่ต้อง

หักเงิน
+
เพิ่ม Item
+
บันทึก Transaction

ถ้าระบบต้องการ Atomicity สูง ควรพิจารณา Transaction ตามความสามารถของ Database/Framework

เพื่อหลีกเลี่ยงสถานการณ์

หักเงินสำเร็จ
↓
เพิ่ม Item ล้มเหลว

หรือกลับกัน

㊵ Event Name ลับไม่ช่วยแทน Validation

อย่าคิดว่า Event

x_9df83_reward_secret

ปลอดภัยกว่า

job:complete

เพียงเพราะชื่อเดายาก

Security ที่ถูกต้องคือ

ต่อให้รู้ชื่อ Event
↓
ส่ง Request ปลอม
↓
Server Validation ไม่ผ่าน
↓
ไม่ได้ Reward

㊶ ไม่ต้องเปลี่ยน Event Name หนีคนโกง

การเปลี่ยนชื่อ Event อาจมีเหตุผลด้าน Refactor

แต่ไม่ควรเป็นวิธีหลักในการแก้ช่องโหว่

ถ้า Logic เดิมคือ

AddMoney(
    source,
    clientAmount
)

ต่อให้เปลี่ยน Event 100 ครั้ง Logic ก็ยังผิด

ต้องแก้ Trust Model

㊷ RegisterNetEvent เฉพาะ Event ที่ต้อง Network

ถ้ามี Server-only Event

economy:transactionCompleted

ที่ถูก Trigger จาก Server Resource อื่น

ควรใช้

AddEventHandler(
    'economy:transactionCompleted',
    function(...)
    end
)

ไม่จำเป็นต้อง RegisterNetEvent() หาก Client ไม่ควรเรียก

Cfx.re แนะนำแนวทางนี้โดยตรงเพื่อลดการเปิด Event ข้าม Context โดยไม่จำเป็น

㊸ Client-to-Server ใช้ RegisterNetEvent

เมื่อ Event ต้องรับ

Client
→
Server

จึงใช้

RegisterNetEvent(
    'shop:buy',
    function(...)
        -- validate
    end
)

อย่าหลีกเลี่ยง Network Event ด้วยวิธีแปลก ๆ

สิ่งสำคัญคือเปิดเฉพาะ Event ที่จำเป็นแล้ว Validate ให้ถูก

㊹ Event Payload ควรเล็ก

Cfx.re แนะนำให้ส่งข้อมูลผ่าน Eventให้น้อยที่สุด เพราะมี Serialization Overhead ตอนส่งและรับ

แทนส่ง

Player Data ทั้งหมด
Shop Config ทั้งหมด
Inventory ทั้งหมด

อาจส่งเพียง

shopId
itemName
amount

แล้ว Server ดึงข้อมูลที่เหลือเอง

㊺ ส่ง ID ดีกว่าส่ง Object ทั้งก้อน

ตัวอย่าง Client มี Shop Data

ไม่ควรจำเป็นต้องส่ง

TriggerServerEvent(
    'shop:buy',
    entireShopObject,
    entireItemObject
)

ใช้

TriggerServerEvent(
    'shop:buy',
    shopId,
    itemName,
    amount
)

แล้ว Server Lookup Config เอง

ช่วยทั้ง Security และ Payload Size

㊻ Payload ใหญ่มากทำอย่างไร

สำหรับ Client → Server Transaction ขนาดใหญ่ระดับหลาย KB ขึ้นไป Cfx.re ระบุว่าสามารถพิจารณา

TriggerLatentServerEvent()

เพราะ Latent Event ไม่ Block Network Channel แบบ Event ปกติในลักษณะเดียวกัน

แต่ Security Rule ยังเหมือนเดิม

Latent
≠
Trusted

Server ยังต้อง Validate

㊼ อย่ารับ SQL จาก Client

ห้ามออกแบบ

TriggerServerEvent(
    'database:execute',
    sql
)

แล้ว Server Execute String ที่ Client ส่ง

ควรรับ Business Action

garage:storeVehicle
bank:deposit
shop:buy

แล้ว Server สร้าง Query เอง

㊽ อย่ารับ File Path หรือ Server Command แบบ Generic

หลักเดียวกันกับระบบที่ Client ส่ง

file path
console command
resource name
arbitrary function name

แล้ว Server Execute แบบไม่จำกัด

Network Event ควรมี Interface ที่เฉพาะและ Allowlist ชัดเจน

ยิ่ง Event สามารถสั่ง Server ได้กว้างเท่าไร Security Review ก็ยิ่งยาก

㊾ Entity Event ต้องตรวจ Network ID

ถ้า Client ส่ง

TriggerServerEvent(
    'vehicle:store',
    networkId
)

Server ไม่ควรเห็น networkId แล้วทำ Action ทันที

ควรตรวจ

Entity มีจริง?
เป็น Vehicle?
Player เกี่ยวข้องกับ Vehicle?
Vehicle เป็นของ Player?
อยู่ใกล้ Garage?
Garage ถูกต้อง?

ตามระบบจริง

㊿ Vehicle Ownership ต้องตรวจ Database

Client อาจบอกว่า

รถนี้เป็นของฉัน

แต่ Server ควรตรวจ

Plate / Identifier
↓
Database
↓
Owner

หรือ Cache/Ownership System ที่เชื่อถือได้

โดยเฉพาะระบบ Store/Sell/Transfer Vehicle

51 Banking Event ต้องตรวจอะไร

ตัวอย่าง Deposit/Withdraw

Server ควรตรวจอย่างน้อย

Amount Type
Amount > 0
Maximum ตามระบบ
Player Account
Balance
Transaction State
Rate Limit
Target ถ้ามี

และ Server เป็นผู้เปลี่ยนยอดเงินจริง

Client มีหน้าที่เพียง Request และแสดง Result

52 Transfer Money ต้องระวังมากกว่า Withdraw

เพราะมีสอง Player

Sender
Target

Server ต้องตรวจ

  • Sender = source

  • Target มีจริง

  • Sender มีเงินพอ

  • Amount ถูกต้อง

  • Sender กับ Target ไม่ผิดเงื่อนไข

  • Transaction ทำครั้งเดียว

อย่าให้ Client ส่ง Sender ID แล้วใช้แทน source

53 Inventory Transfer ต้องตรวจทั้งสองฝั่ง

Player A ส่ง Item ให้ Player B

Server ต้องตรวจ

A มี Item จริงไหม
Amount ถูกไหม
B มีอยู่ไหม
B รับได้ไหม
Distance ต้องตรวจไหม
Item Transfer ได้ไหม

Client UI ไม่ใช่หลักฐานว่า Transaction ถูกต้อง

54 Admin Event ต้องตรวจทุกครั้ง

แม้ Admin Menu เปิดหลัง Server ตรวจ Permission แล้ว

ตอน Client ส่ง Action ใหม่ เช่น

admin:kick
admin:setjob
admin:giveitem

Server ควรตรวจ Permission อีกครั้ง

อย่าพึ่ง Client Cache เช่น

isAdmin = true

เป็น Authority ถาวร

55 Logging ช่วยจับ Event Abuse

Event สำคัญสามารถ Log เช่น

Source
Event
Target
Parameters ที่เหมาะสม
Validation Result
Reason
Timestamp

ช่วยตรวจ

  • Spam

  • Invalid Range

  • Invalid State

  • Suspicious Actions

  • Admin Abuse

  • Script Bug

แต่ไม่ควร Log Sensitive Information มากเกินจำเป็น

56 อย่า Ban เพียงเพราะ Validation Fail หนึ่งครั้ง

Invalid Request อาจเกิดจาก

  • Script Bug

  • Race Condition

  • Stale UI

  • Network Delay

  • Resource Restart

  • User Input ผิด

ดังนั้น Validation Failure หนึ่งครั้งไม่ควรถูกตีความว่าเป็น Cheat โดยอัตโนมัติ

Security Enforcement ควรใช้ Context, Pattern และ Threshold ที่เหมาะสม

57 Anti-Cheat ยังต้องมีไหม

Anti-cheat สามารถช่วยได้ แต่ไม่ได้แทน Secure Server Logic

แนวทางแข็งแรงกว่าคือ

Anti-Cheat
+
Server Validation
+
Permission
+
Rate Limit
+
State Validation
+
Logging

Cfx.re เองแนะนำให้เพิ่ม Server Checks แม้จะมี Anti-cheat อยู่แล้ว

58 Validation Order ที่เหมาะสม

สำหรับ Event ที่มี Cost สูง อาจเรียงประมาณ

① source / player valid
② Type
③ Range
④ Allowlist
⑤ Rate Limit
⑥ Player State
⑦ Permission
⑧ Position
⑨ Money / Inventory
⑩ Database / Transaction

หลักคือ

Reject Request ที่ผิดให้เร็ว ก่อนทำงานหนัก

ช่วยทั้ง Security และ Performance

59 Checklist ป้องกัน TriggerServerEvent โดนโกง

ก่อนเปิด Event Client → Server ให้ตรวจ

① Event จำเป็นต้องเปิด Network หรือไม่?
② Actor ใช้ source หรือยัง?
③ Client ส่งข้อมูลอะไร?
④ ข้อมูลใด Server หาเองได้?
⑤ ตรวจ Type หรือยัง?
⑥ ตรวจ Range หรือยัง?
⑦ ใช้ Allowlist หรือยัง?
⑧ Money ตรวจ Server-side หรือยัง?
⑨ Inventory ตรวจ Server-side หรือยัง?
⑩ Job/Grade ตรวจ Server-side หรือยัง?
⑪ Permission ตรวจ Server-side หรือยัง?
⑫ Position ต้องตรวจหรือไม่?
⑬ Mission State อยู่ Server หรือยัง?
⑭ Reward คำนวณ Server หรือยัง?
⑮ Price มาจาก Server หรือยัง?
⑯ มี Cooldown/Rate Limit หรือยัง?
⑰ ป้องกัน Duplicate Request หรือยัง?
⑱ Entity/Network ID ถูกตรวจหรือยัง?
⑲ Payload เล็กพอหรือไม่?
⑳ มี Logging สำหรับ Action สำคัญหรือไม่?

หาก Event เกี่ยวข้องกับ Economy ควรตรวจ Checklist นี้อย่างจริงจัง

⑥⓪ ตัวอย่าง Shop Event ที่ปลอดภัยกว่า

client.lua

TriggerServerEvent(
    'secure_shop:buy',
    'water',
    2
)

Client ส่งเฉพาะ

itemName
amount

server.lua

local Items = {
    water = {
        price = 10
    },

    bread = {
        price = 15
    }
}

local cooldowns = {}

RegisterNetEvent(
    'secure_shop:buy',
    function(itemName, amount)

        local src = source

        if type(itemName) ~= 'string' then
            return
        end

        if type(amount) ~= 'number' then
            return
        end

        if amount % 1 ~= 0 then
            return
        end

        if amount < 1
        or amount > 20 then
            return
        end

        local item =
            Items[itemName]

        if not item then
            return
        end

        local now =
            os.time()

        local last =
            cooldowns[src] or 0

        if now - last < 1 then
            return
        end

        cooldowns[src] = now

        local totalPrice =
            item.price * amount

        -- ตัวอย่างแนวคิด:
        --
        -- local money =
        --     FrameworkGetMoney(src)
        --
        -- if money < totalPrice then
        --     return
        -- end
        --
        -- if not InventoryCanCarry(
        --     src,
        --     itemName,
        --     amount
        -- ) then
        --     return
        -- end
        --
        -- FrameworkRemoveMoney(
        --     src,
        --     totalPrice
        -- )
        --
        -- InventoryAddItem(
        --     src,
        --     itemName,
        --     amount
        -- )

        print(
            ('Player %s requested %sx %s for %s')
            :format(
                src,
                amount,
                itemName,
                totalPrice
            )
        )

    end
)

AddEventHandler(
    'playerDropped',
    function()
        cooldowns[source] = nil
    end
)

สิ่งที่ Client ไม่ได้เป็นผู้กำหนด คือ

Price
Money Balance
Inventory State
Permission
Reward

Server เป็นผู้ตรวจและคำนวณเอง

นี่คือแนวคิดหลักของ Secure TriggerServerEvent() Design

ตัวอย่าง Job Reward ที่ควรใช้

client.lua

TriggerServerEvent(
    'secure_job:complete'
)

server.lua

local activeJobs = {}

RegisterNetEvent(
    'secure_job:start',
    function()

        local src = source

        if activeJobs[src] then
            return
        end

        activeJobs[src] = {
            startedAt = os.time(),
            reward = 500
        }

    end
)

RegisterNetEvent(
    'secure_job:complete',
    function()

        local src = source

        local job =
            activeJobs[src]

        if not job then
            return
        end

        if os.time() - job.startedAt < 30 then
            return
        end

        activeJobs[src] = nil

        local reward =
            job.reward

        -- Server-side AddMoney
        -- AddMoney(src, reward)

        print(
            ('Reward %s to player %s')
            :format(
                reward,
                src
            )
        )

    end
)

AddEventHandler(
    'playerDropped',
    function()
        activeJobs[source] = nil
    end
)

จุดสำคัญคือ

reward
startedAt
job state

ถูกเก็บฝั่ง Server

Client ส่งแค่

complete request

คำถามที่พบบ่อยเกี่ยวกับการป้องกัน TriggerServerEvent โดนโกง

TriggerServerEvent โดนโกงได้ไหม

Client ที่ถูกดัดแปลงสามารถพยายาม Trigger Network Event และเปลี่ยน Parameters ได้ ดังนั้น Server ต้อง Validate ทุก Action สำคัญ

ซ่อนชื่อ Event ป้องกันได้ไหม

ไม่ควรใช้เป็น Security หลัก Event ควรปลอดภัยแม้ผู้โจมตีรู้ชื่อ

RegisterNetEvent ทำให้ Event ปลอดภัยไหม

ไม่ RegisterNetEvent() ทำให้ Event ใช้ผ่าน Network ได้ แต่ไม่ได้ Validate Client Input ให้โดยอัตโนมัติ

Client ส่ง Reward ให้ Server ได้ไหม

ส่งได้ทางเทคนิค แต่ Server ไม่ควรใช้ค่าจาก Client เป็น Authority ของ Reward

Client ส่ง Price ได้ไหม

ไม่ควรใช้เป็นราคาจริง Serverควร Lookup Price เอง

Client ส่ง Job ได้ไหม

ส่งเพื่อ UI/Context บางอย่างได้ แต่ Permission Decision ต้องใช้ Server-side Player Data

source เชื่อถือได้กว่า Player ID ที่ Client ส่งไหม

สำหรับ Actor ของ Server Network Event ควรใช้ source ที่ Serverได้รับจาก Event Context

ต้องตรวจ Position ฝั่ง Server ไหม

ถ้า Position เป็นเงื่อนไขของ Reward, Shop, Job หรือ Action สำคัญ ควรตรวจ Server-side เมื่อระบบรองรับ

ต้อง Rate Limit ทุก Event ไหม

ไม่จำเป็นทุกตัว แต่ควรพิจารณากับ Event ที่ถูก Spam ได้หรือทำงานหนัก/มีมูลค่า

Cooldown Client พอไหม

ไม่ Server-side Cooldown ยังจำเป็นสำหรับ Security-sensitive Action

ESX ป้องกัน Event ให้เองไหม

ไม่ Developer ยังต้อง Validate Player Data และ Input ตาม Event

QBCore ป้องกัน Event ให้เองไหม

ไม่ หลักเดียวกัน

Qbox ป้องกัน Event ให้เองไหม

ไม่ Framework ไม่ได้เปลี่ยน Trust Boundary ของ Client → Server

Anti-cheat พอไหม

ไม่ควรพึ่ง Anti-cheat เพียงอย่างเดียว Server Event Security ยังต้องออกแบบให้ถูก

TriggerLatentServerEvent ปลอดภัยกว่าไหม

เป็นเรื่องการส่ง Payload ขนาดใหญ่และ Bandwidth ไม่ได้ทำให้ Client Data น่าเชื่อถือขึ้น

สรุปวิธีป้องกัน TriggerServerEvent โดนโกง

วิธีป้องกัน TriggerServerEvent() โดนโกงไม่ใช่การซ่อนชื่อ Event แต่คือทำให้ Server ไม่เชื่อ Client และตรวจทุก Request ก่อนทำ Action

จำ Flow นี้ให้ขึ้นใจ

Client
↓
TriggerServerEvent
↓
Server
↓
source
↓
Validate Type
↓
Validate Range
↓
Validate State
↓
Validate Position
↓
Validate Permission
↓
ตรวจ Money / Inventory
↓
Rate Limit
↓
Server Business Logic

ข้อมูลสำคัญ เช่น

Price
Reward
Money
Inventory
Job
Grade
Permission
XP
Mission Progress

ควรมาจาก Server-side Data ให้มากที่สุด

Client ควรส่งเพียงสิ่งที่ Server จำเป็นต้องรู้ เช่น

ฉันต้องการซื้อ Item นี้
ฉันต้องการเริ่มงาน
ฉันต้องการส่งงาน
ฉันต้องการเก็บรถคันนี้

จากนั้น Server เป็นผู้ตอบคำถามว่า

ทำได้ไหม?
ราคาเท่าไร?
ได้ Reward เท่าไร?
มีสิทธิ์ไหม?
อยู่ถูกที่ไหม?
State ถูกไหม?

สำหรับผู้ที่กำลังเขียน FiveM Script กับ comsiam ให้คิดว่า TriggerServerEvent() ทุกตัวคือ Input Endpoint ที่รับข้อมูลจากเครื่องผู้เล่น ไม่ต่างจาก API Endpoint ที่ต้อง Validate Input

และหลักที่สำคัญที่สุดจาก comsiam คือ Event ที่ปลอดภัยต้องยังปลอดภัยแม้คนโกงรู้ชื่อ Event รู้ Parameters และพยายามเรียก Event ซ้ำ เพราะสุดท้าย Server เป็นผู้ถือ Authority และปฏิเสธ Request ที่ไม่ถูกต้องได้

หัวข้อถัดไปจะเปลี่ยนจาก Event Security ไปสู่พื้นฐาน Networking อีกส่วนที่สำคัญ คือ FiveM Entity คืออะไร ก่อนต่อด้วย Network ID, Entity Ownership, State Bags และ OneSync

Comments

Popular posts from this blog

FiveM ยังน่าเล่นไหม? Enhanced เปลี่ยน FiveM แค่ไหน

FiveM คืออะไร เล่นอย่างไร สำหรับมือใหม่ เริ่มต้นตั้งแต่ศูนย์

วิธีตั้ง Admin Permission ด้วย add_ace และ add_principal FiveM แบบละเอียด