CyberFix Note
セキュアコーディング

マスアサインメント(Mass Assignment)の仕組みと対策、自動バインドの副作用を許可リストで塞ぐ

対象の目安: Web/APIを実装する開発者 / 実務

ソウ攻撃・脆弱性リサーチ担当
・ 約14分で読めます
マスアサインメント(Mass Assignment)の仕組みと対策、自動バインドの副作用を許可リストで塞ぐ

マスアサインメント(Mass Assignment)は、リクエストで受け取った複数のパラメータを、フレームワークがオブジェクトの属性へまとめて束縛(バインド)する便利な仕組みが、公開すべきでない属性の書き換えまで許してしまう欠陥です。登録や更新のフォームやAPIで、受け取った項目をそのままモデルに流し込む実装は記述が短くて済みますが、その一括代入に許可の線引きがないと、roleis_adminのようにサーバ側だけで決めるべき属性を、リクエストに項目を足すだけで上書きされます。

この記事は、Webアプリやそのバックエンドを実装する開発者に向けて、なぜ自動バインドがそのまま脆弱性になるのかという機構から、権限昇格やデータ改ざんへの筋道、そして許可リストと入力専用オブジェクトによる対策までを順に整理します。攻撃を再現する手順ではなく、どこで受け入れる属性を絞れば設計で塞げるのかに焦点を当てます。

マスアサインメントが成立する機構

多くのWebフレームワークは、リクエストのボディやフォームで送られたキーと値の対を、対応する名前のオブジェクト属性へ自動で割り当てる機能を備えています。User(name, email)のように一つずつ代入を書かずに、受け取ったパラメータの集合をそのままモデルの生成や更新へ渡せるため、記述量が減ります。マスアサインメントは、この一括代入に受け入れる属性の線引きがないときに成立します。

問題は、リクエストに含められる項目を送信者が自由に決められる点にあります。フォームの画面に表示された入力欄だけがリクエストの中身を決めるわけではなく、リクエストのボディへ任意のキーを足せます。そのため、モデルにroleis_adminという属性があり、それが自動バインドの対象に入っていると、画面に存在しない項目でもリクエストへ加えるだけで値が届き、サーバ側だけで決めるはずだった属性が上書きされます。原因は、送信者が制御できる入力を、内部だけで管理すべき属性まで含めて無条件に信頼している点にあります。

MITREのCWE-915(Improperly Controlled Modification of Dynamically-Determined Object Attributes)は、複数のオブジェクト属性を指定する入力を受け取りながら、どの属性を変更してよいかを適切に制御しない欠陥と定義しています。オブジェクトが内部利用だけを想定した属性を持つ場合、その意図しない変更が脆弱性につながると説明し、Ruby on Railsでのmass assignment、Spring MVCやASP.NET MVCでのautobindingを別称として挙げています。

権限昇格とデータ改ざんへの筋道

自動バインドで書き換えられる属性が、アプリケーションの判断や残高のような意味を持つ値だと、上書きはそのまま実害になります。典型は権限の昇格です。利用者の登録や更新のリクエストにrole=adminis_admin=trueを足すと、一般利用者として作られるはずのアカウントが管理者権限を得ます。OWASPのMass Assignment Cheat Sheetも、登録フォームにisAdmin=trueを加えて管理者権限を設定する例を挙げています。

金額や状態を表す属性でも同じことが起きます。注文や予約のリクエストにaccount_balancetotal_priceのような属性を混ぜて上書きすれば、支払い前の値を書き換えるデータ改ざんになります。user_idowner_idのような所有者を示す属性を差し替えられると、本来は自分のものではない資源に紐づく形でデータが作られ、他人の資源への操作へ発展します。is_verifiedemail_confirmedのように、本来はサーバ側の処理を経て立てるフラグを直接立てられると、検証の工程を飛ばした状態が作られます。

OWASPのMass Assignment Cheat Sheetは、悪用が現実的になる条件として、送信者が機微な属性の名前を推測できること、モデルのフィールドをソースコードなどから把握できること、対象オブジェクトが空のコンストラクタを持つことを挙げています。名前が推測されにくくても、それ自体は認可の代わりにならないため、受け入れる属性を絞る対策と組み合わせて考えます。

OWASPの分類での位置づけ

マスアサインメントは、OWASP API Security Top 10の2019年版でAPI6:2019 Mass Assignmentとして独立した項目でした。

OWASP API Security Top 10の2019年版API6:2019 Mass Assignmentは、クライアントの入力をオブジェクトの属性へ適切な検証なしに束縛することで生じる問題と説明しています。攻撃者が本来公開されないはずの機微な属性を上書きでき、権限昇格やデータ改ざんに至りうるとし、対策として自動バインドの制限、更新可能な属性の許可リスト化、機微な属性の拒否、入力の厳格な検証を挙げています。

