QBCore Sunucu Güvenliği: Para Hilesi, Event Exploit, Aim ve Wallhack Koruması | ExxDev

ff

ExxDev

TRIAL MOD.
Katılım: 18 Tem 2026
Mesajlar: 4
Beğeniler: 2

QBCORE SUNUCU GÜVENLİĞİ

Para Hilesi, Event Exploit, Aim, Wallhack ve Entity Spam Koruması

Hazırlayan: ExxDev

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Önemli Bilgilendirme

FiveM sunucusuna eklenen tek bir Lua dosyasının bütün hileleri tamamen engellemesi mümkün değildir.

Hile kullanan oyuncular, istemci tarafından erişilebilen eventleri kendileri tetikleyebilir. Bu nedenle asıl güvenlik; para, item, meslek, konum, yetki ve ödül kontrollerinin sunucu tarafında yapılmasıyla sağlanır.

Bu rehberde:

  • Para hilesine neden olan açık eventlerin kapatılması
  • Event spam ve tetikleme sınırı
  • Güvenli market satın alma sistemi
  • Güvenli meslek ödülü sistemi
  • Şüpheli para hareketlerinin kaydedilmesi
  • Anormal silah hasarının engellenmesi
  • Şüpheli aim hareketlerinin kayıt altına alınması
  • Entity spam sınırlandırması
  • server.cfg güvenlik ayarları
  • QBCore yetki güvenliği
  • Şüpheli bakiyelerin SQL üzerinden kontrolü

anlatılacaktır.

Uyarı: Kodları canlı sunucuya eklemeden önce yedek alın ve test sunucusunda deneyin. Her QBCore paketinin script yapısı farklı olabilir.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

1. Hileler Para Açığını Nasıl Kullanıyor?

Aşağıdaki kullanım son derece güvensizdir:

Kod:
RegisterNetEvent('meslek:server:odul', function(amount)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)

    Player.Functions.AddMoney('cash', amount)
end)

Burada para miktarı client tarafından gönderilmektedir. Hile kullanan kişi eventi istediği miktarla çağırabilir:

Kod:
meslek:server:odul
amount = 99999999

Aynı tehlike aşağıdaki değerler için de geçerlidir:

  • Para miktarı
  • Ürün fiyatı
  • Satılacak item değeri
  • Meslek ödülü
  • Oyuncu yetkisi
  • Meslek adı
  • Hedef oyuncu ID’si
  • Araç fiyatı
  • Fatura miktarı
  • Silah ve item miktarı

Client yalnızca oyuncunun gerçekleştirmek istediği işlemi bildirmelidir. İşlemin fiyatı, sonucu ve ödülü server tarafından hesaplanmalıdır.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

2. server.cfg Güvenlik Ayarları

Öncelikle server.cfg dosyanızın yedeğini alın.

Aşağıdaki temel ayarları server.cfg içerisine ekleyin:

Kod:
# ScriptHook tabanlı eklentileri kapatır
sv_scriptHookAllowed 0

# Değiştirilmiş istemci dosyalarını sınırlar
sv_pureLevel 1

# Başka oyuncuların entity kontrolünü ele geçirme isteklerini sınırlar
sv_filterRequestControl 1

# Oyuncu IP adreslerini genel raporlarda gizler
sv_endpointPrivacy true

# Network üzerinden telefon patlaması eventini kapalı tutar
set sv_enableNetworkedPhoneExplosions false

Önce sv_filterRequestControl 1 kullanın. Scriptlerinizi test ettikten sonra daha sıkı koruma için şu ayarı deneyebilirsiniz:

Kod:
sv_filterRequestControl 2

Dikkat: 3 veya 4 gibi daha katı seviyeler bazı araç, garaj ve entity scriptlerini bozabilir. Değişiklikleri doğrudan canlı sunucuda denemeyin.

İsteğe bağlı gelişmiş ayarlar

Kod:
# Network üzerinden yönlendirilen ses eventlerini kapatır
set sv_enableNetworkedSounds false

# Network script entity state eventlerini kapatır
set sv_enableNetworkedScriptEntityStates false

# Client replay özelliğini kapatır
set sv_disableClientReplays 1

