マスアサインメント(Mass Assigment)

# マスアサインメントとは？

マスアサインメントは特定のフレームワークを使用する時、HTTPリクエストを通じてクライアントから渡されたパラメータの値をプログラムコード内の変数やオブジェクトの属性に自動的に割り当てる機能を指します。これにより、エンジニアはコードの簡潔性を高め、各パラメータの値を手動でオブジェクトに割り当てる手間を大幅に減らせます。

# マスアサインメントの例: Ruby on Rails

Ruby on Rails(バージョン7基準)は、このマスアサインメントの機能を利用して、1行のコードで簡単にパラメータの値をモデルの属性に割り当てることができるメカニズムを提供します。

例えば、ユーザーが会員登録フォームを通じて提出したデータはコントローラーに渡され、`User`モデルの属性にマッピングされます。

**View**

```ruby
# app/views/users/new.erb
<%= form_with(model: @user, url: users_path, method: "post") do |form| %>
  <%= form.label :email, "メール" %>
  <%= form.email_field :email %>

  <%= form.label :password, "パスワード" %>
  <%= form.password_field :password %>

  <%= form.submit "会員登録" %>
<% end %>
```

**Controller - マスアサインメントを使用しない場合**

```ruby
# app/controllers/users_controller.rb
class UsersController < ApplicationController
  def create
    @user = User.new
    @user.email = params[:user][:email]
    @user.password = params[:user][:password]

    if @user.save
      redirect_to success_page_path
    else
      render :new
    end
  end
end
```

**Controller - マスアサインメントを使用する場合**

```ruby
# app/controllers/users_controller.rb
class UsersController < ApplicationController
  def create
    @user = User.new(params[:user])

    if @user.save
      redirect_to success_page_path
    else
      render :new
    end
  end
end
```

# マスアサインメントの脆弱性

マスアサインメントの手軽さの裏には、深刻なセキュリティリスクが存在します。例えば、ユーザーモデルに管理者権限を付与する`is_admin`フィルドがある場合は、悪意のあるユーザーがフォームデータを操作して自分のアカウントに管理者権限を付与することができます。

```ruby
# app/controllers/users_controller.rb
class UsersController < ApplicationController
  def create
    @user = User.new(params[:user]) # is_admin: trueキー値ペアが含まれています。
    if @user.save
      redirect_to success_page_path
    else
      render :new
    end
  end
end
```

上記のコードでコントローラーはクライアントから渡された`params[:user]`ハッシュにある全てのキー値ペアを`User`インスタンスに割り当てます。この時、悪意のあるユーザーがHTTPリクエストを操作して`is_admin`の値を`true`に設定すると、コントローラーはこれをそのままデータベースに保存し、悪意のあるユーザーは簡単に管理者権限を取得することができます。

ウェブブラウザのようなクライアント環境はいつでもアクセスして開発者ツールやスクリプトなどでデータを操作することができるので、ここで悪意のあるユーザーは簡単にHTTPリクエストを操作することができます。例えば、元の会員登録フォームには`email`と`password`フィールドだけが公開されていますが、悪意のあるユーザーは次のような方法で`is_admin`フィールドを追加することができます。

1. HTML操作でリクエスト
    
    開発者ツールのElementsパネルで`is_admin`の`input`タグを追加後、フォームを送信してサーバーにリクエストを送ることができます。
    
    ```jsx
    <input type="hidden" name="user[is_admin]" value="true">
    ```
    
2. JavaScript操作でリクエスト
    
    開発者ツールのConsoleパネルで`is_admin`のフォームデータを生成し、fetch APIでサーバーにリクエストを送ることができます。
    
    ```jsx
    // JavaScriptを利用してフォームデータを生成
    let formData = new FormData();
    formData.append('email', 'minimabot@example.com');
    formData.append('password', 'secure_password');
    formData.append('is_admin', 'true');
    
    // fetch APIでリクエスト
    fetch('/sign_up',
      method: 'POST',
      body: formData
    })
    .then(response => response.json())
    .then(data => console.log(data))
    .catch((error) => console.error('Error:', error));
    ```
    
3. 直接HTTPリクエスト
    
    開発者ツールのNetworkタブでフォーム送信のHTTPリクエストを分析します。そして、curlのようなツールでパラメータに`is_admin`を含めt直接サーバーにリクエストを送ることができます。
    
    ```jsx
    curl -X POST -d "email=minimabot@example.com&password=secure_password&is_admin=true" https://example.com/sign_up
    ```
    

# 対応: Strong Parameter

Railsはこのような脆弱性を補強するためにRails４バージョンからStrong Parametersを導入しました。これにより、エンジニアは許可されたパラメータのみを明示的に宣言し、予期しないマスアサインメントを防ぐことができます。

```ruby
# app/controllers/users_controller.rb
class UsersController < ApplicationController
  def create
    @user = User.new(user_params)

    if @user.save
      redirect_to success_page_path
    else
      render :new
    end
  end

  private

  def user_params
	# 許可されたパラメータのみ
    params.require(:user).permit(:email, :password)
  end
end
```
上記の`user_params`メソッドは Strong parameters の良い例です。`permit`メソッドを使って、`email`と`password`フィールドのみを許可し、他の全てのフィールドは無視されるか拒否されます。

# 結論

一括割り当ては開発の効率を高める便利な機能ですが、慎重に使う必要があります。RailsのStrong Parametersのようにフレームワークが提供するセキュリティ機能を正確に理解して適切に活用しましょう。 そうすれば、コードの簡潔性だけでなく、ウェブアプリケーションのセキュリティを大幅に向上させることができます。

# 参考
https://cheatsheetseries.owasp.org/cheatsheets/Mass_Assignment_Cheat_Sheet.html