2023年版では、この項目は読み取り側の欠陥だった旧API3:2019 Excessive Data Exposureと統合され、API3:2023 Broken Object Property Level Authorizationという一つの項目にまとめられました。属性の単位で読み取りと書き込みの認可を確かめるという考え方に整理し直したもので、マスアサインメントは書き込み側の面として位置づけられています。

OWASP API Security Top 10の2023年版API3:2023 Broken Object Property Level Authorizationは、以前のExcessive Data ExposureとMass Assignmentを統合した項目です。利用者が読み取るべきでない機微な属性を公開してしまう面と、利用者が変更してはならない機微な属性の値を追加や変更や削除できてしまう面の両方を、オブジェクトの属性単位での認可の欠落として一つにまとめて扱っています。

この整理は、識別子で選ばれた資源そのものへの認可の欠落であるIDOR(BOLA)とマスアサインメントを、認可という同じ土台の上で区別して考えるのに役立ちます。前者は資源の単位で、後者は資源の属性の単位で許可を確かめます。IDORとアクセス制御の考え方は、次の記事で整理しています。

あわせて読みたい

IDORとアクセス制御の不備、識別子を差し替えられても守れる認可の作り方

危険な実装と安全な実装の対比

対策の核は、受け取った項目をそのままモデルへ流し込まず、受け入れる属性を明示することにあります。危険な形と安全な形を疑似コードで対比すると差がはっきりします。危険なのは、リクエストの中身を丸ごとモデルへ渡す形です。

# 危険: 受け取ったパラメータを丸ごとバインドする
user = User.new(request.params)   # role や is_admin も届いてしまう
user.save()

安全な形は、受け入れる属性の名前を許可リストで並べ、それ以外を捨ててからモデルへ渡します。

# 安全: 許可した属性だけを取り出してバインドする
allowed = ["name", "email"]
safe = pick(request.params, allowed)   # 未知のキーは落とす
user = User.new(safe)
user.save()

さらに一歩進めるなら、入力専用のオブジェクト(DTOやビューモデル)を定義し、そこに編集してよい属性だけをフィールドとして持たせます。リクエストはこの入力専用オブジェクトへ束縛し、検証を通したうえで、必要な属性だけをドメインのモデルへ写します。これにより、モデル側にどんな属性が増えても、入力から届く経路は入力専用オブジェクトのフィールドに限られます。読み取り専用にすべきuser_idroleは、リクエストからは受け取らず、認証情報やサーバ側のロジックから設定します。

外部から来た値をどこで検証し、どこから信頼するかという設計の土台は、次の記事とあわせて読むと固まります。

あわせて読みたい

入力バリデーションと出力エスケープの原則。入口で検証し、出口で文脈別にエスケープする

フレームワーク別の勘所

主要なフレームワークは、マスアサインメントを絞る仕組みを備えています。OWASPのMass Assignment Cheat Sheetが挙げる代表的な手立てを、許可リストか拒否リストかで整理します。

フレームワーク手立て方向
Ruby on RailsStrong Parametersのrequirepermitで通す属性を指定する許可リスト
Spring MVCsetAllowedFields()で許可、setDisallowedFields()で拒否許可/拒否
DjangoModelFormのfieldsで通すフィールドを列挙する許可リスト
Laravel(PHP)モデルの$fillableで許可、$guardedで拒否許可/拒否
Node.js(Mongoose)_.pick()で属性を選ぶ、専用プラグインで保護する許可リスト
Jackson/GSONデシリアライズ時の除外アノテーションで属性を落とす拒否

拒否リストは、守るべき属性を漏れなく列挙し続ける必要があるため、モデルに属性が増えたときに更新を忘れると穴が開きます。許可リストは、受け入れる属性を明示し、それ以外を既定で落とすため、新しい属性が増えても自動的に外部からは書き込めない側へ倒れます。どちらも使える場面では許可リストを基本にすると、抜けが露出につながりにくくなります。

Ruby on RailsのAction Controller Overviewは、Strong Parametersについて、明示的に許可するまでパラメータをActive Modelのマスアサインメントに使えなくする仕組みだと説明しています。permitで通す属性を並べ、許可していない属性を含むマスアサインメントを試みるとActiveModel::ForbiddenAttributesErrorが発生するとしています。これにより、機微な属性の意図しない更新を防ぎます。

