Ways for Exploiters to exploit ProximityPrompts

In my opinion, the new ProximityPrompts make developing games much easier, especially for developers with less scripting experience.

I am impressed by the fact that the firing of events specific for the ProximityPrompts can be detected by the server (eg. You can detect on the server directly when a player triggers a Prompt!) which makes lots of stuff easier to script, saves loads of Remotes you would have to create, and will make sure there are no exploitable vulnerabilities when interacting with the server as client, causing exploiters to have a harder time doing their stuff.

My question now is, are exploiters able to somehow exploit these new Prompts for example

  • Skip HoldDuration and directly trigger the Prompt on the server side
  • Ignore the maximum activation distance meaning they could just trigger the prompt without being in its proximity.
  • Anything else I can’t think of.

Exploits are run through the local scripts so basically they can do the point that i quoted below because i had tested running the code in the command bar of roblox studio while playing on client

game.Workspace.Shenoy6:SetPrimaryPartCFrame(CFrame.new(41.78, 3.985, 46.437))

the cframe value is near the part where the ProximityPrompt is parented to.

1 Like

Any property that the client has access to an exploiter can change it. So yes they could essentially change the HoldDuration and change the MaximumActivationDistance to 0 and other things. You just need to use sanity checks on the server to prevent exploiters from getting any further.

I just tried changing all the values from the client, and a local script didnt have a problem with it, but a server script only activated when I was actually near the prompt and activating it legitimately.

1 Like

Somewhat old topic but I didn’t need to create another one when there is useful information already here.

Anyways, exploiters can’t ignore the maximum activation distance. Roblox created a built in sanity check so you have to be within its distance, even if you change that value on the client.

However, exploiters can remove HoldDuration unfortunately. This creates two problems.

  1. Exploiters can rapid fire ProximityPrompts with no cooldowns as long as they’re in proximity.
  2. Exploiters can avoid the HoldDuration in general. Only works if HoldDuration is bigger than 0.

Fortunately, Roblox gives us some events that we can use to create our own anticheat systems to prevent both of these.

First and probably easiest to fix, is the rapid firing. All you need to do is setup a debounce-like system using the given HoldDuration number. If the prompt is fired, don’t allow the client to fire it again until wait(HoldDuration). (Best way I could explain it, as they shouldn’t fire it until the hold duration timer is complete because it takes (HoldDuration) (0.3 seconds, 1 second, etc) to actually trigger.)

Secondly, this one is a bit harder, you can fix both of these problems by using these two events:
image

image

When PromptButtonHoldBegan is triggered, use os.time() or whatever. Then when it’s triggered (completed), use subtract the os.time() from before from os.time() from when it finishes.

For example.

-- Hold Duration is 1
local StarterOsTime = os.time()

wait(1) -- This wait() is just here for replication sake. In reality you won't need it, just use the two events to get both of the os.times that are needed.

if os.time() - StarterOsTime > HoldDuration then
-- it's valid
end

You can also give the client some leg room incase they’re lagging. For example.

if os.time() - StarterOsTime > HoldDuration - 0.1 then
-- it's valid
end

You’d want to have these two connections being handled on the server, so there might be some network lag from client to server. You could also set this up on the client & server just to be overkill for the best results.

Also after some thinking, I don’t think there’d be any network lag. Yes there’d be general network lag (high ping) in some cases, but that wouldn’t make PromptButtonHoldBegan and Triggered fire instantly next to eachother, so odds are you don’t need to add any sort of HoldDuration - 0.1 thing.


Also just to add, nothing else is worth exploiting. HoldDuration is the only thing exploitable currently and it’ll probably be this way forever so it’s best to develop your own anticheat to combat it now rather than wait 6 years for Roblox to do it for you.

Note: Sorry if this whole topic is a mess to read. I originally came here because I wanted to see what was exploitable with these prompts and I also decided to test it out myself and give results so nobody else would have to. I also wrote this at 2:54 am.

2 Likes

Exploiters can exploit proximity prompts through Synapse X because it uses a Powerful C++ API that injects itself through roblox, since the roblox engine is built using C++. SynapseX exploiters can exploit proximity prompts using SynapseX “fireproximityprompt()” function. For example, they can make a proximity prompt have no wait time once it’s triggered like the one in this script.

BTW roblox staff this is a vulnerability through proximitypromptservice


local ProximityPromptService = game:GetService("ProximityPromptService")
function promptfired(p)
fireproximityprompt(p,5000000)
end
ProximityPromptService.PromptButtonHoldBegan:Connect(promptfired)

For anybody else looking for a way to stop exploiters from instantly calling prompts, I made a simple module that handles that:

local Players = game:GetService("Players")
local PromptService = game:GetService("ProximityPromptService")

local promptProtection = {}
local times = {}

local function getPromptData(prompt: ProximityPrompt, player: Player)
	local playerData = times[player]
	
	if not playerData then
		times[player] = {}
		playerData = times[player]
	end
	
	local promptData = playerData[prompt]
	
	if not promptData then
		playerData[prompt] = {}
		promptData = playerData[prompt]
		
		prompt.Destroying:Once(function()
			if playerData then playerData[prompt] = nil end
		end)
	end
	
	return promptData
end

function promptProtection.CanTrigger(prompt: ProximityPrompt, player: Player)
	assert(typeof(prompt) == "Instance" and prompt:IsA("ProximityPrompt"), "Did not pass a valid ProximityPrompt")
	assert(typeof(player) == "Instance" and player:IsA("Player"), "Did not pass a valid Player")
	
	if prompt.HoldDuration == 0 then return true end
	
	local promptData = getPromptData(prompt, player)

	if not promptData.Start then return end

	local holdDuration = promptData.Finish - promptData.Start
	promptData.Start = nil

	return holdDuration >= prompt.HoldDuration - 0.05 -- Tolarance for os.clock inaccuracy
end

PromptService.PromptButtonHoldBegan:Connect(function(prompt, player)
	local promptData = getPromptData(prompt, player)
	promptData.Start = os.clock()
end)

PromptService.PromptButtonHoldEnded:Connect(function(prompt, player)
	local promptData = getPromptData(prompt, player)
	promptData.Finish = os.clock()
end)

Players.PlayerRemoving:Connect(function(player)
	times[player] = nil
end)

return promptProtection

Usage would look something like this:

local Protection = -- Path to module
local prompt = -- Path to prompt

prompt.Triggered:Connect(function(player)
	if not Protection.CanTrigger(prompt, player) then return end
	print("Successfully triggered!")
end)

Hope this helps!

4 Likes