Alle Projekte

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

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.

Probieren Sie es aus: Spammen Sie mit dem alten Code „Eins nehmen“, und Sie gehen mit mehr Gegenständen davon, als die Kiste enthielt. Wechseln Sie zum Fix, und die Anzahl hält; setzen Sie ein Häkchen bei „Inventar ist voll“, um die Rückerstattung zu sehen. Eine Simulation der Idee, nicht der Servercode.
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)
Aus dem Produktivcode übernommen und zur besseren Lesbarkeit leicht bearbeitet. Nie genug, um das System nachzubauen.

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

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

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.