歴史的な事例として、2012年にGitHubで起きたマスアサインメントの事案が知られています。公開鍵を更新するフォームのリクエストに追加の項目を混ぜることで、本来は変更できないはずの属性を書き換えられる状態でした。研究者がこの欠陥を実証として示し、GitHubはリクエストのパラメータを適切に確認していなかったことが原因だったと説明しています。便利な自動バインドに許可の線引きがないと、規模の大きなサービスでも権限を越えた書き込みが成立しうることを示す例です。

対策の組み立てとテスト観点

対策は、受け入れる属性を絞る作業を実装の各所へ散らさず、順を追って組み立てると抜けを減らせます。

  1. 1

    外部入力をモデルへ束縛している箇所を洗い出す

    登録や更新のエンドポイントで、受け取ったパラメータの集合をそのままモデルの生成や更新へ渡している処理を探します。フォームだけでなく、JSONを受けるAPIの経路も対象です。

  2. 2

    受け入れる属性を許可リストで明示する

    各エンドポイントで通してよい属性の名前を列挙し、それ以外を落とします。RailsのStrong Parameters、Djangoのfields、SpringのsetAllowedFields()など、フレームワークの許可リスト機構を使います。

  3. 3

    入力専用のオブジェクトを分ける

    編集してよい属性だけを持つDTOやビューモデルを定義し、リクエストはそこへ束縛します。ドメインのモデルへは、検証を通した属性だけを写します。

  4. 4

    読み取り専用属性をサーバ側で強制する

    roleやuser_id、各種の検証フラグ、金額のような属性は、リクエストからは受け取らず、認証情報やサーバ側のロジックで設定します。入力から来た同名の値は無視します。

  5. 5

    想定外の項目を送って弾かれるか検証する

    許可リストに載っていない属性(roleやis_adminなど)をリクエストに足して送り、その値が反映されないこと、または400で拒否されることを、許可された自組織の環境で確認します。更新系の経路も同じ観点で確かめます。

テストの観点は、正常な入力が通ることの確認に加えて、想定していない項目を混ぜたリクエストが無視または拒否されることの確認まで含めます。未知のフィールドを含むリクエストを400で弾く厳格なパースを採ると、この確認が明確になります。API全体でこの属性単位の認可が抜けやすい理由は、関連記事でも整理しています。

あわせて読みたい

APIセキュリティの基本。認証認可・レート制限・入力検証とOWASP API Top 10で守る

まとめ

マスアサインメント(CWE-915)は、リクエストのパラメータをフレームワークがオブジェクトの属性へ自動で束縛する便利な仕組みが、公開すべきでない属性の書き換えまで許してしまう欠陥です。roleis_adminaccount_balanceuser_idのようにサーバ側だけで決めるべき属性を、リクエストへ項目を足すだけで上書きされ、権限昇格やデータ改ざんにつながります。OWASP API Security Top 10では2019年版のAPI6として独立し、2023年版ではAPI3 Broken Object Property Level Authorizationに統合され、属性単位の認可の欠落として整理されています。

対策の軸は、受け取った項目をそのままモデルへ流し込まないことです。受け入れる属性を許可リストで明示し、入力専用のDTOやビューモデルを分け、読み取り専用の属性はサーバ側で強制します。フレームワークの許可リスト機構を基本に据え、想定外の項目を送って弾かれるかをテストで確かめれば、単一の対策に頼らずに書き換えの経路を塞げます。

マスアサインメント対策で確認したいポイント

  • 受け取ったパラメータをそのままモデルの生成や更新へ渡している箇所を洗い出したか
  • 各エンドポイントで受け入れる属性を許可リストで明示し、それ以外を落としているか
  • 拒否リストではなく許可リストを基本にし、属性が増えても外部から書けない側へ倒れる設計か
  • 編集してよい属性だけを持つ入力専用のDTOやビューモデルを分けているか
  • roleやuser_id、検証フラグ、金額などの読み取り専用属性をサーバ側で強制しているか
  • 未知のフィールドを含むリクエストを無視または400で拒否する厳格なパースにしているか
  • 許可された自組織の環境で、想定外の属性を送って反映されないことを検証したか

Webアプリの代表的な弱点を全体像として押さえたい場合は、次の記事が入口になります。

あわせて読みたい

OWASP Top 10とは。開発者が押さえるべきWebの代表的リスクと対策を一気に理解する

出典・参考

この記事をシェア

関連する記事

セキュアコーディング

IDORとアクセス制御の不備、識別子を差し替えられても守れる認可の作り方

IDOR(BOLA)は、識別子で受け取った資源を要求元がアクセスしてよいか確かめずに直接参照することで成立します。OWASP A01 Broken Access Controlとの関係、サーバー側でのオブジェクト単位の認可、既定は拒否、推測困難なIDは認可の代替にならない点まで、Web/API開発者向けに整理します。