Skip to content

[Security] Pre-auth keyboard-interactive num-prompts drives new String[N] allocation - ~20-byte USERAUTH_INFO_REQUEST OOMs the client through connect() (verified on 2.28.7) #1139

Closed

Description

@afldl

PoC (Java harness driving the real com.github.mwiede:jsch:2.28.7 from Maven Central + fake SSH server + run script): https://gist.github.com/afldl/d14f64b92ff3c27adb6c53409ad97aca

com.github.mwiede:jsch:2.28.7 (current latest on Maven Central; code unchanged since the original jsch) allocates new String[num] / new boolean[num] directly from the server-controlled num-prompts uint32 of SSH_MSG_USERAUTH_INFO_REQUEST, before reading any prompt data, during keyboard-interactive authentication. A malicious server sends a ~20-byte message with num-prompts = 0x3FFFFFFF; the client dies with java.lang.OutOfMemoryError: Java heap space that escapes Session.connect() uncaught. This is reachable pre-authentication with default configuration - the only precondition is that the client uses keyboard-interactive auth (any UserInfo implementing UIKeyboardInteractive, which jsch 2.x requires for KI, i.e. every KI user). I reproduced this end-to-end against the unmodified released jar (JDK 21, real TCP, fake sshd). Dedup-checked against all 41 keyboard-interactive issues/PRs in this repo and in is/jsch: no prior report of this.

Details

com/jcraft/jsch/UserAuthKeyboardInteractive.java:123-125, in the handling of SSH_MSG_USERAUTH_INFO_REQUEST:

int num = buf.getInt();              // server-declared num-prompts, validated by nothing
String[] prompt = new String[num];   // ~1G refs -> ~4GB allocation attempt
boolean[] echo = new boolean[num];   // ~1GB more

The server-declared count is used directly as an array length before any prompt bytes are read, so its value is completely unconstrained by the actual message size (each prompt would carry at least a 4-byte length prefix, so a valid num-prompts is bounded by the remaining message length - that bound is never checked). With num = 0x3FFFFFFF the allocation attempts exceed any realistic heap, and the resulting java.lang.OutOfMemoryError is an Error, not an Exception, so it passes through Session.connect() uncaught and kills the calling application. The negative variant 0x80000000 produces java.lang.NegativeArraySizeException: -2147483648 with the same escape.

PoC

Build & run the victim

Single path (also in the gist as REPRODUCE.md):

git clone https://gist.github.com/afldl/d14f64b92ff3c27adb6c53409ad97aca poc && cd poc
# fake sshd needs paramiko; harness needs a JDK
python3 -m pip install paramiko
python3 fake_sshd.py 2222 &          # malicious server, injects raw INFO_REQUEST
java -Xmx128m -cp .:jsch-2.28.7.jar KITest   # client harness using the real 2.28.7 jar

Reproduction steps

  1. Start fake_sshd.py - it performs a normal kex, answers SERVICE_REQUEST, advertises keyboard-interactive,password, and on the first USERAUTH_REQUEST("keyboard-interactive") replies with a SSH_MSG_USERAUTH_INFO_REQUEST whose name/instruction/lang are empty strings, num-prompts = 0x3FFFFFFF, and no prompt data (~20 bytes total). No client-side state beyond reaching KI auth is required - that is exactly the pre-auth window.
  2. Run the client harness KITest (calls Session.connect() with a UserInfo implementing UIKeyboardInteractive, default algorithm negotiation).
  3. Expected observation: kex completes (SSH_MSG_NEWKEYS exchanged, SSH_MSG_SERVICE_ACCEPT received), then the process dies with OutOfMemoryError outside connect()'s exception handling - the harness's outer catch sees it (CAUGHT_AT_CONNECT line), exit non-zero.
  4. Variant: num-prompts = 0x80000000 -> NegativeArraySizeException: -2147483648, same escape.
  5. Control: same fake sshd with num-prompts = 1 and one real prompt field - client proceeds normally to UIKeyboardInteractive.promptKeyboardInteractive(), confirming the crash is caused solely by the unchecked count.

PoC source

https://gist.github.com/afldl/d14f64b92ff3c27adb6c53409ad97aca - the gist also carries the full build/repro steps as REPRODUCE.md, plus client_oom.log / client_neg.log.

Captured output

Real run, JDK 21.0.12, -Xmx128m, jsch 2.28.7 vs paramiko 5.0.0 fake sshd (excerpt, full log in gist):

[jsch] SSH_MSG_SERVICE_ACCEPT received
[jsch] Authentications that can continue: keyboard-interactive,password
[jsch] Next authentication method: keyboard-interactive
CAUGHT_AT_CONNECT: java.lang.OutOfMemoryError: Java heap space
VERDICT: pre-auth Error escaped connect(): java.lang.OutOfMemoryError

Negative-size variant (num-prompts = 0x80000000):

[jsch] SSH_MSG_SERVICE_ACCEPT received
[jsch] Authentications that can continue: keyboard-interactive,password
[jsch] Next authentication method: keyboard-interactive
[jsch] Disconnecting from 127.0.0.1 port 2223
CAUGHT_AT_CONNECT: java.lang.NegativeArraySizeException: -2147483648
VERDICT: pre-auth Error escaped connect(): java.lang.NegativeArraySizeException

Impact

Any application using JSch for keyboard-interactive authentication against a malicious or compromised SSH server (or a man-in-the-middle on an unverified host key) is killed by a single ~20-byte pre-auth message: the Error escapes Session.connect(), so unless the application has a top-level catch (Throwable) the JVM terminates. The attacker spends ~20 bytes; repeated connections keep the client unavailable (crash-loop / DoS). No authentication or credentials are disclosed.

CVSS

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H -> 7.5 High

  • AV:N - attacker is the remote SSH server endpoint.
  • AC:L - single malformed message, no special conditions.
  • PR:N / UI:N - fires before authentication, no user interaction.
  • S:U - impact confined to the client JVM.
  • C:N/I:N - no disclosure or integrity effect.
  • A:H - the client process is killed outright, repeatable at will.

Suggested fix

Validate num-prompts against the remaining message bytes before allocating - each prompt carries at least a 4-byte length prefix, so e.g. if (num < 0 || num * 4L > buf.getLength() - buf.getIndex()) throw new JSchException("invalid num-prompts") - or cap it at a sane bound. This mirrors what OpenSSH does implicitly by parsing prompts sequentially.

Affected versions

com.github.mwiede:jsch <= 2.28.7 (verified on 2.28.7, current latest; the code is inherited unchanged from the original com.jcraft jsch 0.1.x line, so those are affected too). Runtime condition: client uses keyboard-interactive authentication (UserInfo implementing UIKeyboardInteractive), which jsch 2.x requires for KI.

Credit

Reported by the diff/ambidiff security research effort (afldl). Happy to coordinate a disclosure timeline; will request a CVE once confirmed.

Activity

  1. added a commit that references this issue on Aug 27, 2026
  2. norrisjeremy commented on Aug 27, 2026

    @norrisjeremy
    Contributor

    Hi @afldl,

    I've updated #1134 to include a change that I believe will remedy the issue you have reported.
    If you can test and confirm, that would be appreciated.

    Thanks,
    Jeremy

  3. added a commit that references this issue on Aug 27, 2026
  4. added 10 commits that reference this issue on Oct 6, 2026
  5. added 2 commits that reference this issue on Oct 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions