Skip to content

Bind userauth username to the first request#1063

Open
yosuke-wolfssl wants to merge 1 commit into
wolfSSL:masterfrom
yosuke-wolfssl:fix/f_5829
Open

Bind userauth username to the first request#1063
yosuke-wolfssl wants to merge 1 commit into
wolfSSL:masterfrom
yosuke-wolfssl:fix/f_5829

Conversation

@yosuke-wolfssl

Copy link
Copy Markdown
Contributor

Bind userauth username to the first request

Summary

Fixes finding f_5829. In keyboard-interactive (KBI) authentication, the
server bound the auth callback's username to whatever ssh->userName currently
held, not to the username that opened the exchange. DoUserAuthRequest
overwrote ssh->userName on every USERAUTH_REQUEST, so a client could
pipeline a request for a second user while the first user's INFO_REQUEST was
still outstanding and have the callback run against the second username with the
pending challenge.

Root cause

DoUserAuthInfoResponse sources the username from the live session value:

authData.username = (byte*)ssh->userName;
authData.usernameSz = ssh->userNameSz;

while DoUserAuthRequest called wolfSSH_SetUsernameRaw(...) on every request,
with nothing preventing a second USERAUTH_REQUEST from being processed while a
KBI INFO_REQUEST was pending. A pipelined
USERAUTH_REQUEST(alice, kbi) then USERAUTH_REQUEST(bob, kbi) followed by an
INFO_RESPONSE invoked the callback as bob for alice's exchange.

This path is compiled only with --enable-keyboard-interactive
(WOLFSSH_KEYBOARD_INTERACTIVE), which is off by default. For password and
publickey the username and credential travel in the same packet, so only the
split KBI flow was exploitable.

Fix

Match OpenSSH's model (auth2.c, input_userauth_request): the username is
bound by the first userauth request and stays fixed for the rest of the
authentication. A later request that changes it ends the session. With
ssh->userName immutable after the first request, the existing
DoUserAuthInfoResponse assignment is always the originating user, so the swap
is closed without KBI-specific state.

if (ret == WS_SUCCESS && serviceValid) {
    /* Bind the username to the first userauth request. A later request
     * that changes it ends the session, so a pipelined request cannot
     * rebind a pending keyboard-interactive exchange to another user. */
    if (!ssh->userAuthSeen) {
        ret = wolfSSH_SetUsernameRaw(ssh,
                authData.username, authData.usernameSz);
        if (ret == WS_SUCCESS)
            ssh->userAuthSeen = 1;
    }
    else if (authData.usernameSz != ssh->userNameSz
            || WMEMCMP(authData.username, ssh->userName,
                    authData.usernameSz) != 0) {
        WLOG(WS_LOG_DEBUG, "DUAR: username change not allowed");
        (void)SendDisconnect(ssh, WOLFSSH_DISCONNECT_PROTOCOL_ERROR);
        ret = WS_INVALID_STATE_E;
    }
}

The per-request overwrite that previously ran after method dispatch is removed.
The new byte userAuthSeen is appended to the end of struct WOLFSSH and is
zero-initialized by the existing full-struct WMEMSET in SshInit.

Behavior

  • The binding is per connection. A different user opening a new connection is
    unaffected: a fresh WOLFSSH starts with userAuthSeen = 0 and binds
    normally.
  • Same-username multi-method retries (for example publickey then password) are
    unaffected, since the username does not change.
  • A username change within a single connection's authentication is rejected with
    a disconnect, including the pipelined KBI swap.

Tests

Added regression coverage in tests/regress.c driving the server with the
existing in-memory transport harness (no sockets, threads, or real KEX):

  • TestUsernameChangeDisconnects (default build, password method): a second
    request with a different username returns WS_FATAL_ERROR, emits
    MSGID_DISCONNECT, and never reaches the callback. Covers the guard in the
    most-shipped configuration.
  • TestKbUsernameChangeDisconnects (WOLFSSH_KEYBOARD_INTERACTIVE): the
    reported attack. alice's request emits an INFO_REQUEST; bob's pipelined
    request disconnects and never reaches the callback.
  • TestKbSameUserResponseSucceeds (WOLFSSH_KEYBOARD_INTERACTIVE): a normal
    single-user exchange still reaches the callback bound to the originating user.

Verification

  • Builds clean with -Werror in both the default build and
    --enable-keyboard-interactive --enable-sftp.
  • tests/regress.test and tests/api.test pass in both configurations;
    the existing test_wolfSSH_KeyboardInteractive still passes.
  • Negative control: restoring the per-request overwrite makes the new tests
    fail (the second request is accepted), confirming they catch the bug.

@yosuke-wolfssl yosuke-wolfssl self-assigned this Jun 26, 2026
Copilot AI review requested due to automatic review settings June 26, 2026 06:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #1063

Scan targets checked: wolfssh-bugs, wolfssh-src

No new issues found in the changed files. ✅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants