วิธีป้องกัน 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 =
sourceTarget มีจริง
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
Post a Comment