セキュリティ戦略とチートの軽減

*このコンテンツは、ベータ版のAI(人工知能)を使用して翻訳されており、エラーが含まれている可能性があります。このページを英語で表示するには、 こちら をクリックしてください。

Roblox は、クライアントがオブジェクトの制御の物理シミュレーションを監督する分散物理システム を使用しています 、通常はプレイヤーのキャラクターとそのキャラクターの近くに固定されていないオブジェクト。さらに、サードパーティのソフトウェアの使用を通じて、悪用者は、クライアントのデータモデルを操作し、実行中のコードを逆コンパイルして表示するため、クライアントのコードを実行します。

単純に言えば、これは熟練した悪用者がゲームで詐欺を行う可能性のあるコードを実行できることを意味します。それには以下が含まれます:

  • 場プレースの周りで自分のキャラクターをテレポートしています。
  • 安全でない発射 RemoteEvents または獲得しないでアイテムを授与するために呼び出される RemoteFunctions など。
  • キャラクターの WalkSpeed を調整して、本当に速く動くようにします。

限られた デザイン防御 を実装して、一般的な攻撃をキャッチできますが、サーバーがすべての実行経験の最終権限者であるため、より信頼性の高い サーバー側の軽減戦略 を実装することを強く推奨します。

防御的なデザイン戦略

基本的なデザイン決定は、悪用を減少する「最初のステップ」のセキュリティ対策として機能することができます。たとえば、他のプレイヤーを殺すためにポイントを獲得するシューターゲームでは、悪用者が同じ場所にテレポートする群れのボットを作成し、ポイントを素早く殺すことができます。この潜在的な欠陥を考えると、2つのアプローチと予測可能な結果を検討してください:

手法予測可能な結果
ボットを検出しようとするコードを書き、ボットを追跡します。
新しく生成されたプレイヤーでのキルに対するポイント獲得を減少または完全に削除する。

防御的デザインは明らかに完全かつ包括的な解決策ではありませんが、サーバー側の軽減とともに、より広範なセキュリティアプローチに貢献できます。

サーバー側の軽減

可能な限り、 サーバー は、何が「真」であり、世界の現在の状態が何であるかに最終的な判断を下すべきです。クライアントは、もちろん、サーバーに変更を行ったり、アクションを実行したりするようにリクエストできますが、サーバーは結果が他のプレイヤーにレプリケートされる前に、これらの変更/アクションすべてを検証し、承認する必要があります。

特定の物理操作を除き、クライアントのデータモデルの変更はサーバーにレプリケートされないので、メインの攻撃パスは RemoteEventsRemoteFunctions で宣言したネットワークイベントを通じてよくあります。クライアント上で自分のコードを実行している悪用者が、好きなデータでこれらを呼び出すことを忘れないでください。

リモートランタイムタイプ検証

1つの攻撃パスは、悪用者が不正なタイプの入力数で RemoteEvents および RemoteFunctions を呼び出すためです。いくつかのシナリオでは、これにより、サーバー上のコードがこれらのリモートを監視してエラーが発生する可能性があり、悪用者にとって有益です。

リモートイベント/機能を使用すると、サーバー上のパスされた引数の タイプ を検証することで、このタイプの攻撃を防ぐことができます。モジュール "t" 、ここで利用可能 ここ 、この方法でタイプチェックに便利です。たとえば、モジュールのコードが という名前の t 内に存在すると仮定して:

スタータープレイヤースクリプトのローカルスクリプト

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteFunction = ReplicatedStorage:WaitForChild("RemoteFunctionTest")
-- 関数を呼び出すときにパーツの色と位置をパスする Pass part color and position when invoking the function
local newPart = remoteFunction:InvokeServer(Color3.fromRGB(200, 0, 50), Vector3.new(0, 25, 0))
if newPart then
print("The server created the requested part:", newPart)
elseif newPart == false then
print("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

悪用者が使用できる別の一般的な攻撃は、tablesInstance の代わりに送信することです。複雑なペイロードは、通常のオブジェクト参照と同じように見えることができます。

たとえば、価格などのアイテムデータが オブジェクトに保存される体験ショップ システムで、悪用者はフォロー中のことを行うことで、他のすべてのチェックを回避できます:

スタータープレイヤースクリプトのローカルスクリプト

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)) -- 「false 無効なアイテムが提供されました」を出力
-- サーバーに実際のアイテムを送信する (これは通過する!)
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 を介して送信され、リクエストされているコンテキストで有効で論理的であることを確認する必要があります。2つの一般的な例は、体験中のショップ武器ターゲティング システムです。

インエクスペリエンス買う

例えば、「購入」ボタンを含む製品選択メニューのユーザーインターフェイスを備えた体験中のショップシステムを考えてみましょう。ボタンが押されると、クライアントとサーバー間の RemoteFunction を呼び出して、購入をリクエストできます。ただし、エクスペリエンスの最も信頼性の高い管理者である サーバー が、ユーザーがアイテムを購入するのに十分なお金を持っていることを確認することが重要です。

Example purchase flow from client to server through a RemoteEvent
クライアントからサーバーへの例の購入フロー通過 RemoteFunction

武器ターゲティング

戦闘シナリオは、特に狙いとヒット検証を通じて、値の有効性を検証する必要があります。

