Mengamankan batasan klien-server

*Konten ini diterjemahkan menggunakan AI (Beta) dan mungkin mengandung kesalahan. Untuk melihat halaman ini dalam bahasa Inggris, klik di sini.

Setiap kali klien dapat memicu tindakan di server, ada potensi untuk penyalahgunaan. Meskipun RemoteEvents dan RemoteFunctions adalah vektor serangan yang paling umum, contoh lain seperti ProximityPrompt juga rentan. Bagian ini membahas cara mengamankan batas ini.

Memvalidasi input jarak jauh

Setiap data yang dikirim dari klien harus divalidasi oleh server sebelum digunakan. Terapkan lapisan validasi ini berdasarkan apa yang dilakukan remote.

Validasi konteks/izin

Ini diperlukan untuk remote yang mempengaruhi status pengalaman, kemajuan, atau pemain lain. Server harus memverifikasi apakah pemain memiliki izin yang diperlukan untuk membuat permintaan. Misalnya, apakah pemain cukup dekat dengan toko untuk membeli sesuatu? Apakah mereka memiliki kunci yang diperlukan untuk membuka pintu? Apakah karakter mereka masih hidup?

Validasi diperlukan untuk remote yang:

  • Memberikan hadiah atau memodifikasi kemajuan pemain
  • Mempengaruhi pemain lain atau status pengalaman bersama
  • Melakukan tindakan dengan persyaratan jarak, waktu, atau izin (misalnya, jarak toko)

Validasi tipe dan struktur

Serangan lain yang mungkin diluncurkan oleh penipu adalah mengirim tipe yang secara teknis valid tetapi membuatnya sangat besar, panjang, atau sebaliknya cacat. Misalnya, jika server harus melakukan operasi mahal pada string yang skala dengan panjang, seorang penipu bisa mengirim string yang sangat besar atau cacat untuk memperlambat server.

Serangan umum lainnya yang mungkin digunakan penipu melibatkan pengiriman tables sebagai pengganti Instance. Payload yang kompleks dapat meniru apa yang akan menjadi referensi objek yang biasa.

Misalnya, diberikan dengan sistem toko dalam pengalaman di mana data item seperti harga disimpan dalam objek NumberValue, seorang penipu dapat menghindari semua pemeriksaan lain dengan melakukan hal berikut:

LocalScript in StarterPlayerScripts

local 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, -- Nilai negatif juga dapat digunakan, menghasilkan pemberian mata uang alih-alih mengambilnya!
},
}
-- Kirim payload berbahaya ke server (ini akan ditolak)
print(buyItemEvent:InvokeServer(payload)) -- Mengeluarkan "false Invalid item provided"
-- Kirim item yang valid ke server (ini akan diterima!)
print(buyItemEvent:InvokeServer(itemDatafolder["Real Blade"])) -- Mengeluarkan "true" dan sisa mata uang jika pembelian berhasil
Script in ServerScriptStorage

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local itemDataFolder = ReplicatedStorage:WaitForChild("ItemData")
local buyItemEvent = ReplicatedStorage:WaitForChild("BuyItemEvent")
local function buyItem(player, item)
-- Periksa apakah item yang diteruskan tidak dipalsukan dan ada di folder ItemData
if typeof(item) ~= "Instance" or not item:IsDescendantOf(itemDataFolder) then
return false, "Invalid item provided"
end
-- Server kemudian dapat melanjutkan untuk memproses pembelian berdasarkan alur contoh di bawah ini
end
-- Sambungkan "buyItem()" ke callback fungsi jarak jauh
buyItemEvent.OnServerInvoke = buyItem

Validasi nilai

Selain memvalidasi tipe dan data, Anda harus memvalidasi nilai yang diteruskan melalui RemoteEvents dan RemoteFunctions, memastikan mereka valid dan logis dalam konteks yang diminta. Ini penting untuk remote yang menangani mata uang, item, input numerik, atau konten yang dibuat pengguna. Anda harus memastikan nilai berada dalam batas yang diharapkan (misalnya, jumlah pembelian lebih besar dari nol) dan bahwa ID atau nama yang diberikan valid (misalnya, itemId sesuai dengan item nyata di game Anda).

