
POC_CVE-2026-45185 for nuclei-templates
This repository is not a general-purpose exploit PoC repository. It is a local
validation lab for reproducing and reviewing the behavior of a
CVE-2026-45185 template intended for contribution to
projectdiscovery/nuclei-templates.
The nuclei code template is not a version-only detector.
It directly controls STARTTLS, BDAT, TLS close_notify, and a following
SMTP plaintext byte on the same TCP connection.
This sequence matches in the local vulnerable Exim 4.99.2 GnuTLS lab and does
not match in the patched Exim 4.99.3 GnuTLS lab under the same conditions.
The current validation signal is a remote SMTP response oracle, not a direct
observation of the UAF write itself. Internally, this CVE is a use-after-free
where newline bytes (\r/\n) can be written into a freed GnuTLS transfer
buffer after TLS shutdown. In practice, a client cannot realistically observe
that freed-buffer write directly through SMTP responses. Therefore, this README
and the template do not claim to directly prove the
bdat_ungetc -> tls_ungetc UAF write or RCE. Instead, the template detects a
receive stack/state recovery difference that appears during the trigger flow.
While tracing the vulnerability mechanism, I observed that after TLS
close_notify during STARTTLS + BDAT handling, stale tls_* function pointers
can remain in the lower layer of the BDAT receive stack instead of being
properly restored to smtp_* function pointers. That state becomes visible in
how the server handles the next SMTP command. After the split BDAT reaches first
completion, sending NOOP on the same session makes the vulnerable Exim 4.99.2
GnuTLS lab return 421 lost input connection, while the patched Exim 4.99.3
GnuTLS lab handles it normally with 250 OK. This clear vulnerable/patched
response difference is used as the matcher for the local, authorized nuclei code
template.
For a deeper vulnerability flow walkthrough, see
CVE-2026-45185-Technical-Analysis.md.
| Target | Version | TLS backend | STARTTLS | CHUNKING | Port | Expected nuclei result |
|---|---|---|---|---|---|---|
| vulnerable | Exim 4.99.2 | GnuTLS | yes | yes | 127.0.0.1:2525 | match |
| patched | Exim 4.99.3 | GnuTLS | yes | yes | 127.0.0.1:2526 | no match |
Common SMTP envelope recipient (RCPT TO):
The Docker lab's lab_rcpt ACL accepts this envelope recipient. On a general
SMTP target, if RCPT TO is rejected, the sequence may not reach the BDAT body
parser, so the template needs an accepted recipient.
This value is distinct from the To: header inside the BDAT body. The
RCPT TO recipient is an SMTP envelope address that must pass the server's
recipient checks. The To: value in the BDAT body is only message header text;
it does not need to exist or be accepted as a mailbox by the server.
templates/CVE-2026-45185.yaml
Template name:
Exim 4.97-4.99.2 GnuTLS STARTTLS BDAT - Same-Session Response Check
The template is written with metadata.verified: true and has the code and
intrusive tags.
Run the following commands from the POC_2026_45185/ directory.
docker compose build
docker compose up -d
Validate the template syntax:
nuclei -duc -validate -code -t templates/CVE-2026-45185.yaml
Sign the local code template before running it:
nuclei -duc -code -t templates/CVE-2026-45185.yaml -sign
Run against the vulnerable lab:
nuclei -code \
-t templates/CVE-2026-45185.yaml \
-u 127.0.0.1:2525 \
-var [email protected] \
-debug
Expected result:
CVE-2026-45185: vulnerable response oracle matched
Run against the patched lab:
nuclei -code \
-t templates/CVE-2026-45185.yaml \
-u 127.0.0.1:2526 \
-var [email protected] \
-debug
Expected result:
no match
NO-MATCH: patched-like response: NOOP succeeded after split trigger
Note that the nuclei code protocol is not executed by default, so -code is
required. Nuclei also blocks unsigned code templates. If the local nuclei
private key is protected with a passphrase, run the signing command in an
interactive terminal and enter that passphrase. Re-sign after every template
change because the digest covers the template content.
These screenshots show the local lab response-oracle result after the template has been signed. They are validation evidence for the same-session response difference described above, not a direct debugger or ASAN proof of the internal UAF write.
Vulnerable Exim 4.99.2 GnuTLS lab on 127.0.0.1:2525:

