You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
email: folding of quoted string in display_name violates RFC #80222
When using a policy for an EmailMessage that triggers folding (during serialization) of a fairly long display_name in an address field, the folding process removes the quotes from the display name breaking the semantics of the field.
In particular, for a From address's display name like r'anything@anything.com ' + 'a' * MAX_LINE_LEN the folding puts anything@anything.com unquoted immediately after the From: header. For applications that do sender verification inside and then send it to an internal SMTP server that does not perform its own sender verification this could be considered a security issue since it enables sender spoofing. Receiving mail servers might be able to detect the broken header, but experiments show that the mail gets delivered.
Simple demonstration (reproduced in attachment) of issue:
SMTP_POLICY=email.policy.default.clone(linesep='\r\n', max_line_length=72)
address=Address(display_name=r'anything@anything.com '+'a'*72, addr_spec='dev@local.startmail.org')
message=EmailMessage(policy=SMTP_POLICY)
message['From'] =Address(display_name=display_name, addr_spec=addr_spec)
# Trigger folding (via as_string()), then parse it back in.msg_string=message.as_string()
msg_bytes=msg_string.encode('utf-8')
msg_deserialized=BytesParser(policy=SMTP_POLICY).parsebytes(msg_bytes)
# Verify badnessfrom_hdr=msg_deserialized['From']
assertfrom_hdr!=str(address) # But they should be equal...
Since Address itself renders it correctly (str(address)), the problem is going to take a bit of digging to find. I'm guessing the quoted_string atom is getting transformed incorrectly into something else at some point during the folding.
SwampWalker commented on Feb 20, 2019
SwampWalkermannequin
MannequinAuthor
More actions
Hi David, the problem is in email._header_value_parser._refold_parse_tree.
Specifically, when the parsetree renders too long, it recursively gets newparts = list(part) (the children). When it does this to a BareQuotedString node, the child nodes are unquoted and unescaped and it just happily serializes these.
I thought I had attached a file that monkey patches the _refold_parse_tree function with a fixed version... let me try again.
I'm afraid I don't have time to parse through the file you uploaded. Can you produce a pull request or a diff showing your fix? And ideally some added tests :) But whatever you can do is great, if you don't have time maybe someone else will pick it up (I unfortunately don't have time, though I should be able to do a review of a PR).
SwampWalker commented on Feb 20, 2019
SwampWalkermannequin
MannequinAuthor
More actions
Sure thing, I'll try to produce something tomorrow.
SwampWalker commented on Feb 26, 2019
SwampWalkermannequin
MannequinAuthor
More actions
Sorry about the delay. I opened pull request #12054 for this. Let me know if you need anything else.
SwampWalker commented on Feb 26, 2019
SwampWalkermannequin
MannequinAuthor
More actions
Although I am not personally interested in backporting a fix for this issue, anyone that experiences this issue in python 3.5 can execute the following monkey patch to solve the issue:
def_fix_issue_36041_3_5():
fromemail._header_value_parserimportQuotedString, ValueTerminal, quote_stringimportemail._header_value_parserclassBareQuotedString(QuotedString):
token_type='bare-quoted-string'def__str__(self):
returnquote_string(''.join(str(x) forxinself))
@propertydefvalue(self):
return''.join(str(x) forxinself)
@propertydefparts(self):
parts=list(self)
escaped_parts= []
forpartinparts:
ifisinstance(part, ValueTerminal):
escaped=quote_string(str(part))[1:-1]
escaped_parts.append(ValueTerminal(escaped, 'ptext'))
else:
escaped_parts.append(part)
# Add quotes to the first and last parts.escaped_parts[0] =ValueTerminal('"'+str(escaped_parts[0]), 'ptext')
escaped_parts[-1] =ValueTerminal(str(escaped_parts[-1] +'"'), 'ptext')
returnescaped_partsemail._header_value_parser.BareQuotedString=BareQuotedString
Activity
SwampWalker commentedon Feb 19, 2019
When using a policy for an EmailMessage that triggers folding (during serialization) of a fairly long display_name in an address field, the folding process removes the quotes from the display name breaking the semantics of the field.
In particular, for a From address's display name like r'anything@anything.com ' + 'a' * MAX_LINE_LEN the folding puts anything@anything.com unquoted immediately after the From: header. For applications that do sender verification inside and then send it to an internal SMTP server that does not perform its own sender verification this could be considered a security issue since it enables sender spoofing. Receiving mail servers might be able to detect the broken header, but experiments show that the mail gets delivered.
Simple demonstration (reproduced in attachment) of issue:
bitdancer commentedon Feb 19, 2019
Since Address itself renders it correctly (str(address)), the problem is going to take a bit of digging to find. I'm guessing the quoted_string atom is getting transformed incorrectly into something else at some point during the folding.
SwampWalker commentedon Feb 20, 2019
Hi David, the problem is in email._header_value_parser._refold_parse_tree.
Specifically, when the parsetree renders too long, it recursively gets newparts = list(part) (the children). When it does this to a BareQuotedString node, the child nodes are unquoted and unescaped and it just happily serializes these.
I thought I had attached a file that monkey patches the _refold_parse_tree function with a fixed version... let me try again.
bitdancer commentedon Feb 20, 2019
I'm afraid I don't have time to parse through the file you uploaded. Can you produce a pull request or a diff showing your fix? And ideally some added tests :) But whatever you can do is great, if you don't have time maybe someone else will pick it up (I unfortunately don't have time, though I should be able to do a review of a PR).
SwampWalker commentedon Feb 20, 2019
Sure thing, I'll try to produce something tomorrow.
SwampWalker commentedon Feb 26, 2019
Sorry about the delay. I opened pull request #12054 for this. Let me know if you need anything else.
SwampWalker commentedon Feb 26, 2019
Although I am not personally interested in backporting a fix for this issue, anyone that experiences this issue in python 3.5 can execute the following monkey patch to solve the issue:
serhiy-storchaka commentedon May 16, 2024
I came to report a similar issue (discovered during writing tests for #118643). I thing they are the parts of the same problem.
The address like
"<one@example.com>," <two@example.com>can turn into two addresses<one@example.com>, <two@example.com>after re-folding.pythongh-80222: Fix email address header folding with long quoted-string
medmunds commentedon Aug 6, 2024
Still an issue in 3.13. #security-issue.
5 remaining items
pythongh-80222: Fix email address header folding with long quoted-str…
pythongh-80222: Fix email address header folding with long quoted-str…
pythongh-80222: Fix email address header folding with long quoted-str…
[3.13] gh-80222: Fix email address header folding with long quoted-st…
[3.12] gh-80222: Fix email address header folding with long quoted-st…
pythongh-80222: Fix email address header folding with long quoted-str…
[3.11] gh-80222: Fix email address header folding with long quoted-st…
[3.10] pythongh-80222: Fix email address header folding with long quo…
[3.10] gh-80222: Fix email address header folding with long quoted-st…
[3.9] gh-80222: Fix email address header folding with long quoted-str…