Bu ayarlar bazı yasal scriptleri etkileyebilir:

  • sv_enableNetworkedSounds false: Network ses kullanan scriptleri bozabilir.
  • sv_enableNetworkedScriptEntityStates false: Bazı entity ve araç scriptlerini etkileyebilir.
  • sv_disableClientReplays 1: Rockstar Editor kullanımını kapatır.
  • sv_pureLevel 2: Ses ve grafik modları dahil bütün değiştirilmiş dosyaları engeller.

Bütün ayarları aynı anda açmak yerine tek tek açıp test edin.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

3. ExxDev Security Resource Oluşturma

Sunucunuzun resources klasörüne girin ve şu klasörü oluşturun:

Kod:
resources/[standalone]/exxdev_security

Klasörün içerisine şu dosyaları oluşturun:

Kod:
exexxdev_security/
├── fxmanifest.lua
├── config.lua
└── server/
    └── main.lua

fxmanifest.lua

Kod:
fx_version 'cerulean'
game 'gta5'

author 'ExxDev'
description 'QBCore server-side security helpers'
version '1.0.0'

server_scripts {
    'config.lua',
    'server/main.lua'
}

dependency 'qb-core'

config.lua

Kod:
Config = {}

-- İlk kurulumda false bırakın.
-- Logları kontrol ettikten sonra true yapabilirsiniz.
Config.AutoKick = false

-- Belirlenen süre içerisinde bu puana ulaşan oyuncu atılır.
Config.KickAfter = 10
Config.StrikeWindowSeconds = 60

-- Tek işlemde bu miktarların üzerindeki para hareketleri loglanır.
-- Kendi ekonominize göre değiştirin.
Config.MoneyAlert = {
    cash = 50000,
    bank = 250000,
    crypto = 50
}

Config.Combat = {
    -- Bir saniyede izin verilecek hasar eventi
    MaxDamageEventsPerSecond = 80,

    -- overrideDefaultDamage kullanılarak gönderilebilecek en yüksek hasar
    MaxOverrideDamage = 1000,

    -- Kısa sürede gerçekleşen ölümcül hit sayısı
    LethalHitsForReview = 6,
    LethalWindowSeconds = 15
}

-- İlk kurulumda kapalı tutun.
-- Çok fazla obje oluşturan scriptlerle çakışabilir.
Config.EntityCreation = {
    Enabled = false,
    MaxEntities = 80,
    WindowMilliseconds = 10000
}

server/main.lua

Kod:
local QBCore = exports['qb-core']:GetCoreObject()

local rateBuckets = {}
local playerStrikes = {}
local lethalHits = {}

local function GetLicense(src)
    for _, identifier in ipairs(GetPlayerIdentifiers(src)) do
        if identifier:sub(1, 8) == 'license:' then
            return identifier
        end
    end

    return 'license:unknown'
end

local function SecurityLog(src, reason, points)
    src = tonumber(src)

    if not src or src <= 0 then
        return
    end

    points = tonumber(points) or 0

    local now = os.time()
    local strike = playerStrikes[src]

    if not strike or now >= strike.resetAt then
        strike = {
            points = 0,
            resetAt = now + Config.StrikeWindowSeconds
        }

        playerStrikes[src] = strike
    end

    strike.points = strike.points + points

    local playerName = GetPlayerName(src) or 'Bilinmeyen'
    local license = GetLicense(src)

    local message = (
        '[ExxDev Security] Oyuncu: %s | ID: %s | License: %s | Sebep: %s | Puan: %s'
    ):format(
        playerName,
        src,
        license,
        tostring(reason),
        strike.points
    )

    print(message)

    if GetResourceState('qb-log') == 'started' then
        TriggerEvent(
            'qb-log:server:CreateLog',
            'anticheat',
            'ExxDev Security',
            'red',
            message,
            false
        )
    end

    if Config.AutoKick and strike.points >= Config.KickAfter then
        DropPlayer(
            src,
            'Sunucu güvenlik sistemi tarafından şüpheli işlemler nedeniyle bağlantınız kesildi.'
        )
    end
end