Pertimbangan khusus untuk nilai numerik: Baik inf maupun NaN adalah tipe nomor yang valid, tetapi keduanya dapat menyebabkan masalah besar jika penipu mengirimkannya dan tidak ditangani dengan benar. Gunakan fungsi seperti ini untuk mendeteksi dan menolak mereka:


local function isNaN(n: number): boolean
-- NaN tidak pernah sama dengan dirinya sendiri
return n ~= n
end
local function isInf(n: number): boolean
-- Nomor bisa jadi -inf atau +inf
return math.abs(n) == math.huge
end

Bahaya NaN

Seorang penipu dapat mengirim NaN (Not a Number) sebagai argumen. NaN sangat berbahaya karena ini adalah tipe "number" tetapi gagal semua perbandingan standar, memungkinkan untuk secara halus melewati pemeriksaan logis yang tampak aman. Mari kita lihat sistem perdagangan yang rentan di mana seorang pemain membuat tawaran.


-- KODE SERVER YANG RENTAN
local function onCreateTradeOffer(player, offeredGold)
-- 1. PERIKSA TIPE: Ini berhasil! typeof(NaN) adalah "number".
if typeof(offeredGold) ~= "number" then
return "Invalid offer"
end
-- 2. PERIKSA RENTANG: Ini dilewati!
-- (NaN < 0) adalah false. (NaN > 1000000) juga false. Pemeriksaan tidak ada artinya.
if offeredGold < 0 or offeredGold > 1000000 then
return "Offer out of range"
end
-- 3. PERIKSA INVENTARIS: Ini dilewati!
-- (NaN > player.Gold.Value) adalah false.
if offeredGold > player.Gold.Value then
return "Not enough gold"
end
-- KERENTANAN: Tawaran perdagangan yang curang dengan NaN gold telah dibuat!
createTrade(player, {gold = offeredGold})
return "Trade offer created."
end

Seorang penipu berhasil membuat tawaran perdagangan tanpa memiliki emas, karena setiap pemeriksaan gagal secara diam-diam. Selain itu, nilai NaN ini akan meracuni sistem lain yang mencoba menggunakannya, dan bahkan menyebar ke pemain lain melalui tawaran.

Berikut adalah beberapa contoh tingkat tinggi yang mencakup desain aman dari fitur umum dalam permainan.

Toko dalam pengalaman

Pertimbangkan sistem toko dalam pengalaman dengan antarmuka pengguna, misalnya menu pemilihan produk dengan tombol "Beli". Ketika tombol ditekan, Anda dapat memanggil RemoteFunction antara klien dan server untuk meminta pembelian. Namun, penting bahwa server, manajer pengalaman yang paling andal, mengonfirmasi bahwa pengguna memiliki cukup uang untuk membeli item tersebut.

Contoh alur pembelian dari klien ke server melalui RemoteEvent
Contoh alur pembelian dari klien ke server melalui RemoteFunction

Penargetan senjata

Skenario pertarungan memerlukan perhatian khusus dalam memvalidasi nilai, terutama melalui penargetan dan validasi hit.

Bayangkan pengalaman di mana seorang pemain dapat menembakkan laser ke pemain lain. Alih-alih klien memberi tahu server siapa yang harus dirugikan, seharusnya memberi tahu server posisi asal tembakan dan bagian/posisi yang dia kira telah mengenai. Server kemudian dapat memvalidasi berikut:

  • Posisi yang dilaporkan klien saat menembak berada dekat karakter pemain di server. Perhatikan bahwa server dan klien akan sedikit berbeda karena latensi, jadi toleransi tambahan perlu diterapkan.
  • Posisi yang dilaporkan klien sebagai mengenai cukup dekat dengan posisi bagian yang dilaporkan klien sebagai mengenai, di server.
  • Tidak ada hambatan statis antara posisi di mana klien melaporkan menembak dan posisi yang dilaporkan klien sebagai target. Pemeriksaan ini memastikan klien tidak mencoba menembak melalui dinding. Perhatikan bahwa ini hanya harus memeriksa geometri statis untuk menghindari tembakan yang valid ditolak karena latensi.

