[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
Description
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 moreThe 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 jarReproduction steps
- Start
fake_sshd.py- it performs a normal kex, answersSERVICE_REQUEST, advertiseskeyboard-interactive,password, and on the firstUSERAUTH_REQUEST("keyboard-interactive")replies with aSSH_MSG_USERAUTH_INFO_REQUESTwhose 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. - Run the client harness
KITest(callsSession.connect()with aUserInfoimplementingUIKeyboardInteractive, default algorithm negotiation). - Expected observation: kex completes (
SSH_MSG_NEWKEYSexchanged,SSH_MSG_SERVICE_ACCEPT received), then the process dies withOutOfMemoryErroroutsideconnect()'s exception handling - the harness's outer catch sees it (CAUGHT_AT_CONNECTline), exit non-zero. - Variant:
num-prompts = 0x80000000->NegativeArraySizeException: -2147483648, same escape. - Control: same fake sshd with
num-prompts = 1and one real prompt field - client proceeds normally toUIKeyboardInteractive.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
mwiede#1139: add bounds checks when parsing prompts from SSH_MSG_USER…
norrisjeremy commentedon Aug 27, 2026
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
mwiede#1127 & mwiede#1139: expand upon previous bounds checks to acco…
mwiede#1139: add bounds checks when parsing prompts from SSH_MSG_USER…
mwiede#1127 & mwiede#1139: expand upon previous bounds checks to acco…
mwiede#1139: add bounds checks when parsing prompts from SSH_MSG_USER…
mwiede#1127 & mwiede#1139: expand upon previous bounds checks to acco…
mwiede#1139: add bounds checks when parsing prompts from SSH_MSG_USER…
mwiede#1127 & mwiede#1139: expand upon previous bounds checks to acco…
mwiede#1139: add bounds checks when parsing prompts from SSH_MSG_USER…
mwiede#1127 & mwiede#1139: expand upon previous bounds checks to acco…
mwiede#1139: add bounds checks when parsing prompts from SSH_MSG_USER…
mwiede#1127 & mwiede#1139: expand upon previous bounds checks to acco…
#1139: add bounds checks when parsing prompts from SSH_MSG_USERAUTH_I…
#1127 & #1139: expand upon previous bounds checks to account for arit…
mwiede commentedon Oct 10, 2026
https://github.com/mwiede/jsch/releases/tag/jsch-2.28.8