Index: Lib/email/header.py =================================================================== --- Lib/email/header.py (revision 87496) +++ Lib/email/header.py (working copy) @@ -46,7 +46,11 @@ # For use with .match() fcre = re.compile(r'[\041-\176]+:$') +# Find a header embeded in a putative header value. Used to check for +# header injection attack. +_embeded_header = re.compile(r'\n[^ \t]+:') + # Helpers _max_append = email.quoprimime._max_append @@ -316,7 +320,11 @@ if len(lines) > 1: formatter.newline() formatter.add_transition() - return formatter._str(linesep) + value = formatter._str(linesep) + if _embeded_header.search(value): + raise HeaderParseError("header value appears to contained " + "an embeded header: {!r}".format(value)) + return value def _normalize(self): # Step 1: Normalize the chunks so that all runs of identical charsets Index: Lib/email/test/test_email.py =================================================================== --- Lib/email/test/test_email.py (revision 87496) +++ Lib/email/test/test_email.py (working copy) @@ -561,7 +561,19 @@ "attachment; filename*=utf-8''Fu%C3%9Fballer%20%5Bfilename%5D.ppt", msg['Content-Disposition']) + # Issue 5871: reject an attempt to embed a header inside a header value + # (header injection attack). + def test_embeded_header_via_Header_rejected(self): + msg = Message() + msg['Dummy'] = Header('dummy\nX-Injected-Header: test') + self.assertRaises(errors.HeaderParseError, msg.as_string) + def test_embeded_header_via_string_rejected(self): + msg = Message() + msg['Dummy'] = 'dummy\nX-Injected-Header: test' + self.assertRaises(errors.HeaderParseError, msg.as_string) + + # Test the email.encoders module class TestEncoders(unittest.TestCase): def test_encode_empty_payload(self):