Skip to content

🔒 Fix STARTTLS stripping vulnerability#664

Merged
nevans merged 5 commits intomasterfrom
security/STARTTLS-stripping
Apr 23, 2026
Merged

🔒 Fix STARTTLS stripping vulnerability#664
nevans merged 5 commits intomasterfrom
security/STARTTLS-stripping

Conversation

@nevans
Copy link
Copy Markdown
Collaborator

@nevans nevans commented Apr 23, 2026

Warning

Without this patch, a man-in-the-middle attacker can cause Net::IMAP#starttls to return "successfully", without starting TLS.

This ensures that #starttls either succeeds or raises an exception.

The following cases will raise an exception and close the connection:

  • For #starttls, when another error wasn't raised (e.g: the response errors for NO or BAD) but the handler did not run. (Any handler errors are re-raised, since 🥅 Re-raise #starttls error from receiver thread #395.)
  • For any command, if the server sends a tagged OK response for that command before the command has been fully sent (before the command's own response handlers are added), an exception is raised and the connection is closed.

nevans added 5 commits April 23, 2026 10:40
I'm going to add another, and IMO it's easier to read inline too.
I'm putting this in its own commit to simplify testing across backports.
Also, I'm taking a "belt-and-suspenders" approach, and I'm going to test
that either of the two fixes passes the tests.
Taking a "belt-and-suspenders" approach to a STARTTLS stripping attack:

This handles `STARTTLS` as a special-case: if the `STARTTLS` handler
did not run, for _whatever_ reason, an exception _must_ be raised and
the connection dropped.

_No_ command should ever receive a tagged `OK` prior to completely
sending the command.  But `STARTTLS` is security-sensitive enough to
warrant this special-case handler.
Taking a "belt-and-suspenders" approach:

This is a potential problem for any command which registers a response
handler: a malicious server can easily guess what the next tag will be,
and send an `OK` response _before_ the client the response handler is
attached.

`STARTTLS` is an extreme example of this issue: if the `STARTTLS`
handler does not run, then `#starttls` will not start the TLS session,
and the connection is not secured, _but no error is raised._

We should _also_ attach the response handler before sending the `CRLF`,
but that is neither necessary (the response handler will added before
the `synchronize` mutex is unlocked) nor sufficient (the fake `OK` can
be sent _much_ earlier).

On the other hand, it _is_ okay for the server to send an error tagged
response (`NO` or `BAD`), before sending the command has completed.
As far as I can tell, this really isn't necessary: there is no race
condition because the `synchronize` mutex will not be released before
the response handler is added.  Nor is it sufficient to protect against
any invalid responses sent previously.

But the code _reads_ more cleanly (to me) when it is written so the
critical window that requires that mutex is as short as possible.
@nevans nevans added bug Something isn't working backport-0.5 This ticket needs to be backported to the v0.5-stable branch. backport-0.4 This ticket needs to be backported to the v0.4-stable branch security vulnerability patch Pull requests that address security vulnerabilities labels Apr 23, 2026
@nevans nevans merged commit 0ede4c4 into master Apr 23, 2026
39 checks passed
@nevans nevans deleted the security/STARTTLS-stripping branch April 23, 2026 17:43
nevans added a commit that referenced this pull request Apr 23, 2026
🔒 Fix STARTTLS stripping vulnerability (backports #664)
nevans added a commit that referenced this pull request Apr 23, 2026
…664]

I'm putting this in its own commit to simplify testing across backports.
Also, I'm taking a "belt-and-suspenders" approach, and I'm going to test
that either of the two fixes passes the tests.
nevans added a commit that referenced this pull request Apr 23, 2026
…ort #664]

Taking a "belt-and-suspenders" approach to a STARTTLS stripping attack:

This handles `STARTTLS` as a special-case: if the `STARTTLS` handler
did not run, for _whatever_ reason, an exception _must_ be raised and
the connection dropped.

_No_ command should ever receive a tagged `OK` prior to completely
sending the command.  But `STARTTLS` is security-sensitive enough to
warrant this special-case handler.
nevans added a commit that referenced this pull request Apr 23, 2026
…664]

Taking a "belt-and-suspenders" approach:

This is a potential problem for any command which registers a response
handler: a malicious server can easily guess what the next tag will be,
and send an `OK` response _before_ the client the response handler is
attached.

`STARTTLS` is an extreme example of this issue: if the `STARTTLS`
handler does not run, then `#starttls` will not start the TLS session,
and the connection is not secured, _but no error is raised._

We should _also_ attach the response handler before sending the `CRLF`,
but that is neither necessary (the response handler will added before
the `synchronize` mutex is unlocked) nor sufficient (the fake `OK` can
be sent _much_ earlier).

On the other hand, it _is_ okay for the server to send an error tagged
response (`NO` or `BAD`), before the sending the command has completed.
nevans added a commit that referenced this pull request Apr 23, 2026
🔒 Fix STARTTLS stripping vulnerability (backports #664, #395, #198)
nevans added a commit that referenced this pull request Apr 23, 2026
🔒 Fix STARTTLS stripping vulnerability (backports #664)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-0.4 This ticket needs to be backported to the v0.4-stable branch backport-0.5 This ticket needs to be backported to the v0.5-stable branch. bug Something isn't working security vulnerability patch Pull requests that address security vulnerabilities

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant