ルカ-さん Luca-san

API Stream Error with Burp


While testing a web application with Burp suite, I encountered the issue:

Communication error / Stream failed to close correctly

and found out this might be a bug in the underlying HTTP Java implementation. Sadly I couldn't definitely confirm this is really the cause, which is disappointing, but that's the impression I was left with after troubleshooting.

If you found this blog post afer searching the error and just want to know what to do, change Burp settings and downgrade to HTTP/1.1, see here how.

What i found, in short:

The rest of this post details my troubleshooting of the issue.

Table of Contents

The Problem

While testing an app with Burp i noticed failures with completing POST requests that upload files (specifically, images). This application automatically retries the upload 3 times before giving up, and it was failing each time. It seems that these requests were timing out as no response was received from the server, however this was happening too quickly and suggested me it is not an actual timeout, or a normal one.

POST request failure

After that, Burp event logger was reporting two errors:

Communication error: target.example.com
Stream failed to close correctly

...but without further explanation so i went in the rabbit hole and started investigate.

Troubleshoot

Repeater

First i tried send the request to the repeater for testing, and noticed that sending it from there worked:

Repeater test

...but only once. Any number of requests sent immediately after were failing, with the event logger recording the errors again. Playing with it a bit more i found out that sending multiple requests works if I waited a bit between requests.

Further debugging it, i found that if i disable the HTTP/2 connection reuse option in the repeater, then it works even when sending requests without any delay.

Repeater connection reuse

This might be an hint on what is happening, more on it later. I thought, great, now i can disable this option in the proxy too, solve and move on, but it seems the proxy doesn’t have this setting. Doing some research i found that it's because Burp leave the handling of the session on the application, which make sense, however as the app works without Burp proxying the request, it also doesn’t make much sense 🫠

Wireshark

In the hope of getting more insight on what’s really happens, i tried using Wireshark to inspect the traffic. As it's HTTPS, i need to import the SSL key from Burp to get the plaintext, this can be done by launching the jar, see those guides https://joshua.hu/extracting-tls-session-keys-Burp-proxy-debugging and https://gitlab.com/wireshark/wireshark/-/wikis/tls#using-the-pre-master-secret

  1. Download extract-tls-secrets-4.0.0.jar from https://github.com/neykov/extract-tls-secrets

  2. Launch Burp via CLI (adjust your path, this is for macOS):

    /Applications/Burp\ Suite\ Professional.app/Contents/Resources/jre.bundle/Contents/Home/bin/java -javaagent:/path-to/extract-tls-secrets-4.0.0.jar -jar /Applications/Burp\ Suite\ Professional.app/Contents/Resources/app/Burpsuite_pro.jar
  3. Open Wireshark and read the key: EditPreferencesProtocolsTLS, (Pre)-Master-Secret log filename

Then replicated the issue with Burp proxying the traffic and observed the events in Wireshark. Seems that shortly after the failing POST, my client send a RST_STREAM which i think it’s what’s actually closing the connection and causing the issue. This also happens when trying with the repeater: when the first request works the stream appear normally, showing this:

Wireshark request working

but the subsequent ones fails (this is the HTTP/2 stream):

Wireshark request failing

HTTP/1.0 and HTTP/1.1

Next i tested downgrading http protocol to HTTP/1.1 and this did the trick. To change this setting in Burp go to NetworkHTTP:

Burp downgrade to HTTP/1.1

Switching back to HTTP/2 and Burp logs the usual error:

Communication error: target.example.com
Stream failed to close correctly

Funny side note here is that PortSwigger itself is advocating deprecating HTTP/1.1... Assuming this is indeed a bug in their/Java implementation, i could ask, what about update your own tool to work fine with HTTP/2 and also support HTTP/3 first? Or better, what about rewrite Burp in something that is not Java, afterall it’s 2025...

Anyway, back to the troubleshoot hole.

Browser

To compare, i decided testing the same flow with a normal browser (eg. Firefox) without Burp proxing the traffic. Checking the HAR, i see it uses a mix of HTTP/2 and HTTP/3, with the two POST requests being sent in HTTP/3. Note that Burp doesn’t support HTTP/3 so we cannot try that.

Firefox working