Patched Exim 4.99.3 GnuTLS lab on 127.0.0.1:2526:

The core of this CVE is not an SMTP banner or version check. The template needs to create the following transport-state transition on the same TCP connection:
plaintext SMTP EHLO
-> STARTTLS
-> TLS handshake on the same TCP connection
-> TLS EHLO / MAIL FROM / RCPT TO / BDAT 70 LAST
-> first 69 bytes of the BDAT body as TLS application data
-> TLS close_notify without closing the TCP socket
-> final body byte as plaintext on the same TCP connection
-> same-session plaintext NOOP response check
The Python code inside the YAML template prints the fixed marker only after all of the following conditions pass. The nuclei matcher only matches that marker.
Exim identity is found
AND STARTTLS is advertised in plaintext EHLO
AND CHUNKING is advertised in plaintext EHLO
AND STARTTLS is accepted
AND CHUNKING is advertised in TLS EHLO
AND MAIL FROM is accepted
AND RCPT TO is accepted
AND the split close_notify BDAT reaches first completion
AND first completion contains "250 OK id="
AND the same-session plaintext NOOP response contains "421"
AND the same-session plaintext NOOP response contains "lost input connection"
The template does not match on any of the following signals alone:
version-only
timeout-only
connection-drop-only
empty-response-only
recipient rejection
STARTTLS/CHUNKING advertisement only
Both labs process the split BDAT message through first completion.
250- 70 byte chunk, total 72
250 OK id=...
The difference appears when the next SMTP plaintext command is sent on the same SMTP session.
Vulnerable 4.99.2:
NOOP -> 421 exim-lab.local lost input connection
QUIT -> 421 exim-lab.local lost input connection
RSET -> 421 exim-lab.local lost input connection
Patched 4.99.3:
NOOP -> 250 OK
QUIT -> 221 exim-lab.local closing connection
RSET -> 250 Reset OK
Interpretation:
Observation:
Both labs reach split BDAT message completion.
Only the vulnerable lab fails to return cleanly to the next plaintext SMTP
command loop in the same session.
Evidence:
The vulnerable follow-up response is 421 lost input connection.
The patched follow-up response is 250 OK or 221 closing connection.
Inference:
This difference is consistent with the receive stack/state recovery difference
after STARTTLS close_notify.
The template uses the following 70-byte message as the BDAT body.
From: [email protected]\r\n
To: [email protected]\r\n
Subject: poc\r\n
\r\n
body
The SMTP envelope recipient is passed separately through the template variable
recipient. The To: header in the body is text and is unrelated to whether
SMTP RCPT TO is accepted, so it does not need to exist or be accepted by the
server.
Split shape:
BDAT 70 LAST
TLS body: first 69 bytes, ending with "bod"
TLS event: close_notify
Plaintext: final byte "y"
Follow-up: NOOP
You can manually verify that both labs advertise Exim, STARTTLS, and CHUNKING.
printf 'EHLO lab-client.local\r\nQUIT\r\n' | nc -w 3 127.0.0.1 2525
printf 'EHLO lab-client.local\r\nQUIT\r\n' | nc -w 3 127.0.0.1 2526
Expected signals:
Exim
STARTTLS
CHUNKING
You can also check the normal STARTTLS path:
openssl s_client -starttls smtp -connect 127.0.0.1:2525 -crlf
openssl s_client -starttls smtp -connect 127.0.0.1:2526 -crlf
debugging/ and notes/source-walkthrough-progress.md.POC_2026_45185/
compose.yaml
images/ local nuclei validation screenshots
vulnerable/ Exim 4.99.2 + GnuTLS debug build
patched/ Exim 4.99.3 + GnuTLS debug build
nuclei-templates/ submodule: local nuclei template workspace