Summary
ValueReader.readBytes() allocates a byte array sized by a wire-declared content length without validating it against actual frame data. A malicious AMQP peer triggers OOM by declaring a ~2GB string/bytes field.
Vulnerable Code
src/main/java/com/rabbitmq/client/impl/ValueReader.java lines 83-95:
java private static byte[] readBytes(final DataInputStream in) throws IOException { final long contentLength = unsignedExtend(in.readInt()); if(contentLength < Integer.MAXVALUE) { final byte[] buffer = new byte[(int)contentLength]; // allocates before reading in.readFully(buffer); return buffer; } }
Attack Scenario
A malicious AMQP server sends a LongString field (type tag 'S') with declared length 0x7FFFFFFE (2,147,483,646). The check contentLength < Integer.MAXVALUE passes. new byte[2147483646] attempts ~2GB allocation, causing OutOfMemoryError before readFully() attempts to read data.
The allocation size is attacker-controlled and is NOT validated against the frame size or TruncatedInputStream bounds. Exploitable pre-authentication via connection.start server-properties table.
Impact
Denial of service via JVM OutOfMemoryError. Crashes the entire JVM.
CWE
CWE-789: Memory Allocation with Excessive Size Value
Remediation
Validate contentLength against the frame's remaining bytes or the negotiated max frame size (default 131,072) before allocating.
Summary
ValueReader.readTable() and readArray() recursively call readFieldValue() with no depth limit. A malicious AMQP peer can crash the client JVM by sending a deeply nested table structure.
Vulnerable Code
src/main/java/com/rabbitmq/client/impl/ValueReader.java lines 139-155 and 237-249:
java private static Map<String, Object> readTable(DataInputStream in) throws IOException { long tableLength = unsignedExtend(in.readInt()); // ... while(tableIn.available() > 0) { String name = readShortstr(tableIn); Object value = readFieldValue(tableIn); // recursive call } }
static Object readFieldValue(DataInputStream in) throws IOException { switch(in.readUnsignedByte()) { case 'F': value = readTable(in); // mutual recursion case 'A': value = readArray(in); // mutual recursion } }
Attack Scenario
A malicious AMQP server (or MitM) sends a connection.start frame with ~580 levels of nested tables. Each level costs ~7 bytes (4-byte length + 1-byte key length + 1-byte key + 1-byte type tag), totaling ~4060 bytes within the 131,072 byte max frame size. With the default JVM stack (~512KB, ~864 bytes/frame), this triggers StackOverflowError, killing the I/O thread.
Exploitable pre-authentication since connection.start is the very first server frame.
Impact
Denial of service. StackOverflowError kills the client I/O thread.
CWE
CWE-674: Uncontrolled Recursion
Remediation
Add a depth counter to readTable/readArray/readFieldValue and throw MalformedFrameException when exceeding a threshold (e.g., 32).
Summary RabbitMQ Java Client's inbound AMQP command assembly accepts a content header declaring a small body and then processes a larger body frame by throwing a raw UnsupportedOperationException from CommandAssembler. A broker peer that the client has connected to can use this malformed frame sequence to fail frame processing and tear down the client connection instead of receiving a clean protocol-level malformed-frame error.
This was discovered based on an existing vulnerability CVE-2017-15699.
Details Inbound frames enter the client through SocketFrameHandler.readFrame, which returns frames parsed from the peer-controlled input stream (src/main/java/com/rabbitmq/client/impl/SocketFrameHandler.java:197). AMQConnection.MainLoop reads each frame (src/main/java/com/rabbitmq/client/impl/AMQConnection.java:692) and dispatches non-zero-channel frames to the channel while the connection is open (src/main/java/com/rabbitmq/client/impl/AMQConnection.java:748 and src/main/java/com/rabbitmq/client/impl/AMQConnection.java:766). The channel then passes the frame to the current command assembler through AMQChannel.handleFrame and AMQCommand.handleFrame (src/main/java/com/rabbitmq/client/impl/AMQChannel.java:121, src/main/java/com/rabbitmq/client/impl/AMQCommand.java:114). When a content-bearing method is followed by a content header, CommandAssembler.consumeHeaderFrame records the header's declared body size in remainingBodyBytes after only checking it against the configured maximum (src/main/java/com/rabbitmq/client/impl/CommandAssembler.java:126 through src/main/java/com/rabbitmq/client/impl/CommandAssembler.java:139). The body-frame path subtracts the received payload length from that remaining count before validating that the payload fits (src/main/java/com/rabbitmq/client/impl/CommandAssembler.java:145 through src/main/java/com/rabbitmq/client/impl/CommandAssembler.java:149), so a body frame larger than the declared size drives the count negative and reaches the raw UnsupportedOperationException at src/main/java/com/rabbitmq/client/impl/CommandAssembler.java:150 and src/main/java/com/rabbitmq/client/impl/CommandAssembler.java:151. AMQConnection catches the resulting throwable in frame processing and performs connection failure handling and final shutdown (src/main/java/com/rabbitmq/client/impl/AMQConnection.java:695 through src/main/java/com/rabbitmq/client/impl/AMQConnection.java:705).
PoC poc.zip
bash bash ./poc/run.sh
text Exception in thread "main" java.lang.UnsupportedOperationException: %%%%%% FIXME unimplemented
The UnsupportedOperationException: %%%%%% FIXME unimplemented fingerprint is the raw exception thrown at the negative remainingBodyBytes check in CommandAssembler.consumeBodyFrame. This line shows the malformed declared-size/body-size sequence reached the vulnerable assembler path.
Impact The attacker model is a remote AMQP broker peer that the RabbitMQ Java Client application has accepted, including a malicious broker endpoint, a compromised broker, or routing that sends the client to an attacker-controlled peer. The peer needs a non-zero open channel that can receive a content-bearing server-to-client method such as basic.deliver, then sends the method frame, a content header declaring a body below the configured maximum, and a body frame whose payload exceeds that declared size. Under those conditions, the peer can force frame processing to fail with UnsupportedOperationException and close the AMQP connection, producing a client-side denial of service for work depending on that connection; the finding does not indicate memory corruption, data disclosure, or code execution.
Vulnerability Summary
com.rabbitmq.client.TrustEverythingTrustManager accepts ANY TLS certificate (including null chains) and is used as the default trust manager when calling ConnectionFactory.useSslProtocol() without arguments. Combined with hostname verification being disabled by default, this enables trivial man-in-the-middle attacks.
Affected Components
- com.rabbitmq.client.TrustEverythingTrustManager — accepts any certificate - com.rabbitmq.client.ConnectionFactory.useSslProtocol() — uses TrustEverythingTrustManager - Hostname verification disabled by default (enableHostnameVerification() must be called explicitly) - com.rabbitmq.client.ConnectionFactory.getPassword() — returns plaintext with no redaction - Default port 5672 (plaintext) with PLAIN SASL — credentials sent unencrypted
POC (Verified on Java 21, amqp-client 5.25.0)
java // TrustEverythingTrustManager accepts ANY certificate including null TrustEverythingTrustManager tm = new TrustEverythingTrustManager(); tm.checkServerTrusted(null, "RSA"); // No exception — accepts null cert chain tm.getAcceptedIssuers(); // Returns empty array — trusts all CAs
// ConnectionFactory defaults ConnectionFactory factory = new ConnectionFactory(); factory.useSslProtocol(); // Uses TrustEverythingTrustManager internally // enableHostnameVerification() NOT called by default
// Credential exposure factory.setPassword("secretpassword123"); factory.getPassword(); // Returns "secretpassword123" — no redaction
// Default plaintext port factory.getPort(); // 5672 (plaintext, not 5671/TLS)
// PLAIN SASL sends cleartext credentials PlainMechanism pm = new PlainMechanism(); // handleChallenge() sends username+password in cleartext
Attack Scenarios
1. MITM: Attacker presents self-signed cert → TrustEverythingTrustManager accepts it → all RabbitMQ traffic intercepted 2. Credential theft: Default plaintext port (5672) + PLAIN SASL = credentials readable on network 3. DNS rebinding: No hostname verification → attacker DNS record → MITM without cert 4. Logging exposure: getPassword() returns plaintext → credentials in logs/stack traces
Suggested Fix 1. Deprecate TrustEverythingTrustManager — it should never be used in production 2. useSslProtocol() should use the JVM default trust store, not TrustEverything 3. Enable hostname verification by default 4. Redact password in getPassword() or remove the public getter 5. Warn when using PLAIN SASL without TLS
Summary The max body size was enforced to patch CVE-2023-46120, but even though that limit still works, the frame size itself still exceeds the given max size.
Root cause The Java client records the AMQP 0-9-1 framemax negotiated during connection tuning, but the socket inbound frame reader continues to validate broker-controlled payload lengths against the much larger maxInboundMessageBodySize limit. A broker peer can therefore send a method frame whose payload is larger than the negotiated framemax, have it allocated and decoded, and complete the connection handshake instead of being rejected as a protocol violation.
Reported by Team Atlanta.