I disabled HTTP/3 in Firefox and then tried again but it seems to work just as fine with HTTP/2. So at this point i know the browser works fine and i can’t reproduce the issue without Burp so the problem must lie in whatever the proxy is doing in the middle. I wanted to give this a shot based on this part in the article here (although it might be outdated):

I imagine the reason browsers cannot simply “continuously try the connection again if it gets reset/closed on the same connection as a GOAWAY was received” is to avoid a thundering herd problem. Some browsers do try again once, though – but if the second attempt results in the same problem, it gives up trying.

Based on my tests, Firefox is able to handle the problem the best, which I believe is due to this commit (which is still in the src) where any “busted http2 sessions” are retried in with HTTP/1.1 once (unlike Chrome, which retries with HTTP/2).

I wanted to see if i got the same behaviour, expecting a first POST in HTTP/2 to fail and then observe Firefox retry with HTTP/1.1 (which seems to work with Burp too) but instead, everything worked fine on the first attempt and no retries were made.

Firefox still working

...so no luck in this too.

Full tunnel VPN

I was testing this at work and we have an always-on, full-tunnel-with-exceptions ZT VPN and although i didn't think it was related to the problem, it was at least worth confirming to rule this out. We can disable it for short period of time so i tried that, did a few tests and observed no differences so i concluded this could be safely excluded as a possible cause of the issue.

Connection stream / reset

There are very few resources online on this error so i tried prompting our new AI overlords to share their wisdom. All i got was that the RST_STREAM i see in wireshark indicates that the browser is terminating the stream, but why this happens? not even they know. We can definitely rule out the timeout as a cause, because the error is raised almost immediately after the request is sent, and increasing the timeout value in Burp (for the response) has no effect.

Burp Interception

TL;DR Tested this with HTTP/2 and made it to work, but it's quite convoluted so bear with me. Quite some trials and erros for this one, which gave somewhat interesting results.

I proxied the traffic normally and replicated the tests until it was time to click to upload the picture, then turned ON the master interception and paused it to manually forward each requests. Then i was ready to click and upload the media.

Now, in the normal flow of this application, the first request before the upload is a preflight OPTION, followed by the actual POST to /example that upload and contains the media. I clicked and forwarded all the requests i had, until this POST showed up.

Then i sent that POST to the repeater and manually sent it from there: as shown in the previous tests above it worked and correctly returned a 201 created. Back to the master intercept, i forwarded the POST again, meaning i’ve sent this to the server twice, first using the repeater then here. I tested this multiple times and dropping the request will halt the entire flow.

Now if i switch back to the proxy history tab, i can see that this time the server replied to that POST, and i got a 201 created here as well. In the master intercept tab i see the app is sending me traffic, a hint that this is working. In my few tests this sometimes doesn’t work on the first try and you see the POST to /example appearing again. In this case re-do the process to send to repeater, etc. until you see the response from the server in the one forwarded by the proxy.

In the app i'm testing there is actually a second POST with a media, to /example2. I did the same process, and once this passed disabled the master intercept as the other requests aren't relevant.

...somehow this worked and complete the flow.

As a proof, the screenshot below shows the two POST request (highlighted in red) that were normally failing, working correctly with the method above:

Burp manual interception working

Why this works, i have little idea. Might be because doing it manually takes longer, and that somehow is fine for the session, as we saw doing tests with the repeater where sending requests one after the another fails, but waiting a bit in between works. Still unclear why this issue only appears when proxying the traffic as using the browser result in the same speed.

Might be a bug in Burp and its java implementation of the session?

OWASP ZAP

Tested the same flow with ZAP that by default uses HTTP/1.1 and worked fine. Trying to send it to the requester (same as Burp repeater) and manually set HTTP/2 resulted in this error:

IO error in sending request: class org.apache.hc.core5.http2.H2StreamResetException: Stream reset (1)
OWASP ZAP java stream reset error

But actually i get this error trying to use HTTP/2 on any requests, so i think it’s just broken. This release page https://www.zaproxy.org/docs/desktop/releases/2.13.0/ says ZAP support it by default but doesn’t seems the case. I admit i didn't investigated further.

Caido

Tested Caido as well but it seems this new proxy doesn’t support HTTP/2 yet; there is an open request in github here https://github.com/caido/caido/issues/439 . Using HTTP/1.1 it works fine but i already knew that.