Selain itu, Anda mungkin ingin menerapkan validasi lebih lanjut di sisi server sebagai berikut:

  • Lacak kapan pemain terakhir menembakkan senjata mereka dan validasi untuk memastikan mereka tidak menembak terlalu cepat.
  • Lacak jumlah amunisi setiap pemain di server dan konfirmasi bahwa pemain yang menembak memiliki cukup amunisi untuk melakukan serangan senjata.
  • Jika Anda telah mengimplementasikan tim atau sistem pertarungan "pemain melawan bot", konfirmasi bahwa karakter yang terkena adalah musuh, bukan rekan satu tim.
  • Konfirmasi bahwa pemain yang terkena masih hidup.
  • Simpan status senjata dan pemain di server dan konfirmasi bahwa pemain yang menembak tidak terhalang oleh aksi saat ini seperti memuat ulang atau status seperti berlari.

Pembatasan laju

Setiap kali logika sisi server dapat dipicu oleh klien (melalui remote, acara Touched, prompt kedekatan, ClickDetectors, dll.), ada kemungkinan bahwa logika ini dapat dihujani oleh penipu atau bahkan pengguna sah. Pembatasan laju mengontrol seberapa sering tindakan ini dapat dilakukan, mencegah penyalahgunaan dan overloading sistem. Meskipun praktis (dan terkadang perlu) untuk menerapkan batas laju di klien, jangan pernah hanya bergantung pada batas laju di sisi klien.

Untuk logika sisi server mana pun yang dapat dipicu oleh klien, selalu pertimbangkan laju maksimum di mana logika semacam itu harus dijalankan. Banyak tindakan sisi server harus memiliki frekuensi terbatas untuk mencegah penyalahgunaan atau kegagalan, seperti:

  • Panggilan API Roblox: API backend seperti DataStoreService dan BadgeService memiliki batas laju bawaan yang akan menyebabkan permintaan Anda gagal jika melebihi
  • Operasi yang memakan biaya tinggi: Tindakan yang mengonsumsi sumber daya server yang signifikan atau mempengaruhi klien lain
  • Contoh server: menggandakan model besar dari ServerStorage, bahkan jika mereka tidak pernah direplikasi ke klien
  • Contoh klien: menginstruksikan semua klien yang terhubung untuk memperbarui GUI mereka secara bersamaan
  • Mekanik yang dapat dieksploitasi: Tindakan yang dapat disalahgunakan jika dilakukan dengan cepat, seperti memberikan kekebalan, teleportasi pemain, atau memberikan mata uang

Contoh token bucket

Pendekatan yang kuat dan umum untuk pembatasan laju adalah algoritma token bucket. Bayangkan setiap pengguna memiliki ember yang dapat menampung sejumlah token tertentu. Untuk melakukan tindakan, pengguna harus menghabiskan satu token. Ember tersebut terisi ulang dengan token baru pada tingkat yang stabil. Metode ini memungkinkan adanya lonjakan tindakan pendek saat dibutuhkan, tetapi mencegah spam yang berkelanjutan dengan menegakkan rata-rata tingkat sepanjang waktu.


