Roblox는 클라이언트가 제어하는 물체의 물리적 시뮬레이션을 관리하는 분산 물리 시스템을 사용하며, 일반적으로 플레이어의 캐릭터와 그 캐릭터 근처의 고정되지 않은 개체입니다.또한 제3자 소프트웨어의 사용을 통해 악용자는 클라이언트에서 임의의 Luau 코드를 실행하여 클라이언트의 데이터 모델을 조작하고 그 위에서 실행되는 코드를 디컴파일하고 보기 수 있습니다.
집단적으로, 이는 숙련된 악용자가 게임에서 속임수를 쓸 수 있는 코드를 실행할 수 있다는 것을 의미합니다(포함:
- 플레이스주변에서 자신의 캐릭터를 순간이동합니다.
- 보안되지 않은 발사 RemoteEvents 또는 획득하지 않고 아이템을 수여하기 위해 호출 RemoteFunctions와 같은 경우
- 캐릭터의 WalkSpeed를 조정하여 매우 빠르게 이동합니다.
일반적인 공격을 캡처하기 위해 제한된 디자인 방어를 구현할 수는 있지만, 서버가 실행 중인 모든 경험의 최종 권한이므로 더 신뢰할 수 있는 서버 측면 완화 전략을 구현하는 것이 좋습니다.
방어적 디자인 전략
기본 디자인 결정은 악용을 억제하기 위한 "첫 번째 단계" 보안 조치로 사용될 수 있습니다.예를 들어, 플레이어가 다른 플레이어를 처치하여 포인트를 얻는 슈터 게임에서 악용자는 같은 장소로 순간이동하는 많은 봇을 만들어 포인트를 신속하게 죽일 수 있습니다.이 잠재적 취약점을 고려하여 두 가지 접근법과 예측 가능한 결과를 고려하십시오:
접근법 | 예상 가능한 결과 |
---|---|
코드를 작성하여 봇을 감지하려는 시도를 통해 봇을 추적합니다. | |
새로 생성된 플레이어에 대한 킬에 대한 점수 이득을 줄이거나 제거합니다. |
방어 디자인은 명백히 완벽하거나 포괄적인 솔루션이 아니지만, 서버 측 완화와 함께 더 광범위한 보안 접근 방식에 기여할 수 있습니다.
서버 측 완화
가능한 한, 서버 는 무엇이 "진실"이고 현재 세계의 상태가 무엇인지에 대한 최종 판결을 내려야 합니다.클라이언트는 물론 서버에게 변경 사항을 요청하거나 액션수행하도록 요청할 수 있지만, 서버는 결과가 다른 플레이어에 복제되기 전에 이러한 변경/작업 각각을 검증하고 승인해야 합니다.
특정 물리 작업을 제외하고 클라이언트의 데이터 모델에 대한 변경은 서버에 복제되지 않으므로 주요 공격 경로는 종종 RemoteEvents 및 RemoteFunctions로 선언한 네트워크 이벤트를 통해 이루어집니다.클라이언트에서 자체 코드를 실행하는 악성 코드가 원하는 데이터로 이들을 호출할 수 있다는 점을 기억하십시오.
원격 런타임 유형 유효성 검사
한 공격 경로는 악용자가 잘못된 입력인수로 RemoteEvents 및 RemoteFunctions를 호출하도록 하는 것입니다.일부 시나리오에서는 서버에서 이러한 원격을 수신하는 코드가 악용자에게 유리한 방식으로 오류가 발생할 수 있습니다.
원격 이벤트/함수를 사용할 때, 서버에서 전달된 인수의 유형 을 검사하여 이러한 유형의 공격을 방지할 수 있습니다.여기에서 사용할 수 있는 모듈 "t" 는 이러한 방식으로 형식 검사에 유용합니다.예를 들어, 모듈의 코드가 내부에서 명명된 t 안에 존재한다고 가정하면:
스타터플레이어스크립트의 LocalScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")local remoteFunction = ReplicatedStorage:WaitForChild("RemoteFunctionTest")-- 함수를 호출할 때 파트 색상과 위치 전달 Pass part color and position when invoking the functionlocal newPart = remoteFunction:InvokeServer(Color3.fromRGB(200, 0, 50), Vector3.new(0, 25, 0))if newPart thenprint("The server created the requested part:", newPart)elseif newPart == false thenprint("The server denied the request. No part was created.")end
ServerScriptService의 스크립트
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
local remoteFunction = ReplicatedStorage:WaitForChild("RemoteFunctionTest")
local t = require(ReplicatedStorage:WaitForChild("t"))
-- 불필요한 오버헤드를 피하기 위해 미리 유형 유효성 검사기를 만들어 보세요
local createPartTypeValidator = t.tuple(t.instanceIsA("Player"), t.Color3, t.Vector3)
-- 전달된 속성으로 새 부품 만들기
local function createPart(player, partColor, partPosition)
-- 전달된 인수를 형식 검사하기
if not createPartTypeValidator(player, partColor, partPosition) then
-- 형식 검사가 여기에서 실패하면 조용히 "false"를 반환합니다
-- 쿨다운 없이 오류를 발생시키면 서버가 느려지도록 남용할 수 있습니다
-- 대신 클라이언트 피드백을 제공하세요!
return false
end
print(player.Name .. " requested a new part")
local newPart = Instance.new("Part")
newPart.Color = partColor
newPart.Position = partPosition
newPart.Parent = Workspace
return newPart
end
-- 원격 함수의 콜백에 "createPart()" 바인딩
remoteFunction.OnServerInvoke = createPart
데이터 검증
악용자가 시작할 수 있는 또 다른 공격은 기술적으로 유효한 유형을 보내지만 매우 크거나 길거나 그렇지 않으면 잘못된 형식으로 만드는 것입니다.예를 들어, 서버가 길이에 비례해 확장되는 문자열에 대해 비싼 작업을 수행해야 하는 경우, 악용자는 서버를 늦추기 위해 엄청나게 큰 또는 잘못 형식화된 문자열을 보낼 수 있습니다.
마찬가지로, 둘 모두 와 는 서로 다른 문제를 일으킬 수 있지만, 악용자가 이들을 보내면 팔로잉같은 함수를 통해 올바르게 처리되지 않으면 둘 다 심각한 문제를 일으킬 수 있습니다.
local function isNaN(n: number): boolean
-- NAN은 결코 자체와 같지 않습니다
return n ~= n
end
local function isInf(n: number): boolean
-- 숫자는 -inf 또는 inf일 수 있습니다
return math.abs(n) == math.huge
end
악용자가 사용할 수 있는 또 다른 일반적인 공격은 tables 대신 Instance 을 보내는 것입니다.복잡한 페이로드는 일반적인 개체 참조와 유사한 것을 모방할 수 있습니다.
예를 들어, 가격과 같은 아이템 데이터가 경험 내 상점 시스템에 저장된 시스템에 NumberValue에서 악용하면 다른 모든 검사를 우회할 수 있습니다.
스타터플레이어스크립트의 LocalScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")local itemDataFolder = ReplicatedStorage:WaitForChild("ItemData")local buyItemEvent = ReplicatedStorage:WaitForChild("BuyItemEvent")local payload = {Name = "Ultra Blade",ClassName = "Folder",Parent = itemDataFolder,Price = {Name = "Price",ClassName = "NumberValue",Value = 0, -- 부정적인 값도 사용할 수 있으며, 결과적으로 통화를 주는 대신 받지 않습니다!},}-- 서버에 악성 페이로드 전송(이는 거부됩니다)print(buyItemEvent:InvokeServer(payload)) -- 출력 "잘못된 항목 제공"-- 서버에 실제 아이템 전송(이것은 통과됩니다!)print(buyItemEvent:InvokeServer(itemDatafolder["Real Blade"])) -- Outputs "true" and remaining currency if purchase succeeds
ServerScriptService의 스크립트
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local itemDataFolder = ReplicatedStorage:WaitForChild("ItemData")
local buyItemEvent = ReplicatedStorage:WaitForChild("BuyItemEvent")
local function buyItem(player, item)
-- 전달된 항목이 조작되지 않고 ItemData 폴더에 있는지 확인
if typeof(item) ~= "Instance" or not item:IsDescendantOf(itemDataFolder) then
return false, "Invalid item provided"
end
-- 서버는 그런 다음 아래의 예제 흐름에 따라 구매를 처리할 수 있습니다
end
-- 원격 함수의 콜백에 "buyItem()" 바인딩
buyItemEvent.OnServerInvoke = buyItem
값 유효성 검사
유효성 검사 외에도 유형 및 데이터 를 확인하고, 값 을 통해 RemoteEvents 및 RemoteFunctions 를 통과하여 요청된 컨텍스트에서 유효하고 논리적인지 확인해야 합니다.두 가지 일반적인 예는 경험 내 상점 과 무기 타겟팅 시스템입니다.
경험 내 둘러보다
예를 들어, 구매 버튼이 있는 제품 선택 메뉴와 같은 경험 내 상점 시스템을 사용자 인터페이스로 고려하십시오.버튼을 누르면 클라이언트와 서버 사이에서 RemoteFunction를 호출하여 구매를 요청할 수 있습니다.그러나 경험의 가장 신뢰할 수 있는 관리자인 서버 가 사용자가 아이템을 구매할 충분한 돈을 가지고 있음을 확인해야 합니다.

무기 타깃팅
전투 시나리오는 특히 조준 및 타격 유효성 검사를 통해 값을 유효성 검사해야 합니다.
플레이어가 다른 플레이어에게 레이저 빔을 발사할 수 있는 게임을 상상해 보십시오.클라이언트가 서버에 누가 피해를 입힐지 말하는 대신, 서버에 발사의 원본 위치와 서버가 생각하는 부분/위치를 말해야 합니다.서버는 팔로잉검사할 수 있습니다:
클라이언트가 보고하는 위치 발사 는 서버에서 플레이어의 캐릭터 근처에 있습니다.지연으로 인해 서버와 클라이언트가 약간 다르게 작동하므로 추가 용량이 적용해야 합니다.
클라이언트가 보고하는 위치 타격 은 서버에서 클라이언트가 타격하는 부품 의 위치와 비슷하게 가깝습니다.
클라이언트가 발사하는 위치와 클라이언트가 발사하는 위치 사이에 정적 장애물이 없습니다.이 검사는 클라이언트가 벽을 통과하려고 하지 않는지 확인합니다.지연으로 인해 유효한 샷이 거부되지 않도록 정적 기하 검사만 수행해야 합니다. Note that this should only check static geometry to avoid valid shots being rejected due to latency. 추가로 , 다음과 같은 서버 측 유효성 검사를 구현하려는 경우가 있을 수 있습니다:
플레이어가 무기를 발사한 마지막 시점을 추적하고 유효성을 검사하여 너무 빨리 발사하지 않도록 합니다.
서버에서 각 플레이어의 탄약 수량을 추적하고 발사 플레이어가 무기 공격을 수행할 탄약이 충분하다는 것을 확인합니다.
팀 또는 플레이어 대 봇 전투 시스템을 구현했으면 타격 캐릭터가 적이지 팀원이 아닌지 확인하십시오.
타격 플레이어가 살아 있는지 확인합니다.
서버에 무기 및 플레이어 상태를 저장하고 발사 플레이어가 재장전 또는 스프린트와 같은 현재 작업에 의해 차단되지 않는지 확인합니다.
데이터 저장소 조작
플레이어 데이터를 저장하기 위해 DataStoreService 를 사용하는 경험에서, 악용자는 유효하지 않은 데이터 및 더 불명확한 메서드를 이용하여 플레이어 DataStore 가 올바르게 저장되지 않도록 방지할 수 있습니다.아이템 거래, 마켓플레이스 및 유사한 시스템에서 아이템이나 통화가 플레이어의 인벤토리를 떠나는 경험에서 특히 악용될 수 있습니다.
플레이어 데이터에 클라이언트 입력을 통해 수행된 모든 작업이 팔로잉따라 정화되도록 하십시오: RemoteEvent 또는 RemoteFunction 를 통해 플레이어 데이터에 영향을 주는 모든 작업을 정화하십시오.
- DataStores 에는 데이터 제한이 있습니다.임의 길이의 문자열은 검사되고/제한되어야 하며, 클라이언트에서 제한 없는 임의 키를 테이블에 추가할 수 없도록 해야 합니다.
- 테이블 인덱스는 NaN 또는 nil 일 수 없습니다. 클라이언트에서 전달된 모든 테이블을 반복하고 모든 인덱스가 유효한지 확인하십시오.
- DataStores 는 유효한 UTF-8 문자만 허용하므로 클라이언트를 통해 제공된 모든 문자열을 utf8.len() 를 통해 정규화하여 유효한지 확인해야 합니다.utf8.len()는 유니코드 문자를 단일 문자로 처리하여 문자열의 길이를 반환합니다; 유효하지 않은 UTF-8 문자가 발견되면 nil와 유효하지 않은 문자의 위치가 반환됩니다.유효하지 않은 UTF-8 문자열이 테이블에서 키와 값으로 나타날 수도 있다는 점에 유의하십시오.
원격 속도 제한
클라이언트가 서버에서 계산적으로 비용이 많이 드는 작업을 완료하거나 속도 제한 서비스인 를 통해 액세스할 수 있는 경우, 작업이 너무 자주 호출되지 않도록 하기 위해 속도 제한 을 구현해야 합니다.클라이언트가 원격 이벤트를 마지막으로 호출한 시기를 추적하고 너무 빨리 호출되면 다음 요청을 거부하여 속도 제한을 구현할 수 있습니다.
이동 유효성 검사
경쟁적인 경험의 경우, 서버에서 플레이어 캐릭터 이동을 유효성 검사하여 맵을 순회하거나 허용 가능한 속도보다 빠르게 이동하지 않도록 할 수 있습니다.
1초 증가로, 캐릭터의 새 위치를 이전에 캐시된 위치와 비교합니다.
실제 거리 차이를 허용 가능한 차이와 비교하고 다음과 같이 진행합니다:
- 허용 가능한 델타를 위해 다음 증가된 확인준비하기 위해 문자의 새 위치를 캐시합니다.
- 예기치 않거나 참을 수 없는 델타(잠재적 속도/텔레포트 악용):
- 플레이어에 대한 별도의 "범죄 수" 값을 증가시키고, 극단적인 서버 대기 시간이나 다른 비공격 요소로 인해 발생하는 "거짓 양성"으로 처벌하지 않습니다.
- 30~60초 동안 많은 범죄가 발생하면 Kick()하고, 그렇지 않으면 "범죄 수" 카운트를 재설정합니다.부정 행위로 플레이어를 추방할 때 이벤트를 기록하여 얼마나 많은 플레이어가 영향을 받았는지 추적할 수 있는 모범 사례입니다.