local function RateLimit(src, eventName, maxCalls, windowMilliseconds)
    src = tonumber(src)

    if not src or src <= 0 then
        return false
    end

    eventName = tostring(eventName)
    maxCalls = math.floor(tonumber(maxCalls) or 1)
    windowMilliseconds = math.floor(tonumber(windowMilliseconds) or 1000)

    local now = GetGameTimer()
    local key = ('%s:%s'):format(src, eventName)
    local bucket = rateBuckets[key]

    if not bucket
        or now < bucket.startedAt
        or now - bucket.startedAt >= windowMilliseconds
    then
        rateBuckets[key] = {
            startedAt = now,
            count = 1
        }

        return true
    end

    bucket.count = bucket.count + 1

    if bucket.count > maxCalls then
        SecurityLog(
            src,
            ('Event spam: %s (%s çağrı)'):format(eventName, bucket.count),
            1
        )

        return false
    end

    return true
end

local function PositiveInteger(value, maximum)
    value = tonumber(value)
    maximum = tonumber(maximum)

    if not value or value ~= value then
        return nil
    end

    if value ~= math.floor(value) or value < 1 then
        return nil
    end

    if maximum and value > maximum then
        return nil
    end

    return value
end

local function IsNear(src, targetCoords, maximumDistance)
    src = tonumber(src)
    maximumDistance = tonumber(maximumDistance) or 5.0

    if not src or not targetCoords then
        return false
    end

    local ped = GetPlayerPed(src)

    if ped == 0 or not DoesEntityExist(ped) then
        return false
    end

    local playerCoords = GetEntityCoords(ped)
    return #(playerCoords - targetCoords) <= maximumDistance
end

exports('RateLimit', RateLimit)
exports('PositiveInteger', PositiveInteger)
exports('IsNear', IsNear)
exports('Flag', SecurityLog)

-- QBCore para hareketlerini izler.
-- Bu event para hareketini engellemez; şüpheli işlemi loglar.
AddEventHandler(
    'QBCore:Server:OnMoneyChange',
    function(playerSource, moneyType, amount, action, reason)
        local src = tonumber(playerSource)
        amount = tonumber(amount)
        moneyType = tostring(moneyType or ''):lower()
        action = tostring(action or '')
        reason = tostring(reason or 'unknown'):sub(1, 100)

        if not src or not amount then
            return
        end

        local threshold = Config.MoneyAlert[moneyType]

        if not threshold then
            return
        end

        if (action == 'add' or action == 'set') and amount >= threshold then
            SecurityLog(
                src,
                (
                    'Yüksek para hareketi | Tür: %s | İşlem: %s | Miktar: %s | Reason: %s'
                ):format(moneyType, action, amount, reason),
                0
            )
        end
    end
)