--!strict
-- Modul terletak di 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
-- Membuat limiter yang memungkinkan paling tidak `capacity` acara, terisi ulang kembali ke
-- penuh pada tingkat `capacity / windowSeconds` token per detik.
function TokenBucket.new(capacity: number, windowSeconds: number): TokenBucketT
assert(capacity >= 1, "capacity must be >= 1")
assert(windowSeconds > 0, "windowSeconds must be > 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
-- Mengembalikan false jika pengguna akan melebihi batas mereka
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

Untuk menggunakan modul tersebut, pertama-tama buat instance limiter baru dengan TokenBucket.new(capacity, windowSeconds). capacity adalah jumlah maksimum permintaan yang dapat dibuat pengguna dalam lonjakan cepat, dan windowSeconds menentukan berapa lama dibutuhkan untuk mengisi ulang semua token tersebut. Misalnya, TokenBucket.new(5, 10) menciptakan limiter yang memungkinkan lonjakan hingga 5 permintaan dan mengisi ulang satu token setiap dua detik (10 detik / 5 token).

Sebelum mengeksekusi logika yang dilindungi, lakukan panggilan ke metode :allow(userId), menolak tindakan jika mengembalikan false. Ini juga merupakan praktik baik untuk membersihkan data ember pengguna ketika mereka keluar untuk mencegah kebocoran memori.

Contoh skrip berikut menunjukkan bagaimana melindungi RemoteEvent yang digunakan untuk sistem obrolan kustom dari dihujani spam oleh pemain.


-- Contoh penggunaan dalam skrip server
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 pesan per 10 detik (kapasitas 5, terisi ulang pada 0.5 token/detik)
local chatLimiter = TokenBucket.new(5, 10)
ChatRemote.OnServerEvent:Connect(function(player: Player, message: string)
if not chatLimiter:allow(player.UserId) then
-- Terlalu cepat: abaikan permintaan atau kirim peringatan spam
return
end
-- Proses pesan (setelah validasi lain)
broadcastMessage(player, message)
end)
-- Pembersihan untuk mencegah kebocoran memori
Players.PlayerRemoving:Connect(function(p: Player)
chatLimiter.buckets[p.UserId] = nil
end)

Mengamankan instance yang dipicu klien

ProximityPrompts, ClickDetectors, dan DragDetectors bukan remote, tetapi seorang penipu dapat memicu acara mereka dari jarak mana pun, kapan saja, sering kali mengabaikan properti seperti Enabled atau MaxActivationDistance. Objek-objek ini harus diamankan dengan ketat seperti remote.

ProximityPrompt

  • Seorang penipu dapat memicu acara apa pun bahkan jika Enabled tidak aktif di server.
  • Enabled, MaxActivationDistance, dan RequiresLineOfSight dapat diam-diam diubah di klien, jadi setiap ProximityPrompt yang direplikasi dapat dilihat dan kemungkinan berinteraksi dari lokasi mana pun.
  • Server akan menerima acara tombol tekan (seperti PromptButtonHoldBegan) bahkan jika HoldDuration di sisi server adalah nol. Klien dapat memanipulasi HoldDuration untuk melakukan interaksi dengan sangat cepat.
  • Hanya acara Triggered yang memiliki pemeriksaan jarak di sisi server. Acara lain (seperti PromptButtonHoldBegan dan TriggerEnded) tidak memiliki pemeriksaan jarak, dan dapat dikirim secara sembarangan oleh klien mana pun.

ClickDetector

  • Tidak ada pemeriksaan sama sekali di server untuk acara apa pun.
  • Penipu dapat mereplikasi acara apa pun dari jarak mana pun, bahkan jika ClickDetector dinonaktifkan (MaxActivationDistance 0 atau DragDetector.Enabled false).
  • Acara dapat direplikasi bahkan jika ClickDetector tidak diparent pada sesuatu yang dapat diklik.

DragDetector

  • Untuk acara yang diwarisi dari ClickDetector, tidak ada pemeriksaan.
  • Untuk acara drag, properti Enabled dan PermissionPolicy diperiksa dan dihormati oleh server. Semua properti lainnya tidak diperiksa.

Ketika salah satu acara ini dipicu, skrip server Anda harus melakukan validasi sendiri sebelum mengambil tindakan:

  • Periksa apakah sudah diaktifkan: Pastikan bahwa instance dimaksudkan untuk diaktifkan dengan memeriksa propertinya (seperti Enabled, HoldDuration, RunLocally, dll.) di server. Langkah ini tidak perlu jika objek selalu diaktifkan.
  • Periksa status pemain: Pastikan bahwa pemain dapat melakukan tindakan, mengingat status pemain saat ini. Apakah karakter pemain ada di dunia? Apakah karakter mereka cukup dekat dengan objek? Haruskah pemain tetap hidup untuk berinteraksi dengan objek ini? Periksa status pemain lain yang dipelihara oleh pengalaman Anda di server sesuai kebutuhan.
  • Terapkan pembatasan laju: Seperti yang dibahas di Pembatasan Laju, terapkan cooldown di klien dan tegakkan batas laju di server untuk mencegah acara ini dihujani spam dan disalahgunakan. Untuk interaksi yang diharapkan memakan waktu minimum, pastikan bahwa klien tidak menyelesaikannya terlalu cepat.

Catatan tentang kepemilikan jaringan: Secara default, jika salah satu objek ini adalah anak dari bagian yang tidak dipaku atau kelompok yang tidak dipaku, seorang penipu dapat mengambil kepemilikan jaringan dari bagian induk dan langsung memindahkannya ke karakter mereka, melewati pemeriksaan jarak. Untuk tindakan penting, gunakan bagian yang dipaku atau API kepemilikan jaringan bersama dengan validasi di sisi server yang diperlukan.

RemoteEvents dan RemoteFunctions

Batasi cakupan dan dampak dari RemoteFunctions dan RemoteEvents. Berhati-hatilah dalam memuat dinamis aset apa pun (bahkan tekstur atau suara) atau menjalankan kode pengalaman (terutama melalui require) berdasarkan argumen fungsi/event jarak jauh. Hindari menerapkan remote yang memungkinkan klien untuk menentukan jalur atau referensi instance sembarang untuk dihapus atau dimodifikasi oleh server, bahkan jika modifikasi tersebut tampak sepele. Remote yang dapat mengubah status instance sembarang atau membuat perubahan luas pada pohon DataModel sering kali dapat dirangkai dengan bug lainnya untuk sangat mempengaruhi status atau logika keseluruhan. Periksa tidak hanya tipe argumen instance, tetapi juga kelas dan lokasi atau struktur yang diharapkan dalam DataModel.

Komunikasi klien ke klien

Beberapa remote dirancang untuk memungkinkan satu klien memicu efek di klien lain. Ini terjadi ketika klien memicu remote ke server, yang kemudian menggunakan RemoteEvent:FireAllClients(), RemoteEvent:FireClient(), atau metode serupa untuk meneruskan informasi. Pola ini berbahaya jika tidak diamankan dengan benar - server harus menjadi penjaga gerbang, bukan hanya penyampaikan.

Bayangkan skenario di mana pemain dapat melancarkan mantra petir. Ketika dilancarkan, pemain di sekitar melihat kilatan, mendengar suara, dan merasakan guncangan kamera.

Skrip server yang rentan mungkin terlihat seperti ini:

Kode server yang rentan (skrip di ServerScriptStorage)

local castLightningEvent = game.ReplicatedStorage.CastLightning
-- Skrip server ini meneruskan pesan ke semua orang tanpa validasi
castLightningEvent.OnServerEvent:Connect(function(player, strikePosition)
castLightningEvent:FireAllClients(strikePosition)
end)
Kode efek klien

local castLightningEvent = game.ReplicatedStorage.CastLightning
-- Buat efek visual dan suara di klien
local function createLightningEffect(strikePosition)
-- Kode untuk mengguncang kamera
-- Kode untuk memainkan suara petir yang keras
-- Kode untuk membuat kilatan petir visual
print("Petir menyambar di: " .. tostring(strikePosition))
end
-- Ketika server menyiarkan acara, klien ini menjalankan efek
castLightningEvent.OnClientEvent:Connect(createLightningEffect)

Seorang penipu dapat menyalahgunakan sistem rentan ini dengan:

  • Spam: Memanggil remote ratusan kali per detik, menyebabkan efek terus-menerus yang membuat pengalaman tidak dapat dimainkan
  • Data tidak valid: Mengirim nil, NaN, atau tipe yang salah yang menyebabkan kesalahan skrip di semua klien
  • Penggunaan yang tidak sah: Melancarkan mantra yang belum mereka buka kuncinya atau tidak memiliki sumber daya untuk

Server harus menjadi penjaga gerbang, bukan relai. Sebelum menyiarkan, server harus memvalidasi permintaan dan menerapkan pembatasan laju per pemain. Berikut adalah contoh beberapa metode yang dapat digunakan untuk memvalidasi permintaan.

Kode server yang aman

local castLightningEvent = game.ReplicatedStorage.CastLightning
local playerCooldowns = {}
local COOLDOWN_TIME = 3
castLightningEvent.OnServerEvent:Connect(function(player, strikePosition)
-- 1. Validasi tipe
if typeof(strikePosition) ~= "Vector3" then
return -- Tipe tidak valid, tolak diam-diam
end
-- 2. Periksa keberadaan NaN (NaN ~= NaN adalah satu-satunya cara untuk mendeteksinya)
if strikePosition.X ~= strikePosition.X then
return -- Mengandung NaN, tolak
end
-- 3. Contoh Pembatasan laju
local lastCast = playerCooldowns[player] or 0
if tick() - lastCast < COOLDOWN_TIME then
return -- Masih dalam cooldown
end
-- 4. Contoh pemeriksaan izin
if not player:GetAttribute("HasLightningSpell") then
return -- Pemain tidak memiliki mantra ini
end
-- 5. Contoh validasi jarak
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 -- Di luar jangkauan
end
-- Semua pemeriksaan terlewat - aman untuk disiarkan
playerCooldowns[player] = tick()
castLightningEvent:FireAllClients(player, strikePosition)
end)

Dengan menerapkan pemeriksaan ini, akan lebih sulit bagi penipu untuk merusak pengalaman bagi pemain lain. Server bertindak sebagai penghalang pelindung, memastikan hanya tindakan yang valid dan sah yang disiarkan kepada klien lain.

Pertimbangan khusus

Berikut adalah beberapa kasus tambahan yang harus Anda pertimbangkan yang mungkin memerlukan penanganan khusus.

Manipulasi data store

Dalam pengalaman yang menggunakan DataStoreService untuk menyimpan data pemain, penipu dapat memanfaatkan data tidak valid, kondisi balapan untuk merusak penyimpanan atau menduplikasi item di DataStore. Ini sangat bermasalah dalam pengalaman dengan perdagangan item, pasar, dan sistem mata uang.

Pastikan bahwa setiap tindakan yang dilakukan melalui RemoteEvent atau RemoteFunction yang mempengaruhi data pemain dengan input klien disanitasi berdasarkan hal berikut:

  • Nilai instance tidak dapat diserialisasi ke dalam DataStore dan akan gagal. Gunakan validasi tipe untuk mencegah ini.
  • DataStores memiliki batasan data. String dengan panjang sembarang harus diperiksa dan/atau dibatasi untuk menghindari ini, bersama dengan memastikan kunci arbitrer tanpa batas tidak dapat ditambahkan ke tabel oleh klien.
  • Indeks tabel tidak dapat NaN atau nil. Iterasi semua tabel yang diteruskan oleh klien dan verifikasi bahwa semua indeks valid.
  • DataStores hanya dapat menerima karakter UTF-8 yang valid, jadi Anda harus mensanitasi semua string yang disediakan oleh klien melalui utf8.len() untuk memastikan mereka valid. utf8.len() akan mengembalikan panjang suatu string, memperlakukan karakter unicode sebagai satu karakter; jika karakter UTF-8 yang tidak valid ditemui, akan mengembalikan nil dan posisi karakter yang tidak valid. Perhatikan bahwa string UTF-8 yang tidak valid juga dapat hadir di tabel sebagai kunci dan nilai.
  • Eksploitasi nomor tak terbatas/NaN - Penipu dapat mengirim nilai seperti -1/0, 0/0, atau math.huge untuk menyebabkan mata uang tak terbatas atau menghancurkan perhitungan. Selalu validasi menggunakan metode yang dijelaskan dalam bahaya NaN.

Kondisi balapan dapat terjadi ketika pemain memanipulasi waktu antara operasi untuk menduplikasi item atau merusak data.

Contoh eksploitasi perdagangan: Seorang pemain memulai perdagangan, mengirim item mereka ke pemain lain, lalu segera meninggalkan permainan. Jika perdagangan berhasil tetapi penyimpanan DataStore mereka gagal karena data yang tidak valid, mereka bergabung kembali dengan item asli mereka sementara pemain lain menyimpan item yang diperdagangkan - menghasilkan duplikasi.

Strategi pencegahan:

  • Validasi semua data sebelum melakukan operasi perdagangan
  • Gunakan pola seperti transaksi di mana data semua pemain divalidasi sebelum mengkomit perubahan
  • Terapkan penanganan kesalahan yang tepat yang membatalkan semua perubahan jika bagian mana pun gagal

MarketplaceService

Untuk interaksi yang melibatkan MarketplaceService, seperti pass atau produk pengembang, semua validasi pembelian dan pemberian item harus dilakukan di server. Secara khusus, gunakan callback ProcessReceipt untuk memvalidasi penerimaan pembelian secara aman. Jangan pernah mempercayai sinyal sisi klien, seperti PromptProductPurchaseFinished, untuk mengonfirmasi pembelian tanpa verifikasi server, karena ini dapat dipalsukan. Pastikan bahwa fungsi ProcessReceipt Anda memeriksa dengan teliti rincian penerimaan dan hanya memberikan item atau mata uang setelah mengonfirmasi transaksi yang sah dengan server Roblox, dan bersiaplah untuk menangani kasus di mana produk telah diberikan sebelumnya untuk penerimaan yang diberikan.

©2026 Roblox Corporation. Roblox, logo Roblox, dan Powering Imagination termasuk dalam merek dagang kami yang terdaftar dan tidak terdaftar di AS dan negara lainnya.