Description
msk_blackmoney:getMoneyAfterWashing in server.lua (lines 39-87) converts all black money into clean cash with no server-side location, item or permission check:
RegisterNetEvent('msk_blackmoney:getMoneyAfterWashing')
AddEventHandler('msk_blackmoney:getMoneyAfterWashing', function()
local src = source
local xPlayer = ESX.GetPlayerFromId(src)
local blackmoney = xPlayer.getAccount('black_money')
if not blackmoney or (blackmoney and blackmoney.money == 0) then return end
xPlayer.removeAccountMoney('black_money', blackmoney.money)
...
xPlayer.addAccountMoney('money', blackmoney.money - amount)
...
end)
The intended flow requires the player to:
- have the required item (
Config.neededItem, checked inside the admin-restricted command at lines 5-13),
- physically travel to the washing location,
- complete the skill-check minigame on the client (
msk_blackmoney:startBlackmoney / skipSkillCheck).
None of that is enforced on the server for the final payout event:
- No location/distance check — the event does not verify the player is anywhere near
Config.Locations['wash_money']
- No item check —
Config.neededItem is only consumed by the admin command; the payout event never validates it
- No minigame/state check — there is no server-side "washing in progress" state; the client skill check is purely cosmetic
- No permission check — although the command to start washing is
{'admin'} restricted, the payout event itself is a plain RegisterNetEvent that any player can trigger directly
Also note msk_blackmoney:removeItem (lines 89-95) takes an arbitrary item name from the client and removes it without validating that the item is part of the washing flow — an attacker could use it to destroy arbitrary items in their own inventory (e.g. bypass "must hold the required item" mechanics by removing the needed item mid-flow, or grief themselves), though the primary issue is the wash itself.
Impact
- Any player with black money (drugs, heists, dirty cash from any source) can launder their entire balance to clean cash instantly, anywhere, without the required item or the skill check
- The
admin restriction on the start command is meaningless because the payout is publicly triggerable
- Economy bypass: the intended risk/gameplay (travel + minigame + time) is completely removed
Suggested fix
- Track a server-side washing session (player started washing at location X, minigame passed) and require it in
getMoneyAfterWashing
- Validate
#(xPlayer.getCoords() - Config.Locations['wash_money'].moneyCut.coords) <= someDistance
- Validate that the player holds
Config.neededItem before paying out
- Add a cooldown and a "currently washing" state per player
Affected file
server.lua - msk_blackmoney:getMoneyAfterWashing
Description
msk_blackmoney:getMoneyAfterWashinginserver.lua(lines 39-87) converts all black money into clean cash with no server-side location, item or permission check:The intended flow requires the player to:
Config.neededItem, checked inside the admin-restricted command at lines 5-13),msk_blackmoney:startBlackmoney/skipSkillCheck).None of that is enforced on the server for the final payout event:
Config.Locations['wash_money']Config.neededItemis only consumed by the admin command; the payout event never validates it{'admin'}restricted, the payout event itself is a plainRegisterNetEventthat any player can trigger directlyAlso note
msk_blackmoney:removeItem(lines 89-95) takes an arbitrary item name from the client and removes it without validating that the item is part of the washing flow — an attacker could use it to destroy arbitrary items in their own inventory (e.g. bypass "must hold the required item" mechanics by removing the needed item mid-flow, or grief themselves), though the primary issue is the wash itself.Impact
adminrestriction on the start command is meaningless because the payout is publicly triggerableSuggested fix
getMoneyAfterWashing#(xPlayer.getCoords() - Config.Locations['wash_money'].moneyCut.coords) <= someDistanceConfig.neededItembefore paying outAffected file
server.lua-msk_blackmoney:getMoneyAfterWashing