-- Anormal silah hasarı ve hasar event spam koruması
AddEventHandler('weaponDamageEvent', function(sender, data)
    sender = tonumber(sender)

    if not sender or sender <= 0 or type(data) ~= 'table' then
        return
    end

    if not RateLimit(
        sender,
        'weaponDamageEvent',
        Config.Combat.MaxDamageEventsPerSecond,
        1000
    ) then
        CancelEvent()
        return
    end

    if data.overrideDefaultDamage then
        local damage = tonumber(data.weaponDamage)

        if not damage
            or damage ~= damage
            or damage < 0
            or damage > Config.Combat.MaxOverrideDamage
        then
            CancelEvent()

            SecurityLog(
                sender,
                ('Anormal silah hasarı engellendi: %s'):format(tostring(damage)),
                3
            )

            return
        end
    end

    -- Bu bölüm otomatik ban atmaz.
    -- Yalnızca kısa süredeki ölümcül hitleri inceleme için kaydeder.
    if data.willKill and tonumber(data.hitGlobalId) then
        local netId = tonumber(data.hitGlobalId)
        local hitEntity = NetworkGetEntityFromNetworkId(netId)

        if hitEntity ~= 0 and DoesEntityExist(hitEntity) then
            local targetOwner = NetworkGetEntityOwner(hitEntity)

            if targetOwner and targetOwner > 0 and targetOwner ~= sender then
                local now = os.time()
                local hits = lethalHits[sender] or {}

                for index = #hits, 1, -1 do
                    if now - hits[index] > Config.Combat.LethalWindowSeconds then
                        table.remove(hits, index)
                    end
                end

                hits[#hits + 1] = now
                lethalHits[sender] = hits

                if #hits >= Config.Combat.LethalHitsForReview then
                    SecurityLog(
                        sender,
                        (
                            '%s saniyede %s ölümcül hit. Aim incelemesi gerekli.'
                        ):format(
                            Config.Combat.LethalWindowSeconds,
                            #hits
                        ),
                        0
                    )

                    lethalHits[sender] = {}
                end
            end
        end
    end
end)

-- İsteğe bağlı entity spam koruması
if Config.EntityCreation.Enabled then
    AddEventHandler('entityCreating', function(entity)
        local owner = NetworkGetEntityOwner(entity)

        if owner and owner > 0 then
            local allowed = RateLimit(
                owner,
                'entityCreating',
                Config.EntityCreation.MaxEntities,
                Config.EntityCreation.WindowMilliseconds
            )

            if not allowed then
                CancelEvent()

                SecurityLog(
                    owner,
                    'Entity oluşturma limiti aşıldı.',
                    2
                )
            end
        end
    end)
end

AddEventHandler('playerDropped', function()
    local src = source
    local prefix = tostring(src) .. ':'

    playerStrikes[src] = nil
    lethalHits[src] = nil

    for key in pairs(rateBuckets) do
        if key:sub(1, #prefix) == prefix then
            rateBuckets[key] = nil
        end
    end
end)

Önemli: Bu resource tek başına diğer scriptlerde bulunan açık eventleri kapatmaz. Aşağıdaki güvenli kontrolleri para ve item veren scriptlerin server dosyalarına uygulamanız gerekir.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

4. Resource Başlatma Sırası

server.cfg içerisine şu sırayla ekleyin:

Kod:
ensure qb-core
ensure qb-inventory
ensure qb-log
ensure exxdev_security

# Korunacak meslek ve market scriptleri bundan sonra başlamalıdır.
ensure exx-shop
ensure exx-job

qb-log kullanmıyorsanız ilgili satırı kaldırabilirsiniz. Güvenlik sistemi konsol üzerinden yine kayıt oluşturacaktır.

Sunucuyu yeniden başlatın ve konsolda hata bulunmadığını kontrol edin.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

5. Güvenli Market Satın Alma Eventi

Aşağıdaki örnekte client yalnızca item adı ve miktar gönderir. Fiyat, maksimum miktar, oyuncunun konumu, parası ve envanter kapasitesi server tarafından kontrol edilir.

Bu kodu kendi market scriptinizin server dosyasına uyarlayın:

Kod:
local QBCore = exports['qb-core']:GetCoreObject()

local Market = {
    coords = vector3(25.74, -1347.27, 29.49),
    maximumDistance = 5.0,

    items = {
        water_bottle = {
            price = 3,
            maximumAmount = 10
        },

        sandwich = {
            price = 5,
            maximumAmount = 10
        }
    }
}

RegisterNetEvent('exx-shop:server:buyItem', function(itemName, requestedAmount)
    local src = source

    if not exports['exxdev_security']:RateLimit(
        src,
        'exx-shop:buyItem',
        5,
        5000
    ) then
        return
    end

    if type(itemName) ~= 'string' then
        exports['exxdev_security']:Flag(
            src,
            'Market eventinde geçersiz item türü.',
            1
        )

        return
    end

    local itemData = Market.items[itemName]

    if not itemData then
        exports['exxdev_security']:Flag(
            src,
            'Market listesinden bulunmayan item istenildi: ' .. itemName,
            2
        )

        return
    end

    local amount = exports['exxdev_security']:PositiveInteger(
        requestedAmount,
        itemData.maximumAmount
    )

    if not amount then
        exports['exxdev_security']:Flag(
            src,
            'Market eventinde geçersiz miktar.',
            1
        )

        return
    end

    if not exports['exxdev_security']:IsNear(
        src,
        Market.coords,
        Market.maximumDistance
    ) then
        exports['exxdev_security']:Flag(
            src,
            'Marketten uzakta satın alma eventi tetiklendi.',
            2
        )

        return
    end

    local Player = QBCore.Functions.GetPlayer(src)

    if not Player then
        return
    end

    local totalPrice = itemData.price * amount

    local canAdd, reason = exports['qb-inventory']:CanAddItem(
        src,
        itemName,
        amount
    )

    if not canAdd then
        TriggerClientEvent(
            'QBCore:Notify',
            src,
            reason or 'Envanterinizde yeterli alan bulunmuyor.',
            'error'
        )

        return
    end

    local paid = Player.Functions.RemoveMoney(
        'cash',
        totalPrice,
        'exx-shop-purchase'
    )

    if not paid then
        TriggerClientEvent(
            'QBCore:Notify',
            src,
            'Yeterli paranız bulunmuyor.',
            'error'
        )

        return
    end

    local added = exports['qb-inventory']:AddItem(
        src,
        itemName,
        amount,
        false,
        false,
        'exx-shop-purchase'
    )

    if not added then
        Player.Functions.AddMoney(
            'cash',
            totalPrice,
            'exx-shop-refund'
        )

        TriggerClientEvent(
            'QBCore:Notify',
            src,
            'Item eklenemediği için ücret iade edildi.',
            'error'
        )

        return
    end

    TriggerClientEvent(
        'QBCore:Notify',
        src,
        ('%sx %s satın aldınız.'):format(amount, itemName),
        'success'
    )
end)

Bu yapıda hile kullanan kişi fiyatı değiştiremez. Çünkü fiyat client tarafından alınmaz; server içerisindeki Market tablosundan hesaplanır.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

6. Güvenli Meslek Ödülü Sistemi

Sadece “meslek bitti” eventi kullanmak yeterli değildir. Oyuncunun görevi başlatıp başlatmadığı, checkpoint sırası, geçen süre ve konumu server tarafından takip edilmelidir.

Aşağıdaki örneği meslek scriptinizin server dosyasına ekleyebilirsiniz:

Kod:
local QBCore = exports['qb-core']:GetCoreObject()
local ActiveJobs = {}

local Job = {
    requiredJob = 'garbage',

    startCoords = vector3(-322.24, -1545.82, 31.02),
    maximumDistance = 8.0,

    reward = {
        minimum = 400,
        maximum = 550
    },

    steps = {
        {
            coords = vector3(114.18, -1462.01, 29.29),
            minimumDelay = 20
        },
        {
            coords = vector3(-6.04, -1566.23, 29.21),
            minimumDelay = 20
        },
        {
            coords = vector3(-146.67, -1688.35, 33.06),
            minimumDelay = 20
        }
    }
}

RegisterNetEvent('exx-job:server:start', function()
    local src = source

    if not exports['exxdev_security']:RateLimit(
        src,
        'exx-job:start',
        2,
        10000
    ) then
        return
    end

    if ActiveJobs[src] then
        return
    end

    if not exports['exxdev_security']:IsNear(
        src,
        Job.startCoords,
        Job.maximumDistance
    ) then
        exports['exxdev_security']:Flag(
            src,
            'Meslek başlangıç noktasından uzakta start eventi.',
            2
        )

        return
    end

    local Player = QBCore.Functions.GetPlayer(src)

    if not Player then
        return
    end

    if Job.requiredJob
        and Player.PlayerData.job.name ~= Job.requiredJob
    then
        exports['exxdev_security']:Flag(
            src,
            'Yanlış meslekle görev başlatılmaya çalışıldı.',
            1
        )

        return
    end

    ActiveJobs[src] = {
        nextStep = 1,
        lastStepAt = os.time(),
        startedAt = os.time()
    }

    TriggerClientEvent(
        'QBCore:Notify',
        src,
        'Görev güvenli şekilde başlatıldı.',
        'success'
    )
end)

RegisterNetEvent('exx-job:server:completeStep', function(requestedStep)
    local src = source

    if not exports['exxdev_security']:RateLimit(
        src,
        'exx-job:completeStep',
        4,
        5000
    ) then
        return
    end

    local session = ActiveJobs[src]

    if not session then
        exports['exxdev_security']:Flag(
            src,
            'Görev başlatılmadan checkpoint eventi tetiklendi.',
            2
        )

        return
    end

    local step = exports['exxdev_security']:PositiveInteger(
        requestedStep,
        #Job.steps
    )

    if not step or step ~= session.nextStep then
        exports['exxdev_security']:Flag(
            src,
            'Meslek checkpoint sırası değiştirildi.',
            2
        )

        return
    end

    local stepData = Job.steps[step]
    local now = os.time()

    if now - session.lastStepAt < stepData.minimumDelay then
        exports['exxdev_security']:Flag(
            src,
            'Meslek checkpointi mümkün olmayan sürede tamamlandı.',
            1
        )

        return
    end

    if not exports['exxdev_security']:IsNear(
        src,
        stepData.coords,
        Job.maximumDistance
    ) then
        exports['exxdev_security']:Flag(
            src,
            'Checkpointten uzakta görev eventi tetiklendi.',
            2
        )

        return
    end

    session.lastStepAt = now

    if step < #Job.steps then
        session.nextStep = step + 1

        TriggerClientEvent(
            'QBCore:Notify',
            src,
            ('Görev aşaması tamamlandı: %s/%s'):format(step, #Job.steps),
            'success'
        )

        return
    end

    -- Event tekrar tetiklense bile ikinci kez para vermemesi için
    -- ödülden önce aktif görev silinir.
    ActiveJobs[src] = nil

    local Player = QBCore.Functions.GetPlayer(src)

    if not Player then
        return
    end

    local reward = math.random(
        Job.reward.minimum,
        Job.reward.maximum
    )

    Player.Functions.AddMoney(
        'bank',
        reward,
        'exx-job-secure-reward'
    )

    TriggerClientEvent(
        'QBCore:Notify',
        src,
        ('$%s meslek ödülü banka hesabınıza yatırıldı.'):format(reward),
        'success'
    )
end)

AddEventHandler('playerDropped', function()
    ActiveJobs[source] = nil
end)

Kendi meslek scriptinizdeki koordinatları, minimum süreleri, meslek adını ve ödülleri değiştirin.

Not: Bu sistem teleport kullanan oyuncuyu tek başına kesin olarak tespit etmez. Ancak görevi başlatmadan, sırasını tamamlamadan veya doğrudan para eventini spamlayarak ödül alınmasını engeller.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

7. Güvenli Item Satış Mantığı

Item satan scriptlerde para vermeden önce item server tarafından kaldırılmalıdır.

Kod:
local QBCore = exports['qb-core']:GetCoreObject()

local SellPrices = {
    copper = 25,
    iron = 35,
    aluminum = 30
}

RegisterNetEvent('exx-sell:server:sellItem', function(itemName, requestedAmount)
    local src = source

    if not exports['exxdev_security']:RateLimit(
        src,
        'exx-sell:sellItem',
        5,
        5000
    ) then
        return
    end

    if type(itemName) ~= 'string' or not SellPrices[itemName] then
        exports['exxdev_security']:Flag(
            src,
            'Geçersiz item satış isteği.',
            2
        )

        return
    end

    local amount = exports['exxdev_security']:PositiveInteger(
        requestedAmount,
        25
    )

    if not amount then
        return
    end

    local removed = exports['qb-inventory']:RemoveItem(
        src,
        itemName,
        amount,
        false,
        'exx-secure-item-sale'
    )

    if not removed then
        exports['exxdev_security']:Flag(
            src,
            'Sahip olunmayan item satılmaya çalışıldı.',
            2
        )

        return
    end

    local Player = QBCore.Functions.GetPlayer(src)

    if not Player then
        return
    end

    local payment = SellPrices[itemName] * amount

    Player.Functions.AddMoney(
        'cash',
        payment,
        'exx-secure-item-sale'
    )
end)

Satış fiyatını hiçbir zaman client tarafından almayın.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

8. Açık Eventleri Bulma

Visual Studio Code üzerinden resources klasörünü açın ve genel aramada aşağıdaki ifadeleri tek tek arayın:

Kod:
RegisterNetEvent
AddMoney(
SetMoney(
AddItem(
RemoveItem(
TriggerServerEvent
QBCore.Functions.CreateCallback
MySQL.query
MySQL.update
MySQL.insert

Özellikle şu yapıları arayın:

Kod:
AddMoney('cash', amount)
AddMoney('bank', price)
AddItem(item, amount)
SetMoney(moneyType, amount)

Her network eventinde şu soruları sorun:

  • Miktar client tarafından mı geliyor?
  • Fiyat client tarafından mı geliyor?
  • Oyuncunun mesleği server üzerinden kontrol ediliyor mu?
  • Oyuncunun konumu server üzerinden kontrol ediliyor mu?
  • Event için cooldown veya rate limit var mı?
  • Item gerçekten oyuncuda bulunuyor mu?
  • Oyuncu hedef oyuncuya yakın mı?
  • Görev daha önce başlatılmış mı?
  • Checkpointler doğru sırayla tamamlanmış mı?
  • Yetkili işlemlerinde ACE veya QBCore permission kontrolü var mı?

Event adını karmaşık veya rastgele yapmak güvenlik sağlamaz. Hile kullanan kişi event adlarını görebilir. Eventin kendisi güvenli olmalıdır.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

9. Aim Hilesi Hakkında Gerçekler

Aim hilesi kullanan bir oyuncu server tarafına çoğu zaman normal bir silah hasarı gibi görünen veri gönderebilir. Bu nedenle yalnızca “çok headshot attı” kontrolüyle otomatik ban atmak doğru değildir.

Yukarıdaki güvenlik resource’u:

  • Bir saniyedeki aşırı hasar eventi sayısını sınırlar.
  • Anormal override hasarını iptal eder.
  • Kısa sürede gerçekleşen ölümcül hitleri loglar.
  • Şüpheli oyuncuyu personel incelemesine yönlendirir.

Ancak ölümcül hit kaydı tek başına aim kanıtı değildir. İyi oynayan oyuncular, pompalı tüfekler, yakın mesafe çatışmaları veya özel silah scriptleri yanlış alarm oluşturabilir.

Bu nedenle:

  • Aim kayıtlarında otomatik ban kullanmayın.
  • Oyuncunun geçmiş combat loglarını kontrol edin.
  • Personel tarafından spectate incelemesi yapın.
  • Silah türü, mesafe, isabet serisi ve çatışma koşullarını birlikte değerlendirin.
  • Kesin olmayan tespitlerde kalıcı ban uygulamayın.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

10. Wallhack Tamamen Engellenebilir mi?

Wallhack, oyuncunun istemcisine gönderilen entity ve oyuncu bilgilerini ekranda farklı şekilde göstermeye dayanabilir. Server tarafındaki basit bir Lua kodu oyuncunun ekranındaki çizgi, kutu veya iskelet görüntüsünü doğrudan göremez.

Wallhack riskini azaltmak için:

  • Güncel FiveM artifact kullanın.
  • Güncel ve aktif geliştirilen bir anti-cheat kullanın.
  • Gereksiz oyuncu ve koordinat verilerini client eventleriyle dağıtmayın.
  • Bütün oyuncuların koordinatlarını sürekli broadcast eden scriptleri kaldırın.
  • Yönetici blip ve isim sistemlerini sadece yetkililere açın.
  • Görev odaklı özel oturumlarda doğru routing bucket kullanın.
  • Whitelist ve hesap doğrulama sistemi uygulayın.
  • Combat loglarıyla şüpheli takip davranışlarını inceleyin.

Routing bucket sistemini bütün ana haritaya bilinçsiz şekilde uygulamayın. Özellikle mevcut entity ve garaj sistemleriyle test edilmeden strict lockdown açılması scriptleri bozabilir.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

11. QBCore Yetki Güvenliği

QBCore içerisinde aşağıdaki komutlar normal şartlarda admin yetkisine bağlıdır:

Kod:
/givemoney
/setmoney
/giveitem
/addpermission
/removepermission

server.cfg içerisindeki QBCore yetkilerini kontrol edin:

Kod:
add_ace group.admin command allow
add_ace resource.qb-core command allow

add_ace qbcore.god command allow

add_principal qbcore.god group.admin
add_principal qbcore.god qbcore.admin
add_principal qbcore.admin qbcore.mod

Oyuncu yetkileri için isim veya geçici server ID kullanmayın. Kalıcı Rockstar license identifier kullanın:

Kod:
add_principal identifier.license:BURAYA_LICENSE qbcore.admin

Kontrol edilmesi gerekenler:

  • Eski yetkililerin license satırlarını kaldırın.
  • Herkese god veya admin vermeyin.
  • Scriptlerin içerisinde gizli admin identifier olup olmadığını kontrol edin.
  • txAdmin üzerinde herkese all_permissions vermeyin.
  • Console erişimini yalnızca güvenilir yöneticilere verin.
  • server.cfg ve txData dosyalarını herkese açmayın.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

12. Yüksek Para Bakiyelerini SQL ile Kontrol Etme

Aşağıdaki sorguyu phpMyAdmin veya HeidiSQL üzerinden çalıştırarak en yüksek bakiyeleri görüntüleyebilirsiniz:

Kod:
SELECT
    citizenid,
    CAST(
        COALESCE(
            JSON_UNQUOTE(JSON_EXTRACT(money, '$.cash')),
            '0'
        ) AS DECIMAL(20, 2)
    ) AS cash,
    CAST(
        COALESCE(
            JSON_UNQUOTE(JSON_EXTRACT(money, '$.bank')),
            '0'
        ) AS DECIMAL(20, 2)
    ) AS bank,
    CAST(
        COALESCE(
            JSON_UNQUOTE(JSON_EXTRACT(money, '$.crypto')),
            '0'
        ) AS DECIMAL(20, 2)
    ) AS crypto
FROM players
ORDER BY bank DESC
LIMIT 50;

Bu sorgu yalnızca kontrol içindir; hiçbir oyuncunun parasını silmez.

Şüpheli hesap tespit edildiğinde hemen para silmek yerine:

  • qb-log para kayıtlarını kontrol edin.
  • Para hareketindeki reason bilgisini inceleyin.
  • Parayı veren resource’u tespit edin.
  • Açık eventi kapatın.
  • Aynı açıktan yararlanan diğer hesapları bulun.
  • Yedek aldıktan sonra gerekli ekonomi düzeltmesini yapın.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

13. Test Aşaması

Kurulumdan sonra aşağıdaki testleri yapın:

  1. Normal market satın alma işlemini deneyin.
  2. Yetersiz parayla ürün almaya çalışın.
  3. Maksimum miktarın üzerinde ürün almaya çalışın.
  4. Market noktasından uzakta eventi çalıştırmayı deneyin.
  5. Görevi başlatmadan bitirme eventini test edin.
  6. Checkpointleri yanlış sırayla tamamlamayı deneyin.
  7. Aynı eventi kısa sürede tekrar tekrar çağırın.
  8. Yüksek para vererek log sistemini test edin.
  9. Normal çatışmalarda yanlış hasar iptali olup olmadığını kontrol edin.
  10. Araç, garaj, telefon ve ses scriptlerini test edin.

İlk aşamada:

Kod:
Config.AutoKick = false

kullanın.

En az birkaç gün boyunca logları inceleyin. Yanlış alarm olmadığından emin olduktan sonra otomatik kick sistemini açabilirsiniz:

Kod:
Config.AutoKick = true

Şüpheli aim veya wallhack kayıtlarında otomatik kalıcı ban tavsiye edilmez.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Sonuç

FiveM güvenliğinde en önemli kural şudur:


CLIENT TARAFINDAN GELEN HİÇBİR VERİYE DOĞRUDAN GÜVENMEYİN.

Para, fiyat, ödül, item, meslek, konum ve yetki kontrollerini server tarafında gerçekleştirin. Eventlere rate limit ekleyin, yüksek para hareketlerini kaydedin ve her scripti tek tek denetleyin.

Aim ve wallhack için yalnızca basit Lua kontrollerine güvenmeyin. Güncel artifact, güvenli server eventleri, doğru yapılandırılmış anti-cheat, whitelist ve personel incelemesini birlikte kullanın.


Hazırlayan: ExxDev

Kaynak belirtilmeden paylaşılmaması rica olunur.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Resmî Kaynaklar

 
Fivem hileleri konusunda sorun yaşayanlar için iyi bir çözüm olabilir. Teşekkür ederiz.
 
Geri
Üst