في أي وقت يمكن فيه للعميل تحفيز إجراء على الخادم، يكون هناك احتمال للاساءة. في حين أن RemoteEvents و RemoteFunctions هما أكثر طرق الهجوم شيوعًا هنا، فإن حالات أخرى مثل ProximityPrompt تكون عرضة أيضًا. تغطي هذه الفقرة كيفية تأمين هذه الحدود.
التحقق من المدخلات عن بُعد
يجب التحقق من كل قطعة من البيانات المرسلة من العميل بواسطة الخادم قبل استخدامها. ضع هذه الطبقات من التحقق استنادًا إلى ما تفعله الأوامر عن بُعد.
تحقق من السياق/الأذونات
هذا ضروري للأوامر عن بُعد التي تؤثر على حالة التجربة أو التقدم أو اللاعبين الآخرين. يجب على الخادم التحقق مما إذا كان لدى اللاعب الأذونات الضرورية لتقديم الطلب. على سبيل المثال، هل اللاعب قريب بما يكفي من المتجر لشراء شيء؟ هل لديهم مفتاح يتطلبه فتح باب؟ هل شخصيتهم على قيد الحياة؟
التحقق ضروري للأوامر عن بُعد التي:
- تمنح المكافآت أو تعدل تقدم اللاعب
- تؤثر على لاعبين آخرين أو حالة تجربة مشتركة
- تؤدي أحداثًا تتطلب مسافة أو توقيت أو أذونات (على سبيل المثال، مسافة المتجر)
تحقق من النوع والبنية
هجوم آخر قد يطلقه المخترقين هو إرسال أنواع صالحة من الناحية الفنية ولكن جعلها ضخمة للغاية أو طويلة أو مشوهة بطريقة أخرى. على سبيل المثال، إذا كان على الخادم تنفيذ عملية مكلفة على سلسلة نصية تتعلق بالطول، يمكن لمخترق إرسال سلسلة نصية ضخمة للغاية أو مشوهة لتعطيل الخادم.
هجوم شائع آخر قد يستخدمه المخترقون ينطوي على إرسال tables بدلًا من Instance. يمكن أن تحاكي الحمولة المعقدة ما سيكون مرجع كائن عادي.
على سبيل المثال، مع نظام متجر داخل التجربة حيث يتم تخزين بيانات العناصر مثل الأسعار في كائنات NumberValue، قد يت circumvent مخترق جميع الفحوصات الأخرى عن طريق القيام بما يلي:
LocalScript in StarterPlayerScriptslocal ReplicatedStorage = game:GetService("ReplicatedStorage")local itemDataFolder = ReplicatedStorage:WaitForChild("ItemData")local buyItemEvent = ReplicatedStorage:WaitForChild("BuyItemEvent")local payload = {Name = "Ultra Blade",ClassName = "Folder",Parent = itemDataFolder,Price = {Name = "Price",ClassName = "NumberValue",Value = 0, -- يمكن أيضًا استخدام القيم السلبية، مما يؤدي إلى منح العملة بدلاً من أخذها!},}-- إرسال الحمولة الضارة إلى الخادم (سيتم رفض هذا)print(buyItemEvent:InvokeServer(payload)) -- النتيجة "false Invalid item provided"-- إرسال عنصر حقيقي إلى الخادم (سيمر ذلك!)print(buyItemEvent:InvokeServer(itemDataFolder["Real Blade"])) -- النتيجة "true" و العملة المتبقية إذا نجح الشراء
Script in ServerScriptStorage
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local itemDataFolder = ReplicatedStorage:WaitForChild("ItemData")
local buyItemEvent = ReplicatedStorage:WaitForChild("BuyItemEvent")
local function buyItem(player, item)
-- تحقق مما إذا كان العنصر الممرر ليس مزييفًا وداخل مجلد ItemData
if typeof(item) ~= "Instance" or not item:IsDescendantOf(itemDataFolder) then
return false, "Invalid item provided"
end
-- يمكن للخادم بعد ذلك متابعة معالجة عملية الشراء بناءً على تدفق المثال أدناه
end
-- ربط "buyItem()" باستدعاء الوظيفة عن بُعد
buyItemEvent.OnServerInvoke = buyItem
تحقق من القيم
بالإضافة إلى التحقق من الأنواع والبيانات، يجب أن تتحقق من القيم المارة عبر RemoteEvents و RemoteFunctions، للتأكد من أنها صالحة ومنطقية في السياق المطلوب. هذا ضروري للأوامر عن بُعد التي تتعامل مع العملة، العناصر، المدخلات الرقمية، أو المحتوى الذي ينشئه المستخدم. يجب أن تتأكد من أن القيم ضمن الحدود المتوقعة (مثل أن تكون كمية الشراء أكبر من صفر) وأن المعرفات أو الأسماء المقدمة صالحة (مثل، أن تتوافق itemId مع عنصر حقيقي في لعبتك).
اعتبارات خاصة للقيم الرقمية: كل من inf و NaN هما نوعان صالحان من الأرقام، لكن يمكن أن يسببا مشكلات كبيرة إذا أرسل مخترق أحدهما ولم يتم التعامل معه بشكل صحيح. استخدم وظائف مثل هذه لاكتشافها ورفضها:
local function isNaN(n: number): boolean
-- NaN لا تساوي نفسها أبدًا
return n ~= n
end
local function isInf(n: number): boolean
-- يمكن أن يكون الرقم -inf أو +inf
return math.abs(n) == math.huge
end
خطر NaN
يمكن لمخترق إرسال NaN (ليس رقمًا) كحجة. NaN خطيرة بشكل فريد لأنها من نوع "رقم" لكنها تفشل في جميع المقارنات القياسية، مما يسمح لها بتجاوز الفحوصات المنطقية التي تبدو آمنة بشكل غامض. لننظر إلى نظام تداول ضعيف حيث يقدم اللاعب عرضاً.
-- كود خادم ضعيف
local function onCreateTradeOffer(player, offeredGold)
-- 1. تحقق من النوع: هذا يمر! typeof(NaN) هو "رقم".
if typeof(offeredGold) ~= "number" then
return "Invalid offer"
end
-- 2. التحقق من النطاق: يتم تجاوز هذا!
-- (NaN < 0) هو خطأ. (NaN > 1000000) هو أيضًا خطأ. لا يفعل التحقق شيئًا.
if offeredGold < 0 or offeredGold > 1000000 then
return "Offer out of range"
end
-- 3. تحقق من المخزون: يتم تجاوز هذا!
-- (NaN > player.Gold.Value) هو خطأ.
if offeredGold > player.Gold.Value then
return "Not enough gold"
end
-- ثغرة: تم إنشاء عرض تداول احتيالي مع ذهب NaN!
createTrade(player, {gold = offeredGold})
return "Trade offer created."
end
لقد أنشأ المخترق عرض تداول بنجاح دون أن يملك أي ذهب، لأن كل فحص فشل في الصمت. علاوة على ذلك، ستسمم هذه القيمة الوحيدة NaN أي نظام آخر يحاول استخدامها، وحتى تنتشر إلى لاعبين آخرين من خلال العرض.
فيما يلي بعض الأمثلة عالية المستوى التي تغطي تصميم آمن لميزات شائعة في اللعبة.
متجر داخل التجربة
اعتبر نظام متجر داخل التجربة مع واجهة مستخدم، على سبيل المثال، قائمة اختيار المنتج مع زر "شراء". عندما يتم ضغط الزر، يمكنك استدعاء RemoteFunction بين العميل والخادم لطلب الشراء. ومع ذلك، من المهم أن يؤكد الخادم، الذي يعد أكثر مديري التجربة موثوقية، أن المستخدم يملك ما يكفي من المال لشراء العنصر.

