Introduction
Persistent HTTP connections improve application performance by allowing an API Gateway to reuse TCP connections instead of creating a new connection for every request.
But connection reuse also introduces an important security requirement: every component handling the connection must agree on where one message ends and the next begins.
TCP does not understand requests or responses. It provides an ordered byte stream. HTTP defines message semantics and framing, while application-level parsers interpret those bytes.
When these layers disagree about message boundaries, request and response state can become desynchronized.
In this phase of my 16-Phase Offensive Socket Security research, we examine Backend Response Queue Poisoning—a response desynchronization scenario in which a vulnerable gateway can associate a backend response with the wrong client request.
The goal of this lab is to demonstrate how a persistent backend connection can become a cross-user data exposure risk when the gateway and backend do not maintain strict request/response synchronization.
The Vulnerability: Breaking Connection Synchronization
Modern API Gateways commonly reuse persistent HTTP/1.1 connections to internal backend services.
Instead of creating a new TCP connection for every request, the gateway may maintain a connection pool:
Client A ──┐
│
Client B ──┼──> API Gateway ───> Backend
│ |
Client C ──┘ |
|
Persistent TCP
ConnectionThe architecture assumes that requests and responses remain synchronized:
Request A → Response A
Request B → Response B
Request C → Response CThe problem begins when the backend generates more response data than the gateway expects for a logical request.
For example:
Request A → Response A + Unexpected Response BIf the gateway consumes only Response A, the additional response can remain available on the persistent backend connection.
When another client request is subsequently processed using that connection, a vulnerable gateway may consume the unexpected response and associate it with the new request.
That is the response queue poisoning condition demonstrated in this article.
TCP Does Not Provide Application Message Boundaries
A common mistake when working with sockets is assuming that one call to NetworkStream.Read() corresponds to one complete application message.
It does not.
A request can be split across multiple reads:
Read #1:
GET /my-pr
Read #2:
ofile HTTP/1.1
Host: example.com
Multiple application messages can also be present in the same read.
Therefore, production protocol implementations must maintain their own buffering and message-framing logic.
The C# implementation used in this lab deliberately contains a simplified parser so that the synchronization problem is easy to reproduce.
The C# Lab
The vulnerable backend uses TcpClient and keeps the connection alive:
static void Handle(TcpClient client)
{
var stream = client.GetStream();
byte[] buffer = new byte[1024];
while (true)
{
// Vulnerable assumption:
// one Read() represents a complete set of application messages.
int bytesRead = stream.Read(buffer, 0, buffer.Length);
if (bytesRead == 0)
break;
string request =
Encoding.UTF8.GetString(buffer, 0, bytesRead);
// Simplified lab parser.
// This is NOT a standards-compliant HTTP parser.
string[] commands = request.Split(
new[] { "\r\n\r\n", "\n\n" },
StringSplitOptions.RemoveEmptyEntries);
foreach (var command in commands)
{
// Normal request
if (command.StartsWith("GET /"))
{
string body = "USER_DATA";
string response =
"HTTP/1.1 200 OK\r\n" +
$"Content-Length: {Encoding.UTF8.GetByteCount(body)}\r\n" +
"\r\n" +
body;
stream.Write(
Encoding.UTF8.GetBytes(response));
}
// Demonstration-only administrative command
else if (command.StartsWith("ADMIN") &&
command.Contains("TOKEN=valid"))
{
string body = "ADMIN_SECRET";
string response =
"HTTP/1.1 200 OK\r\n" +
$"Content-Length: {Encoding.UTF8.GetByteCount(body)}\r\n" +
"\r\n" +
body;
stream.Write(
Encoding.UTF8.GetBytes(response));
}
}
}
}The vulnerability is not caused by the foreach statement itself.
The problem is the combination of several assumptions:
An arbitrary TCP
Read()is treated as a complete application-level input.The input is split using simplified delimiters rather than proper HTTP message framing.
The TCP connection remains persistent.
Multiple responses can be generated from a single received stream segment.
The gateway assumes that the next backend response belongs to the request it is currently processing.
This is a deliberately simplified lab implementation and should not be considered a standards-compliant HTTP parser.
Exploit Flow: Stranding a Backend Response
The attacker does not necessarily need to receive the sensitive response directly.
Instead, the objective of this demonstration is to create an unexpected response on the shared persistent connection.
The sequence is:
1. The Attacker Sends Crafted Input
The attacker sends data containing two commands over the same TCP byte stream:
GET / HTTP/1.1
Host: localhost
ADMIN TOKEN=validThe backend's simplified parser interprets these as two separate commands.
2. The Backend Generates Two Responses
The backend generates:
HTTP/1.1 200 OK
Content-Length: 9
USER_DATAand then:
HTTP/1.1 200 OK
Content-Length: 12
ADMIN_SECRETBoth responses are written to the same persistent TCP connection.
The important point is that TCP has no concept of either response. It simply carries the resulting ordered bytes.
3. The Gateway Consumes the First Response
The vulnerable gateway expects one response for the attacker's logical request.
It consumes:
USER_DATAand forwards it to the attacker.
The additional ADMIN_SECRET response remains available on the persistent backend connection.
4. The Victim Sends a Legitimate Request
A legitimate client subsequently sends:
GET /my-private-profile HTTP/1.1
Host: localhostThe gateway sends the request to the backend using the same persistent connection.
5. The Response Association Becomes Desynchronized
The vulnerable gateway reads the next available backend response.
Instead of receiving the response generated for:
GET /my-private-profilethe gateway encounters the previously generated:
ADMIN_SECRETIf the gateway incorrectly associates that response with the victim's request, the victim receives data belonging to a different request.
Proof of Concept: Cross-User Response Poisoning
The following screenshot shows the complete execution trace from the C# proof of concept.
The left terminal represents the raw internal backend. The right terminal represents the API Gateway/proxy maintaining the persistent backend connection.

