
一括割り当て
ここでは、一括任務の脆弱性とその様子、およびそれらを回避するためのいくつかの方法について見ていきます。まず、簡単にまとめてみましょう。
Mass Assignmentは、APIエンドポイントが関連オブジェクトのどのプロパティをユーザーが変更できるかを制限しない脆弱性です。
この脆弱性は、HTTPパラメータをモデルに自動的にバインドし、検証なしでそのまま使用できるようにするライブラリ/フレームワークを利用する場合に発生する可能性があります。
リクエストからオブジェクトへの自動バインドを使用すると非常に役立つ場合がありますが、ユーザーがアクセスできないように意図されたプロパティがモデルにあると、セキュリティ上の問題が発生する可能性もあります。
例
ここでは、ユーザーが名前やメールアドレスなどの詳細を変更できるウェブページの例を使用します。User モデルは次のように定義されています。
パブリッククラスユーザーモデル {
パブリックロング ID {取得; 設定;}
パブリック文字列名 {取得; 設定;}
パブリック文字列パスワードハッシュ {get; set;}
パブリック文字列電子メールアドレス {get; set;}
パブリックブールは Admin {取得; 設定;}
}
フロントエンド部分はフォームを以下のように定義します。`isAdmin` 値がないことに注意してください。
<form method="POST">
<input name="Id" type="hidden">
<input name="Name" type="text">
<input name="EmailAddress" type="text">
<input type="submit">
</form>
コントローラーはエンドポイントを次のように定義します。`UserModel` をパラメーターとして指定することで、フレームワークが自動的にそれぞれのプロパティをこのモデルにマッピングしてくれます。
[HTTP ポスト]
パブリックブール更新ユーザー (ユーザーモデルモデル)
{
//ユーザーが自分自身だけを更新するようにする
モデル.ID = リクエスト.ユーザー.ユーザーID;
var success = ユーザーサービス.UpdateUser (モデル);
返品成功;
}
ここから、'UserService.updateUser'メソッドは認証に関してそれ以上の検証は行わず、提供されたユーザーオブジェクトを保存するだけだと仮定できます。
(プロパティに値が指定されていない場合は、既存の値が保持されるだけです)
つまり、ユーザーは「isAdmin」を使用してリクエストを送信できます。これにより、現在の値が上書きされ、ユーザーは以下のように管理者になります。
<form method="POST">
<input name="Id" type="hidden" value="666">
<input name="Name" type="text" value="Bad guy">
<input name="EmailAddress" type="text" value="hacker@attacker.com">
<input name="IsAdmin" type="hidden" value="true">
<input type="submit">
</form>
緩和戦略
Mass Assignmentの脆弱性を回避するために考慮すべきいくつかの緩和策を以下に示します。
リクエストモデルにデータモデルを再利用することは避けてください
データモデル (データベースに保存されている場合もあります) は、クライアントとの通信に使用されるモデルとは別にしておくことが重要です。コントローラーへのフォーム送信の処理は、データベースにデータを永続化してそのデータをデータベースでどのように表現するかとはまったく異なります。これにより、フロントエンドとパーシスタンス層の間には、望ましいレベルよりもはるかに高いレベルの結合が生じます。
マッピングでは明示的に指定してください
自動バインディング (またはマッピング) の問題は、明示的なマッピングがないため、モデル上でアクセスできないはずのプロパティを簡単に公開してしまうことです。リクエストモデルとバックエンドの他の部分とのマッピングを明示的に行うことで、この種の公開を最初から防ぐことができます。
これは、リクエストとデータに異なるモデルを使用することで実現できます。リクエストモデルでは、特定のリクエストで許可されていないプロパティを公開してはならないので、リクエストとデータモデルの間で自動マッパーを使用することを妨げるものではありません。
その他の例
以下に、これがどのようなものになるかについて、さまざまな言語でさらにいくつか例を挙げます。
C#-安全でない
パブリッククラスユーザー {
パブリックロング ID {取得; 設定;}
パブリック文字列ファーストネーム {get; set;}
パブリックストリング LastName {取得; 設定;}
パブリック文字列パスワードハッシュ {get; set;}
パブリック文字列国 {取得; 設定;}
パブリック文字列ロール {取得; 設定;}
}
[HTTP ポスト]
パブリックビュー結果編集 (ユーザーユーザー)
{
//指定したとおりにユーザーを保存するだけです
ユーザーサービス.UpdateUser (ユーザー);
OK (); を返します
}
C#-セキュア
パブリッククラスユーザー {
パブリックロング ID {取得; 設定;}
パブリック文字列ファーストネーム {get; set;}
パブリックストリング LastName {取得; 設定;}
パブリック文字列パスワードハッシュ {get; set;}
パブリック文字列国 {取得; 設定;}
パブリック文字列ロール {取得; 設定;}
}
パブリッククラスアップデートユーザービューモデル {
パブリック文字列ファーストネーム {get; set;}
パブリックストリング LastName {取得; 設定;}
パブリック文字列国 {取得; 設定;}
}
[HTTP ポスト]
パブリックビュー結果編集 (ユーザービューモデルユーザーモデルの更新)
{
var ユーザ = リクエスト.ユーザ;
ユーザー.ファーストネーム = ユーザーモデル.ファーストネーム;
ユーザー.苗字 = ユーザーモデル.苗字;
ユーザー.国 = ユーザーモデル.国;
ユーザーサービス.UpdateUser (ユーザー);
OK (); を返します
}
C#-代替-パラメータを除く
パブリッククラスユーザー {
パブリックロング ID {取得; 設定;}
パブリック文字列ファーストネーム {get; set;}
パブリックストリング LastName {取得; 設定;}
パブリック文字列パスワードハッシュ {get; set;}
パブリック文字列国 {取得; 設定;}
パブリック文字列ロール {取得; 設定;}
}
[HTTP ポスト]
パブリックビュー結果編集 ([バインド (含む =「苗字、姓、国」)] ユーザーユーザ)
{
if (リクエスト.ユーザー.ID!= ユーザー.ID) {
返品禁止 (「他のユーザーの変更要求」);
}
var 既存ユーザ = リクエスト.ユーザ;
ユーザー.パスワードハッシュ = 既存のユーザー.パスワードハッシュ;
ユーザー.ロール = 既存のユーザー.ロール;
ユーザーサービス.UpdateUser (ユーザー);
OK (); を返します
}
Java-安全ではありません
パブリッククラスユーザー {
パブリックヒントID;
パブリック文字列ファーストネーム;
パブリック文字列苗字;
パブリック文字列パスワードハッシュ;
パブリックストリングカントリー;
パブリック文字列ロール;
}
@RequestMapping (値 =「/updateUser」、メソッド = requestMethod.POST)
パブリック文字列 updateUser (ユーザーユーザー) {
ユーザーサービス.更新 (ユーザー);
「ユーザー更新ページ」を返します。
}
Java-セキュア
パブリッククラスユーザービューモデル {
パブリック文字列ファーストネーム;
パブリック文字列苗字;
パブリックストリングカントリー;
}
パブリッククラスユーザー {
パブリックヒントID;
パブリック文字列ファーストネーム;
パブリック文字列苗字;
パブリック文字列パスワードハッシュ;
パブリックストリングカントリー;
パブリック文字列ロール;
}
@RequestMapping (値 =「/updateUser」、メソッド = requestMethod.POST)
パブリック文字列更新ユーザ (@AuthenticationPrincipal User CurrentUser, UserViewModel UserViewModel) {
CurrentUser.FirstName = UserViewModel.FirstName;
CurrentUser.lastName = UserViewModel.LastName;
CurrentUser.Country = UserViewModel.Country;
ユーザーサービス. 更新 (現在のユーザー);
「ユーザー更新ページ」を返します。
}
ジャバスクリプト-安全ではありません
app.get ('/user/update', (req, res) => {
var ユーザ = 必須ユーザ;
オブジェクトアサイン (ユーザ、必須ボディ);
ユーザーサービス.更新 (ユーザー);
「ユーザーが更新されました」を返す;
})
ジャバスクリプト-セキュア
app.get ('/user/update', (req, res) => {
var ユーザ = 必須ユーザ;
ユーザー.ファーストネーム = req.body.FIRSTNAME;
user.lastName = req.body.LastName;
ユーザー.country = req.body.country;
ユーザーサービス.更新 (ユーザー);
「ユーザーが更新されました」を返す;
})
パイソン-安全でない
@app .route (「/ユーザー/更新」, メソッド= ['POST'])
def update_user ():
ユーザー = リクエスト.ユーザー
フォーム = request.form.to_dict (flat=true)
キーの場合、form.items () 内の値:
setattr (ユーザ、キー、値)
ユーザーサービス. 更新ユーザー (ユーザー)
リダイレクト (「/user」、コード=201) を返す
パイソン-セキュア