استهداف الأسلحة
تتطلب السيناريوهات القتالية اهتمامًا خاصًا في التحقق من القيم، خصوصًا عبر التأشير والتحقق من الضربات.
تخيل تجربة حيث يمكن للاعب إطلاق شعاع ليزر على لاعب آخر. بدلاً من أن يخبر العميل الخادم من يجب أن يتضرر، يجب عليه بدلاً من ذلك إخبار الخادم بموقع انطلاق اللقطة والجزء/الموقع الذي يعتقد أنه أصابه. يمكن للخادم بعد ذلك التحقق مما يلي:
- أن الموقف الذي يبلغ العميل عن الإطلاق منه قريب من شخصية اللاعب على الخادم. لاحظ أن الخادم والعميل سيختلفان قليلاً بسبب التأخير، لذا سيكون من الضروري أن يتم تطبيق بعض التسامح الإضافي.
- أن الموقف الذي يبلغ العميل عنه بأنه أصاب هو قريب بشكل معقول من موقع الجزء الذي يبلغ العميل أنه أصابه، على الخادم.
- لا توجد عوائق ثابتة بين الموقف الذي يبلغ العميل عن الإطلاق منه والموقف الذي يبلغ العميل عن الإطلاق تجاهه. يضمن هذا الفحص عدم محاولة عميل إطلاق النار عبر الجدران. لاحظ أنه يجب أن يتحقق هذا فقط من الهندسة الثابتة لتجنب رفض الطلقات الصالحة بسبب التأخير.
بالإضافة إلى ذلك، قد ترغب في تنفيذ مزيد من التحقق من الخادم على النحو التالي:
- تتبع متى أطلق اللاعب سلاحه آخر مرة والتحقق للتأكد من أنهم لا يطلقون النار بسرعة كبيرة.
- تتبع كمية الذخيرة لكل لاعب على الخادم وتأكيد أن اللاعب الذي يطلق النار لديه ما يكفي من الذخيرة لتنفيذ هجوم السلاح.
- إذا قمت بتنفيذ فرق أو نظام قتال "لاعبين ضد الروبوتات"، تحقق من أن الشخصية المضروبة هي عدو وليس زميلًا.
- تأكد من أن اللاعب الضحية على قيد الحياة.
- تخزين حالة السلاح واللاعب على الخادم وتأكيد أن اللاعب الذي يطلق النار ليس محجوزًا بفعل حالي مثل إعادة التحميل أو حالة مثل الجري.
تحديد معدل الدفع
كلما أمكن تحفيز منطق جانب الخادم بواسطة العميل (عبر الأوامر عن بُعد، أعراض تلامس، مطالب قريبة، ClickDetectors، إلخ)، هناك احتمال أن يتم استغلال هذا المنطق بواسطة المخترقين أو حتى بواسطة مستخدمين شرعيين. يحدد تحديد المعدل مدى تكرار هذه الإجراءات، مما يمنع الإساءة وتحميل النظام. في حين أنه من العملي (وأحيانًا الضروري) تنفيذ حد معدل على العميل، لا تعتمد أبدًا فقط على حد معدل جانب العميل.
بالنسبة لأي منطق جانب الخادم يمكن تحفيزه بواسطة عميل، يجب دائمًا مراعاة الحد الأقصى الذي يجب أن يعمل فيه هذا المنطق. يجب أن يكون لدى العديد من الإجراءات جانب الخادم تردد محدود لمنع الإساءة أو الفشل، مثل:
- استدعاءات واجهة برمجة تطبيقات Roblox: واجهات برمجة التطبيقات الخلفية مثل DataStoreService و BadgeService لديها حدود معدلة مدمجة ستجعل طلباتك تفشل إذا تم تجاوزها
- عمليات تحتاج إلى حسابات مكلفة: الإجراءات التي تستهلك موارد كبيرة من الخادم أو تؤثر على عملاء آخرين
- مثال بالخادم: استنساخ نماذج ضخمة من ServerStorage، حتى لو لم تتم إعادة تكوينها لعملاء
- مثال بالعميل: توجيه جميع العملاء المتصلين لتحديث واجهة المستخدم الرسومية الخاصة بهم بشكل متزامن
- آليات قابلة للاساءة: الإجراءات التي يمكن أن تكون مستغلة إذا تمت بشكل سريع، مثل منح حصانة، نقل اللاعبين، أو منح العملة
مثال على دلو الرموز
طريقة قوية وشائعة لتحديد المعدل هي خوارزمية دلو الرموز. تخيل أن كل مستخدم لديه دلو يمكنه الاحتفاظ بعدد معين من الرموز. لأداء إجراء، يجب على المستخدم إنفاق رمز. يتم إعادة ملء الدلو بالرموز الجديدة بمعدل ثابت. تسمح هذه الطريقة بنقاط انطلاق قصيرة من الإجراءات عند الحاجة، لكنها تمنع الرسائل غير الضرورية عن طريق فرض معدل متوسط عبر الوقت.
--!strict
-- الوحدة موجودة في ServerScriptService
type UserId = number
type Bucket = {
tokens: number,
last: number,
}
type TokenBucketT = {
capacity: number,
refillPerSecond: number,
buckets: { [UserId]: Bucket },
allow: (self: TokenBucketT, userId: UserId) -> boolean,
}
local TokenBucket = {}
TokenBucket.__index = TokenBucket
-- ينشئ حداً يسمح بـ `capacity` أحداث على الأكثر، ويعيد ملء بسرعة `capacity / windowSeconds` رموز في الثانية.
function TokenBucket.new(capacity: number, windowSeconds: number): TokenBucketT
assert(capacity >= 1, "الحد الأدنى يجب أن يكون >= 1")
assert(windowSeconds > 0, "windowSeconds يجب أن تكون > 0")
local self: TokenBucketT = {
capacity = capacity,
refillPerSecond = capacity / windowSeconds,
buckets = {},
}
return (setmetatable(self, TokenBucket) :: any) :: TokenBucketT
end
local function refill(b: Bucket, now: number, cap: number, rate: number)
local elapsed = now - b.last
if elapsed > 0 then
b.tokens = math.min(cap, b.tokens + elapsed * rate)
b.last = now
end
end
-- إرجاع false إذا كان المستخدم سيتجاوز حدّه
function TokenBucket:allow(userId: UserId): boolean
local now = time()
local b = self.buckets[userId]
if not b then
b = { tokens = self.capacity, last = now }
self.buckets[userId] = b
else
refill(b, now, self.capacity, self.refillPerSecond)
end
if b.tokens >= 1 then
b.tokens -= 1
return true
end
return false
end
return TokenBucket
لاستخدام الوحدة، عليك أولاً إنشاء مثيل جديد من المحدد بـ TokenBucket.new(capacity, windowSeconds). الـ capacity هو الحد الأقصى لعدد الطلبات التي يمكن أن يقدمها المستخدم في اندفاع سريع، والـ windowSeconds يحدد المدة التي يحتاجها لإعادة ملء جميع تلك الرموز. على سبيل المثال، TokenBucket.new(5, 10) ينشئ محددًا يسمح بنقاط اندفاع تصل إلى 5 طلبات ويعيد ملء رمز واحد كل ثانيتين (10 ثوانٍ / 5 رموز).
قبل تنفيذ منطق محمي، قم بإجراء مكالمة إلى :allow(userId)، رافضًا الإجراء إذا أرجعت false. من الجيد أيضًا مسح بيانات دلو المستخدم عندما يترك لتجنب تسرب الذاكرة.
توضح السكربت التالي كيف نحمي RemoteEvent المستخدم لنظام دردشة مخصص من أن يتم التلاعب به من قبل اللاعبين.
-- مثال للاستخدام في سكربت خادم
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerScriptService = game:GetService("ServerScriptService")
local TokenBucket = require(ServerScriptService.TokenBucket)
local ChatRemote = ReplicatedStorage:WaitForChild("ChatRemote") :: RemoteEvent
-- 5 رسائل في 10 ثوانٍ (الحد 5، يعيد الملء بمعدل 0.5 رمز/ثانية)
local chatLimiter = TokenBucket.new(5, 10)
ChatRemote.OnServerEvent:Connect(function(player: Player, message: string)
if not chatLimiter:allow(player.UserId) then
-- سريع جدًا: تجاهل الطلب أو أرسل تحذيرًا من الرسائل غير المرغوب فيها
return
end
-- معالجة الرسالة (بعد التحقق من المصداقية الأخرى)
broadcastMessage(player, message)
end)
-- تنظيف لتجنب تسرب الذاكرة
Players.PlayerRemoving:Connect(function(p: Player)
chatLimiter.buckets[p.UserId] = nil
end)
تأمين الكائنات المحفزة من العميل
ProximityPrompts و ClickDetectors و DragDetectors ليست أوامر عن بُعد، لكن يمكن للمخترق تحفيز أحداثها من أي مسافة، في أي وقت، وغالبًا ما يتجاهل خصائص مثل Enabled أو MaxActivationDistance. يجب تأمين هذه الكائنات بنفس الصرامة كما هو الحال في الأوامر عن بُعد.
ProximityPrompt
- يمكن أن يقوم المخترق بإطلاق أي حدث حتى لو كانت Enabled مزيفة على الخادم.
- يمكن تغيير Enabled و MaxActivationDistance و RequiresLineOfSight بصمت من العميل، لذا يمكن لأي ProximityPrompt تم تكراره رؤيتها والت interacted معها من أي موقع.
- سيقبل الخادم أحداث الضغط (مثل PromptButtonHoldBegan) حتى لو كانت HoldDuration المحايدة على الخادم صفر. يمكن للعميل التلاعب بـ HoldDuration لتنفيذ التفاعلات بسرعة غير طبيعية.
- فقط حدث Triggered له فحص المسافة على جانب الخادم. ليس لدى الأحداث الأخرى (مثل PromptButtonHoldBegan و TriggerEnded) أي فحص للمسافة، ويمكن أن يتم إرسالها بشكل تعسفي بواسطة أي عميل.
ClickDetector
- لا توجد أي فحوصات على الخادم لأي أحداث.
- يمكن للمخترقين تكرار أي حدث من أي مسافة، حتى لو كانت ClickDetector معطلة (MaxActivationDistance من 0 أو DragDetector.Enabled مزيف).
- يمكن تكرار الأحداث حتى لو لم يتم إرفاق ClickDetector بشيء قابل للنقر.
DragDetector
- بالنسبة للأحداث الموروثة من ClickDetector، لا توجد أي فحوصات.
- بالنسبة لأحداث السحب، يتم التحقق من خاصيتي Enabled و PermissionPolicy واحترامهما من قبل الخادم. جميع الخصائص الأخرى لا يتم فحصها.
عندما يطلق أحد هذه الأحداث، يجب على سكربت الخادم الخاص بك إجراء التحقق الخاص به قبل اتخاذ أي إجراء:
- تحقق مما إذا كانت مفعلّة: تأكد من أن العنصر مصمم ليكون مفعلًا عن طريق التحقق من خصائصه (مثل Enabled و HoldDuration و RunLocally، إلخ) على الخادم. هذه الخطوة ليست ضرورية إذا كان العنصر مفعلًا دائمًا.
- تحقق من حالة اللاعب: تأكد من أن اللاعب يمكنه إجراء الإجراء، نظرًا لوضع اللاعب الحالي. هل شخصية اللاعب في العالم؟ هل هي قريبة بما يكفي من الكائن؟ هل يجب أن يكون اللاعب على قيد الحياة للتفاعل مع هذا الكائن؟ تحقق من أي حالة أخرى للاعب يصونها تجربتك على الخادم حسب الحاجة.
- تطبيق تحديد المعدل: كما هو مغطى في تحديد المعدل، قدّر فترة التهدئة على العميل enforced حدود المعدل على الخادم لمنع هذه الأحداث من أن تكون مزعجة ومسيئة. بالنسبة للتفاعلات التي من المتوقع أن تستغرق حدًا أدنى من الوقت، تأكد من أن العملاء لا يكملونها بسرعة كبيرة جدًا.
ملاحظة عن ملكية الشبكة: بشكل افتراضي، إذا كانت أي من هذه الكائنات هي أطفال من أجزاء غير مثبَّتة أو من مجموعة غير مثبَّتة، يمكن للمخترق الحصول على ملكية الشبكة للأجزاء الأبوية وتحريكها مباشرة نحو شخصيته، متجاوزًا فحوصات المسافة. بالنسبة للإجراءات الحرجة، استخدم أجزاء مثبتة أو واجهة برمجة الشبكة مع التحقق الضروري من جانب الخادم.
RemoteEvents و RemoteFunctions
حدد من نطاق وتأثير RemoteFunctions و RemoteEvents. كن حذرًا جدًا عند تحميل أي أصول ديناميكيًا (حتى القوام أو الأصوات) أو تشغيل الشيفرة الخاصة بالتجربة (خصوصًا عبر require) استنادًا إلى معلمات وظائف/أحداث عن بُعد. تجنب تنفيذ الأوامر عن بُعد التي تسمح للعميل بتحديد مسار تعسفي أو مرجع كائن للخادم لحذفه أو تعديله، حتى لو بدت هذه التعديلات تافهة. يمكن أن يتم ربط الأوامر عن بُعد التي يمكن أن تغير حالة الكائنات التعسفية أو لها تأثير كبير على شجرة DataModel غالبًا بأخطاء أخرى لتؤثر بشدة على الحالة العامة أو المنطق. تحقق من نوع معلمات الكائنات، ولكن أيضًا من الفئة والموقع المتوقع أو الهيكل ضمن DataModel.
التواصل من عميل إلى عميل
بعض الأوامر عن بُعد مصممة لتتيح لعميل واحد تحفيز التأثيرات على عملاء آخرين. يحدث هذا عندما يطلق عميل أمرًا عن بُعد إلى الخادم، والذي يستخدم بعد ذلك RemoteEvent:FireAllClients() أو RemoteEvent:FireClient() أو طرق مماثلة لنقل المعلومات. يعتبر هذا النمط خطيرًا إذا لم يتم تأمينه بشكل صحيح - يجب أن يكون الخادم حارسًا، وليس مجرد ناقل.
تخيل سيناريو حيث يمكن للاعبين إلقاء تعويذة صاعقة. عند الإلقاء، يرى اللاعبون القريبون الوميض، ويسمعون الصوت، ويشعرون بهزة الكاميرا.
قد يبدو سكربت الخادم الضعيف كالتالي:
كود خادم ضعيف (سكربت في ServerScriptStorage)
local castLightningEvent = game.ReplicatedStorage.CastLightning
-- هذا السكربت الخادم ينقل الرسالة للجميع دون أي تحقق
castLightningEvent.OnServerEvent:Connect(function(player, strikePosition)
castLightningEvent:FireAllClients(strikePosition)
end)
كود تأثير العميل
local castLightningEvent = game.ReplicatedStorage.CastLightning
-- أنشئ تأثيرات بصرية وصوتية على العميل
local function createLightningEffect(strikePosition)
-- كود يهز الكاميرا
-- كود لتشغيل صوت صاعقة عالية
-- كود لإنشاء صاعقة بصرية
print("Lightning strikes at: " .. tostring(strikePosition))
end
-- عندما يبث الخادم الحدث، يقوم هذا العميل بتشغيل التأثير
castLightningEvent.OnClientEvent:Connect(createLightningEffect)
يمكن لمخترق استغلال هذا النظام الضعيف من خلال:
- البريد المزعج: استدعاء الأمر عن بُعد مئات المرات في الثانية، مما يتسبب في تأثيرات مستمرة تجعل التجربة غير قابلة للعب
- بيانات غير صحيحة: إرسال nil أو NaN أو أنواع خاطئة تتسبب في أخطاء في السكربت لدى جميع العملاء
- استخدام غير مصرح به: إلقاء تعويذات لم يتم فتحها أو ليس لديهم موارد لذلك
يجب أن يكون الخادم حارسًا، وليس ناقلًا. قبل البث، يجب التحقق من الطلب وتطبيق تحديد المعدل لكل لاعب. إليك مثال عن بعض الطرق التي يمكن استخدامها للتحقق من الطلب.
كود خادم آمن
local castLightningEvent = game.ReplicatedStorage.CastLightning
local playerCooldowns = {}
local COOLDOWN_TIME = 3
castLightningEvent.OnServerEvent:Connect(function(player, strikePosition)
-- 1. تحقق من النوع
if typeof(strikePosition) ~= "Vector3" then
return -- نوع غير صالح، ارفض بهدوء
end
-- 2. تحقق من NaN (NaN ~= NaN هو الوسيلة الوحيدة لاكتشافه)
if strikePosition.X ~= strikePosition.X then
return -- يحتوي على NaN، ارفض
end
-- 3. مثال على تحديد المعدل
local lastCast = playerCooldowns[player] or 0
if tick() - lastCast < COOLDOWN_TIME then
return -- لا يزال في فترة التهدئة
end
-- 4. مثال على تحقق الأذونات
if not player:GetAttribute("HasLightningSpell") then
return -- اللاعب ليس لديه هذه التعويذة
end
-- 5. مثال على تحقق النطاق
local character = player.Character
local humanoidRootPart = character and character:FindFirstChild("HumanoidRootPart")
if not humanoidRootPart then
return
end
local distance = (humanoidRootPart.Position - strikePosition).Magnitude
if distance > 100 then
return -- خارج النطاق
end
-- اجتاز الجميع - آمن للبث
playerCooldowns[player] = tick()
castLightningEvent:FireAllClients(player, strikePosition)
end)
من خلال تنفيذ هذه الفحوصات، يصبح من الصعب على المخترق تدمير التجربة لبقية اللاعبين. يعمل الخادم كحاجز واقٍ، مما يضمن أن الإجراءات الصحيحة والمصرح بها فقط هي التي يتم بثها للعملاء الآخرين.
اعتبارات خاصة
تتضمن الأمور التالية بعض الحالات الإضافية التي يجب أن تأخذها في الاعتبار والتي قد تتطلب معالجة متخصصة.
تلاعب متاجر البيانات
في التجارب التي تستخدم DataStoreService لحفظ بيانات اللاعبين، يمكن أن يستغل المخترقون البيانات غير الصحيحة، وظروف السباق لتلف البيانات أو تكرار العناصر في متجر البيانات. هذا مقلق بشكل خاص في التجارب التي تتضمن تداول العناصر أو الأسواق أو أنظمة العملات.
تأكد من أن أي إجراءات يتم تنفيذها عبر RemoteEvent أو RemoteFunction التي تؤثر على بيانات اللاعب مع إدخال العميل يتم تطهيرها استنادًا إلى ما يلي:
- قيم الكائن لا يمكن تسلسلها إلى متجر البيانات وستفشل. استخدم التحقق من النوع لمنع ذلك.
- متاجر البيانات لديها حدود للبيانات. يجب أن يتم فحص أو تحديد سلسلة النصوص من طول عشوائي لتجنب ذلك، بجانب التأكد من عدم إضافة مفاتيح غير محدودة عشوائية إلى الجدوال بواسطة العميل.
- لا يمكن لمؤشرات الجدول أن تكون NaN أو nil. يجب التحقق من جميع المؤشرات الموصولة من قبل العميل والتأكد من أن جميع المؤشرات صالحة.
- متاجر البيانات يمكنها قبول فقط رموز UTF-8 صالحة، لذا يجب أن تقوم بتنظيف جميع السلاسل النصية المقدمة من العميل عبر utf8.len() للتأكد من أنها صالحة. سيعيد utf8.len() طول سلسلة، معاملة حروف اليونيكود كحرف واحد. إذا تمت مقابلة حرف UTF-8 غير صالح، سيعيد nil وموقع الحرف غير الصالح. لاحظ أن سلاسل UTF-8 غير الصالحة يمكن أيضًا أن تكون موجودة في الجداول كمفاتيح وقيم.
- استغلال الأرقام اللانهائية/NaN - يمكن أن يرسل المخترقون قيمًا مثل -1/0، 0/0، أو math.huge للتسبب في وضع عملة لانهائية أو كسر الحسابات. تحقق دائمًا باستخدام الطرق الموضحة في خطر NaN.
يمكن أن تحدث ظروف السباق عندما يقوم اللاعبون بالتلاعب بالتوقيت بين العمليات لتكرار العناصر أو تلف البيانات.
مثال على الاستغلال في التجارة: يبدأ لاعب تجارة، ويرسل عنصره إلى لاعب آخر، ثم يغادر اللعبة على الفور. إذا اكتملت التجارة لكن حفظ بياناتهم في متجر البيانات يفشل بسبب بيانات غير صحيحة، فإنهم يعيدون الانضمام بعناصرهم الأصلية بينما يحتفظ اللاعب الآخر بالعناصر المتداولة - مما يؤدي إلى التكرار.
استراتيجيات الوقاية:
- تحقق من جميع البيانات قبل أي عمليات تجارية
- استخدم أنماط مثل المعاملات حيث يتم التحقق من بيانات جميع اللاعبين قبل الالتزام بأي تغييرات
- تنفيذ معالجة الأخطاء الصحيحة التي تعيد جميع التغييرات إذا فشل أي جزء
MarketplaceService
للتفاعلات التي تنطوي على MarketplaceService، مثل التمريرات أو منتجات المطورين، يجب أن يحدث جميع التحقق من عمليات الشراء ومنح العناصر على الخادم. بشكل خاص، استخدم استدعاء ProcessReceipt للتحقق من إيصالات الشراء بشكل آمن. لا تثق في إشارة من جانب العميل، مثل PromptProductPurchaseFinished، لتأكيد عملية الشراء بدون تحقق من الخادم، حيث يمكن التلاعب بها. تأكد من أن وظيفة ProcessReceipt الخاصة بك تتحقق بدقة من تفاصيل الإيصال ولا تمنح العناصر أو العملة إلا بعد تأكيد عملية الشراء الشرعية مع خوادم Roblox، كن مستعدًا أيضًا للتعامل مع الحالات التي تم منح فيها منتج بالفعل من أجل إيصال معين.