Live
Eine Live-Wirtschaft absichern
Die Sicherheitsseite von über zwei Jahren als einziger Entwickler einer laufenden Roleplay-Wirtschaft: die Wege finden, auf denen Spieler Dinge dupliziert, teleportiert oder gefälscht haben, und sie schließen, ohne den Server herunterzufahren.
- Rolle
- Exploits finden, beheben und verhindern, im Alleingang
- Stack
- Lua (GLua), NutScript, MySQL
- Kontext
- Diverge Networks, ein Live-Roleplay-Server mit spielergetriebener Wirtschaft
- Status
- Live, der Kofferraum-Fix ist im Test
Das Problem
Auf einem Roleplay-Server sind Gegenstände die Wirtschaft. Ein Bug, der einen Gegenstand verschluckt, kostet einen Spieler Stunden an Spielzeit. Ein Bug, der einen dupliziert, entwertet die Gegenstände aller anderen. Und der Server darf nicht ausfallen, während man das behebt.
Als einziger Entwickler war ich zugleich das Security-Team. Das ist die Seite der Arbeit, die Spieler nie sehen: die Wege finden, auf denen Leute Dinge dupliziert, teleportiert oder gefälscht haben, und sie auf einem Live-Server schließen.
Wie ich vorgehe
- Jeden Netzwerk-Handler lesen, als wäre der Client feindlich. Alles, was das Spiel eines Spielers sendet, lässt sich verändern, also baut der Server es neu auf oder prüft es erneut gegen seine eigenen Daten.
- Erst reservieren, dann warten. Alles Asynchrone, etwa ein Datenbankschreibvorgang oder ein Inventar-Add, ist eine Lücke, in die sich ein schneller Spieler quetschen kann. Also nimmt der Server sich zuerst, was er braucht, und gibt es zurück, wenn die Arbeit scheitert.
- Berechtigungen prüfen, bevor man die Nutzdaten liest, nicht danach.
- Fixes ausliefern, die der Live-Server verkraftet. Wenn ein Exploit ausgenutzt wird, kann der Fix nicht auf das nächste große Update warten.
Duplizierung bei Lieferungen
Live
Das Entnehmen eines Gegenstands aus einer Kiste startete ein asynchrones Inventar-Add, ohne den Gegenstand vorher zu reservieren. Viele schnelle Anfragen konnten alle die Prüfung „Ist noch einer da?“ bestehen, bevor eine von ihnen fertig war, und jede ging mit einem Gegenstand davon.
Ich habe das Back-End für Entnehmen und Ablegen neu geschrieben. Die Anzahl wird jetzt verringert und die Kiste gesperrt, bevor die asynchrone Arbeit beginnt, Entfernung und Mengen werden auf dem Server geprüft, und der Gegenstand wird erstattet, wenn er nicht ins Inventar passt. Nebenbei habe ich ein Admin-Inventar-Reset repariert, das Slots mit Gegenständen belegt lassen konnte, die es gar nicht mehr gab.
netstream.Hook("shpUse", function(client, uniqueID, drop)
local entity = client.nutShipment
-- ... per-player and per-crate rate limits omitted
if (not IsValid(entity) or entity.transactionLockout) then return end -- one take at a time per crate
local itemTable = nut.item.list[uniqueID]
if (not itemTable) then return end
-- Distance and amount are checked on the server.
if (entity:GetPos():Distance(client:GetPos()) > 128) then return end
local amount = entity.items[uniqueID]
if (not isnumber(amount) or amount <= 0) then return end
-- Reserve before waiting: take the item off the count and lock the crate
-- *before* the asynchronous add, so repeated requests can't all pass the check.
entity.items[uniqueID] = amount - 1
entity.transactionLockout = true
local function onError(err)
if (IsValid(client)) then client:notifyLocalized(tostring(err)) end
if (not IsValid(entity)) then return end
-- Didn't fit in the inventory: give the item back to the crate.
if (err == "noFit") then
entity.items[uniqueID] = entity.items[uniqueID] + 1
end
entity.transactionLockout = nil
end
local function onSuccess(res)
hook.Run("OnShipmentItemSpawned", client, res, entity)
if (entity:getItemCount() < 1) then
entity:Remove() -- empty crates break; the lock stays until they're gone
else
entity.transactionLockout = nil
end
end
local add = drop
and nut.item.spawn(uniqueID, entity:GetPos() + Vector(0, 0, 16))
or client:getChar():getInv():add(uniqueID, itemTable.maxQuantity, {})
add:next(function(res)
if (res.error) then return onError(res.error) end
onSuccess(res)
end, onError)
end)Schummeln bei einem Live-PvP-Event
Live
Netzwerkvariablen, die das Target-PvP-Event nutzte, verrieten dem Client genug, damit Spieler es austricksen konnten. Ich habe den Fix so gebaut, dass er sofort auf den Live-Server gehen konnte.
Gefälschte Beweise und Akten
Live
- Die versteckten Daten einer Fingerabdruckkarte ließen sich vom Client aus bearbeiten. Änderungen akzeptieren jetzt nur bekannte Felder, in der Länge begrenzt, und werden auf dem Server gespeichert (siehe Forensik).
- Berufungen akzeptieren nur Akten-IDs, die der Server dem Spieler bereits gezeigt hat; eine geänderte ID in der Anfrage bewirkt daher nichts (siehe das Justizsystem).
- Wenn Charaktere zwischen Fraktionen wechseln, prüft der Server die Fraktions-ID gegen seine eigenen Daten, statt die vom Client gesendete zu akzeptieren.
- Das Lockpicking-Minispiel hat serverseitigen Anti-Cheat, sodass ein modifizierter Client es nicht einfach überspringen kann.
Teleportieren über den Kofferraum
Gebaut, im Test
Donator-Spieler können mehr als ein Fahrzeug spawnen. Waren zwei Kofferräume gleichzeitig offen, konnten Spieler Gegenstände von überall auf der Karte zwischen ihnen verschieben. Jetzt kann immer nur der Kofferraum eines gespawnten Fahrzeugs offen sein. Das wird zusammen mit der Migration auf den gemeinsamen Kofferraum ausgeliefert.
Und viele kleinere
- Eine Kontextmenü-Aktion, die den Waffenrückstoß entfernte.
- Ein Weg an der Bank-Obergrenze vorbei.
- Charakterwechsel, um das Fahrzeuglimit zu umgehen.
- Duplizierung und Umgehung von Beschränkungen über Waffentaschen, die jetzt isolierte Inventare sind.
- Bewegen und Springen über die Konsole, während man gefesselt ist.
- Verbindung trennen, während man in Handschellen liegt, um die Handschellen loszuwerden; sie bleiben jetzt am Charakter, bis sie entfernt werden.
- Staff, die aus dem Noclip schossen und die Physgun missbrauchten, plus Logging, wenn Staff Gegenstände aus Lagern löschen.
- Inventar-Syncs, die groß genug waren, um Netzwerkpuffer zu überlaufen, jetzt codiert und komprimiert.
Die Produktion schützen
Die Fixes bringen nur etwas, wenn ihr Test den Live-Server nicht beschädigen kann. Ich habe auf einen produktionsnahen Testserver gedrängt, der nie an Produktionsdaten kommt, und die Admin-Werkzeuge, die nur für die Entwicklung gedacht sind, so abgesichert, dass sie in der Produktion nicht laufen.