プレイヤーが別のプレイヤーにレーザービームを発射できるゲームを想像してください。クライアントがサーバーに にダメージを与えるかを伝えるのではなく、サーバーにショットの起源位置と思われる部分/位置を伝えるべきです。サーバーはフォロー中を有効にすることができます:

  • クライアントが報告する 撮影位置 は、サーバー上のプレイヤーのキャラクターの近くにあります。遅延により、サーバーとクライアントがわずかに異なるため、追加の許容が適用する必要があります。

  • クライアントが報告する位置 がヒットする は、サーバー上でクライアントが報告するパーツの 位置 に比較的近いです。

  • クライアントがシュートする位置と、クライアントがシュートする位置の間に静的な障害はありません。このチェックは、クライアントが壁を通して撃つことを試みていないことを保証します。静的な幾何学をチェックするだけで、遅延により有効なショットが拒否されるのを避けることに注意してください。 追加 , あなたは次のようにさらにサーバー側の検証を実装したいかもしれません:

  • プレイヤーが武器を最後に発射した時を追跡し、発射速度が高すぎないことを確認するために有効化します。

  • サーバー上で各プレイヤーの弾薬量を追跡し、発射プレイヤーに十分な弾薬があることを確認して、武器攻撃を実行できることを確認します。

  • チームや「プレイヤー対ボット」戦闘システムを実装した場合、ヒットキャラクターが敵であり、チームメイトではないことを確認してください。

  • ヒットプレイヤーが生きていることを確認します。

  • サーバーに武器とプレイヤーの状態を保存し、発射プレイヤーがリロードやスプリントなどの現在のアクションによってブロックされていないことを確認します。

データストア操作

DataStoreService を使用してプレイヤーデータを保存するエクスペリエンスでは、悪用者は無効なデータや、より不明瞭な方法を利用して、DataStore が正しく保存されないようにすることができます。これは、アイテム取引、マーケットプレイス、および同様のシステムで、アイテムや通貨がプレイヤーのインベントリ持ち物リスト離れる経験で特に悪用される可能性があります。

プレイヤーデータにクライアント入力で影響を与える RemoteEvent または RemoteFunction を介して実行されたアクションが、以フォロー中に基づいてサニタイズされることを確認します:

  • Instance 値は DataStore にシリアライズできず、失敗します。これを防ぐには、タイプ検証 を使用してください。
  • DataStores には データ制限 があります。ランダムな長さのストリングはチェックされ、または制限されて、クライアントによって制限なしの任意のキーがテーブルに追加されないようにする必要があります。
  • テーブルインデックスは NaN または nil ではありません。クライアントによって渡されたすべてのテーブルを反復し、すべてのインデックスが有効であることを確認します。
  • DataStores は、有効な UTF-8 文字のみを受け入れるため、クライアントによって提供されるすべての文字列を utf8.len() でサニタイズして、有効であることを確認する必要があります。utf8.len() は、ユニコード文字を単一の文字として長さを返す; 無効な UTF-8 文字が発見された場合は、nil と無効な文字の位置を返す。無効な UTF-8 文字列が、キーと値としてテーブルに存在することに注意してください。

リモート制限

クライアントがあなたのサーバーを計算的に高コストの操作を完了させたり、DataStoreService を介してレート制限付きサービスのようなRemoteEventを行ったりできる場合、オペレーションが過度に頻繁に呼び出されないように レート制限 を実装することが重要です。クライアントがリモートイベントを最後に呼び出した時間を追跡し、すぐに呼び出された場合は次のリクエストを拒否することで、レート制限を実装できます。

移動検証

競争的なエクスペリエンスの場合、サーバー上でプレイヤーキャラクターの移動を検証して、マップをテレポートしたり、許容される速度より速く移動していないことを確認したいかもしれません。

  1. 1秒単位で、キャラクターの新しい場所を以前にキャッシュされた場所と比較します。

    Image showing moving character's position on a straight path in increments of 1 second
  2. 文字の WalkSpeed (秒ごとのスタッド) を掛けて、最大「許容」距離の変更を決定し、サーバーの遅延に対して寛容を許可するために 1.4 を乗算して、最大「許容」距離の変更を決定します。たとえば、デフォルトの WalkSpeed 16では、許容できるデルタは22です。

    Image showing tolerable change in distance based on character's walk speed
  3. 実際の距離デルタと許容できるデルタを比較し、以下の手順に従います:

    • 許容できるデルタのために、次の増分チェックの準備のために、キャラクターの新しい場所をキャッシュします。
    • 予期せぬまたは耐えられないデルタ (潜在的なスピード/テレポート悪用):
      1. 過度のサーバー遅延またはその他の非悪用要因による「偽陽性」によって罰せられるのではなく、プレイヤーに別の「犯罪数」値を増加させ、過度のサーバー遅延またはその他の非悪用要因による「偽陽性」を結果として減少させます。
      2. 30-60秒の期間に大量の違反が発生した場合、Kick() 経験からプレイヤーを完全にリセットします;そうでない場合は、「違反数」カウントをリセットします。不正行為に対してプレイヤーを追放するときは、イベントを記録することで、どれくらいのプレイヤーが影響を受けたかを追跡できるため、最善の方法です。