Figure 1. C# proof of concept demonstrating backend response queue poisoning: the backend generates two responses from the attacker's input, and the gateway subsequently associates the stranded ADMIN_SECRET response with a legitimate victim request.
What the Screenshot Demonstrates
The backend first receives the attacker's input and parses two commands:
[Parsed CMD] GET / HTTP/1.1
[Response] USER_DATA
[Parsed CMD] ADMIN TOKEN=valid
[Response] ADMIN_SECRETThe gateway initially forwards:
USER_DATAto the attacker.
The important event happens when the victim sends:
GET /my-private-profile HTTP/1.1The gateway then forwards:
ADMIN_SECRETas the response to the victim.
This demonstrates the central failure:
The gateway's request/response state is no longer synchronized with the backend's response stream.
The screenshot is particularly useful because it shows the complete sequence rather than only presenting the vulnerable backend code.
Security Impact
The impact depends on the exact gateway and backend architecture, but response desynchronization can create significant security consequences.
Cross-User Data Exposure
A response generated for one request can potentially be delivered to another client.
Depending on the application, exposed information could include:
Personally identifiable information
Account information
Internal API responses
Administrative data
Tenant-specific information
Authentication-related data
Response Integrity Failure
The vulnerability can also affect data integrity.
A client may receive a syntactically valid response that belongs to a completely different request. If the client or application trusts that response, it may process incorrect information.
Cache Poisoning — Conditional
If a caching layer is present and also mishandles the desynchronized response, the issue could potentially extend into cache poisoning.
However, this consequence depends on the behavior of the specific proxy and caching infrastructure and should not be assumed for every response desynchronization vulnerability.
Potential CWE Mappings
The exact CWE classification depends on the vulnerable implementation and which component is considered responsible for the security failure.
Potentially relevant classifications include:
CWE-444 — Inconsistent Interpretation of HTTP Requests
CWE-200 — Exposure of Sensitive Information
CWE-441 — Unintended Proxy or Intermediary Behavior may also be relevant when an intermediary incorrectly processes or associates data because of inconsistent protocol interpretation.
These mappings should be treated as implementation-dependent rather than universal classifications for every persistent-connection desynchronization issue.
CVSS Considerations
For the specific lab scenario, the author assesses the demonstrated vulnerability using the following CVSS v3.1 vector:
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:NThis corresponds to a CVSS score of 8.1 (High) under the stated assumptions.
The assessment applies to the demonstrated architecture and exploitation conditions. In particular, successful exploitation depends on the gateway/backend implementation exhibiting the required request/response synchronization failure.
The score should therefore not be interpreted as applying to all HTTP/1.1 persistent connections or all API Gateway implementations.
Defensive Fix: Enforce Strict Message Framing
The primary defense is to ensure that every component agrees on message boundaries and that a connection cannot be safely reused after its protocol state becomes ambiguous.
Never Treat Read() as a Message Boundary
A TCP read returns the bytes currently available from the stream.
Application code should maintain a receive buffer and parse complete protocol messages from that buffer.
Do not assume:
Read() == One RequestThat assumption is unsafe for stream-oriented protocols.
Use a Standards-Compliant HTTP Parser
Production HTTP infrastructure should use a standards-compliant HTTP implementation instead of manually splitting HTTP data using delimiters such as:
request.Split("\r\n\r\n");HTTP message framing involves more than detecting a blank line. Request and response bodies, headers, connection semantics, and other protocol rules must be handled consistently.
Maintain Request/Response Synchronization
A gateway should maintain a strict relationship between forwarded requests and backend responses:
Request A → Response A
Request B → Response B
Request C → Response CUnexpected response data should never silently become the response for another client request.
Close Ambiguous Connections
If malformed framing, unexpected trailing data, or another synchronization anomaly is detected, the affected connection should be removed from the connection pool rather than reused.
A safe conceptual flow is:
Protocol anomaly detected
|
v
Quarantine connection
|
v
Close TCP connection
|
v
Create a fresh connectionTrying to recover and reuse an ambiguous persistent connection can allow desynchronized state to cross request or tenant boundaries.
Keep Framing Rules Consistent
Every intermediary in the request path should interpret message boundaries consistently.
The key security principle is:
If two components disagree about where a message ends, the connection should not be trusted for further request processing.
Final Insight
Persistent connections are a performance optimization, not a security boundary.
TCP provides an ordered byte stream. HTTP defines message semantics. Parsers transform those bytes into requests and responses.
The security failure occurs when different components make different assumptions about those boundaries.
In a vulnerable gateway architecture, that disagreement can cause a response generated for one request to become associated with another client's request.
The lesson is simple:
Transport-level continuity does not guarantee application-level synchronization.
This article is part of Phase 05 — Stream Desynchronization in my ongoing 16-Phase Offensive Socket Security research.
Source Code and Further Reading
🔗 Full 16-Phase GitHub Repository
🔗 Phase 05B Source Code & POC:
In the next article, Phase 05C, we will examine another form of HTTP desynchronization: conflicting interpretations of Content-Length and Transfer-Encoding, and how those differences can lead to HTTP Request Smuggling.
Join the conversation! Your thoughts help the community grow.