Problem/Motivation

Follow-up from #1856562-31: Convert "Subject" and "Message" into Message base fields.

1) An attacker can sent contact messages with arbitrary unfiltered HTML. This is a concern for security however it has been cleared by the security team for discussion in public, see #59.

To reproduce:
1. Enable the contact module
2. Enable the swiftmailer module to allow sending HTML mails
3. Use the mailsystem module to set swiftmailer as the default formatter and transport
4. Grant permission for anonymous users to use the site-wide contact form
5. As an anonymous user send a contact message containing arbitrary malicious HTML

The attacker could insert script tags even but there isn't much point as the mail client won't run scripts. The real threat is to insert disguised malicious links into a mail that the site admin is likely to trust because it comes from her own site (with all the correct headers to convince the mail client it's not spam).

For example the attacker could send a message that looked like ordinary junk mail. Then at the end it has a link "block future messages from this user". The site admin might think wow my developer added this great new feature yes I'll click that. The link leads to a fake site that looks like the log in page of their site, so they happily enter their password.

2) Contact messages from ordinary friendly users are corrupted if the message resembles HTML. For example

If a

Becomes corrupted to

If a**

Explanation

The contact_message entity renders to HTML markup exactly as expected like any other entity. The problem is that this HTML is decoded through MailFormatHelper::htmlToText() twice.
1) in \Drupal\contact\MessageViewBuilder::view()
2) in \Drupal\Core\Mail\Plugin\Mail\PhpMail::format()

Or in the case of using SwiftMailer, the HTML is converted to text in MessageViewBuilder::view() which gives the original unfiltered user input. However the object still implements MarkupInterface which is a clear breach of the terms of the interface

If there is any risk of the object's __toString() method returning user-entered data that has not been filtered first, it must not be used.

Swiftmailer sees that the object implements MarkupInterface so trusts it and displays the output without any escaping.

Proposed resolution

Remove the decoding from \Drupal\contact\MessageViewBuilder::view().

The documentation for MailInterface::format() refers to hook_mail() which states clearly that this is allowed

* - body: An array of lines containing the message to be sent. Drupal will
* format the correct line endings for you. MailManagerInterface->mail()
* sets this to an empty array when the hook is invoked. The array may
* contain either strings or objects implementing
* \Drupal\Component\Render\MarkupInterface.

Remaining tasks

  1. Maybe write text for release note??
  2. Raise a follow-on issue because PhpMail converts incorrectly: it runs MailFormatHelper::htmlToText() even if the line is already a plain text string. This causes corruption of any message that resembles HTML (for example a user account mail containing a < character).

User interface changes

None.

API changes

None. The documentation for the API has been improved to clarify the responsibilities of the mail plug-in.

It is possible that a Contrib mail plug-in is missing the correct code to convert but has been getting away with it until now. In that case the plugin already exhibits this bug if used with popular modules such as swiftmailer, so this scenario is unlikely except for modules with very low numbers of sites using them. The consequences of this problem are that contact messages could be sent as plain-text but including markup - there is no security risk.

We should consider adding a warning to the release note.

CommentFileSizeAuthor
#99 interdiff.txt574 bytesandypost
#97 contact-decode-D8.2223967-91.patch5.01 KBadamps
#91 contact-decode.2223967-interdiff-87-91.txt1.66 KBadamps
#91 contact-decode.2223967-91.patch5.01 KBadamps
#89 core-2223967-89.patch4.79 KBreplicaobscura
#88 contact-decode.2223967-interdiff-85-87.txt489 bytesadamps
#88 contact-decode.2223967-87.patch4.84 KBadamps
#85 contact-decode.2223967-interdiff-72-85.txt4.67 KBadamps
#85 contact-decode.2223967-85.patch4.59 KBadamps
#81 interdiff-2223967-72-81.txt3.84 KBshaktik
#81 2223967-81.patch5.56 KBshaktik
#77 interdiff-2223967-72-77.txt3.18 KBshaktik
#77 fixed-assertEquals-2223967-77.patch5.78 KBshaktik
#72 interdiff-2223967-70-72.txt1.26 KBsja112
#72 2223967-72.patch3.96 KBsja112
#70 interdiff-2223967-68-70.txt1.13 KBsja112
#70 2223967-70.patch3.78 KBsja112
#68 interdiff-2223967-66-68.txt1.36 KBsja112
#68 2223967-68.patch4.9 KBsja112
#66 2223967-66.patch4.86 KBjofitz
#57 drupal_contact_do-not-double-escape-message-body-2223967-57.patch2 KBmxr576
#57 drupal_contact_do-not-double-escape-message-body-2223967-57-test-only.patch1.05 KBmxr576
#53 2223967-53.patch5.73 KBpminf
#53 interdiff_52_53.txt593 bytespminf
#52 2223967-52.patch5.15 KBgogowitsch
#50 2223967-50.patch5.15 KBpminf
#46 2223967-46.patch5.13 KBjofitz
#46 interdiff-44-46.txt1.1 KBjofitz
#44 2223967-44.patch5.17 KBjofitz
#44 interdiff-41-44.txt1.43 KBjofitz
#41 2223967-41.patch5.23 KBjofitz
#41 interdiff-38-41.txt3.92 KBjofitz
#38 do_not_encode_a_contact-2223967-38.patch1.17 KBreplicaobscura
#34 interdiff.txt1.72 KBmanuel garcia
#34 do_not_encode_a_contact-2223967-34.patch5.18 KBmanuel garcia
#27 2223967-27.patch5.54 KBoleksiy
#23 2223967-23.patch77.6 KBnaveenvalecha
#21 interdiff_18-21.txt2.3 KBLKS90
#21 do_not_encode_a_contact-2223967-21.patch5.54 KBLKS90
#18 do_not_encode_a_contact-2223967-18.patch5.58 KBLKS90
#17 Screen Shot 2015-11-02 at 10.59.56.png57.89 KBLKS90
#15 Screen Shot 2015-09-28 at 11.09.04.png31.82 KBLKS90
#14 Screen Shot 2015-09-28 at 10.41.48.png82.15 KBLKS90
#14 Screen Shot 2015-09-28 at 10.41.42.png84.72 KBLKS90
#14 Screen Shot 2015-09-28 at 10.41.36.png84.2 KBLKS90
#7 interdiff_3-7.txt450 bytesLKS90
#7 do_not_encode_a_contact-2223967-7.patch940 bytesLKS90
#4 contact-mail-html-2443457-4.patch587 bytesberdir
#3 contact-mail-html-2443457-3.patch2.77 KBberdir

Comments

andypost’s picture

Issue tags: +Needs beta evaluation

This is existing bug since d7, so could be postponed til 8.1

andypost’s picture

But also can be a contrib blocker because there's no way to get original message in mail backend

berdir’s picture

Status: Active » Needs review
StatusFileSize
new2.77 KB

Looks like this changed but still worth fixing, it prevents to theme those mails as HTML. Not sure what exactly is going to happen with this, but this might help.

Needs manual testing of the output with and without a HTML mail formatter/sender.

berdir’s picture

StatusFileSize
new587 bytes

That had some unrelated changes.

berdir’s picture

Oh, there is another call to checkPlain() missed that. but we also need to get rid of the double-conversion in the patch.

The last submitted patch, 3: contact-mail-html-2443457-3.patch, failed testing.

LKS90’s picture

StatusFileSize
new940 bytes
new450 bytes

Here is a patch that also removes the checkPlain() call.
I can't find any other occurrence of the MailFormatHelper::htmlToText() function, The @todo about improving htmlToText is probably still valid, just a little out of place without a single htmlToText() call in the class.
The MailFormatHelper::htmlToText() calls in the test can be removed (without the test failing), but I'd like some feedback about that first, don't want to post 20 different patches :D.

juanse254’s picture

Status: Needs review » Reviewed & tested by the community

Tested locally as well, everything seems to work just fine.

andypost’s picture

Status: Reviewed & tested by the community » Needs review
+++ b/core/modules/contact/src/MessageViewBuilder.php
@@ -42,7 +42,7 @@ public function buildComponents(array &$build, array $entities, array $displays,
-          '#markup' => SafeMarkup::checkPlain($entity->getMessage()),
+          '#markup' => $entity->getMessage(),

is that secure?

LKS90’s picture

checkPlain() will be gone soon anyway and #markup is filtered with Xss::adminTagList. If we want more restrictive filtering, you have to add #allowed_tags = {TAGS GO HERE}.

But I don't know which tags should be allowed in such a message?

berdir’s picture

What we basically need/want to test is how this behaves in combination with the default plain text mail plugin and with one that supports HTML.

So install and configure https://github.com/webflo/drupal-swiftmailer, then send a contact mail and check how it looks. Is the HTML in there, is it escaped, ...

I'd guess this also needs a reroll

Status: Needs review » Needs work

The last submitted patch, 7: do_not_encode_a_contact-2223967-7.patch, failed testing.

LKS90’s picture

Issue summary: View changes
Status: Needs work » Needs review
StatusFileSize
new84.2 KB
new84.72 KB
new82.15 KB

I think this is already been fixed in #2559605: Convert SafeMarkup::checkPlain() in render arrays to #plain_text. It uses the '#plain_text' key instead of the '#markup' like it did before.
The content-type is always plain-text (even though HTML might be selected). Is that the expected behaviour? I tested it without any patches, emails sent with the core and the swift mailer plugin (configured either as plain-text or HTML) all show up in the same way:

LKS90’s picture

StatusFileSize
new31.82 KB

Tested it with real emails. Yes, there is a new issue, the old issue seems resolved by the change in #2559605: Convert SafeMarkup::checkPlain() in render arrays to #plain_text but causes emails to be always sent as plain-text.

I sent the HTML email with the following content:

This is a more <b>specific</b> test, I think the <a href="http://d8.dev">emails</a> use markdown for their markup.

<i>This text is italic</i>

This is <sup>superscripted</sup> text.

I received this:

berdir’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests

The contact message body is one thing. That actually should be escaped because you *are* sending a plain text mail.

To send a HTML mail, you must set up something like swiftmailer. The HTML that I'd expect then is the one from the field templates for example.

In Simplenews, we have a test mail plugin and a test that simulates an HTML mail.

We should bring that into core and test a contact message, that just like SimplenewsSourceTest::testSendHTML() explicitly puts special characters like > and ' into mail subject and body and then make sure that it is printed as expected for both plain text (converted according to what we expect) and HTML (escaped exactly once for body). Subject is always plain.

LKS90’s picture

Issue summary: View changes
Status: Needs work » Needs review
StatusFileSize
new57.89 KB

Alright, I set up everything correctly this time, I think. The message is formatted now (at least the content I entered).

Source Content:

This is a more <b>specific</b> test, I think the <a href="http://d8.dev">emails</a> are formatted now.

<i>This text is italic</i>

This is <sup>superscripted</sup> text.

The email in my inbox:

The incorrect formatting for the unsubscribe link is an issue for simplenews I guess.

For the test-porting:

Extend testSendPersonalContactMessage() in ContactPersonalTest with a message that uses HTML formatting (which requires a test system like simplenews, see HTMLTestingMailSystem). That's what I'll be doing now, I'll post the patch tomorrow which extends the test.

LKS90’s picture

StatusFileSize
new5.58 KB

Alright, here is a patch that extends test coverage with HTML formatting for contact messages. It uses the testing mail sender from simplenews (removed references specific for simplenews and fixed the documentation [which is copy/paste in simplenews]). I changed the test old test slightly, adding the special characters to subject and body. This requires me to split the mail body on line breaks to extract the message.

I'll take a look at the old patch, but I think that one is useless by now.

andypost’s picture

+++ b/core/modules/contact/src/Plugin/Mail/HTMLTestingMailSystem.php
@@ -0,0 +1,46 @@
+ * Contains Drupal\contact\Plugin\Mail\HTMLTestingMailSystem.
...
+namespace Drupal\contact\Plugin\Mail;

Suppose this should live in one of contact\tests\modules

berdir’s picture

  1. +++ b/core/modules/contact/src/Plugin/Mail/HTMLTestingMailSystem.php
    @@ -0,0 +1,46 @@
    + */
    +class HTMLTestingMailSystem implements MailInterface {
    

    I would move this this next to TestMailCollector and also name it accordingly.

  2. +++ b/core/modules/contact/src/Tests/ContactPersonalTest.php
    @@ -103,6 +105,38 @@ function testSendPersonalContactMessage() {
    +    $message['message[0][value]'] = "This <i>is</i> a more <b>specific</b> <sup>test</sup>, I think the <a href='http://d8.dev'>emails</a> are formatted now.";
    

    As said above, the body itself should not be formatted, only the other part of the mail. I'll have to test this myself.

LKS90’s picture

Moved the test, still passing even though it shouldn't (according to your second point in #20).

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

naveenvalecha’s picture

Issue tags: -Needs beta evaluation, -Needs tests
StatusFileSize
new77.6 KB

Straight reroll against 8.1.x, we already have tests so removed the "Need tests" tag and it's 8.1.x target so also removed "Needs beta evaluation" tag as well.

pminf’s picture

Status: Needs review » Needs work

Applying the 2223967-23.patch from #23 failed for Drupal 8.1.8:

patching file `composer.lock'
Hunk #1 FAILED at 4.
Hunk #16 FAILED at 680.
[...]
35 out of 78 hunks FAILED -- saving rejects to composer.lock.rej
patching file `core/lib/Drupal/Core/Mail/Plugin/Mail/HTMLTestMailCollector.php'
patching file `core/modules/contact/src/Tests/ContactPersonalTest.php'

andypost’s picture

Issue tags: +Needs reroll
andypost’s picture

I think composer changes unrelated, and should be removed from patch

oleksiy’s picture

Status: Needs work » Needs review
StatusFileSize
new5.54 KB

Reroll of the previous patch #23

Status: Needs review » Needs work

The last submitted patch, 27: 2223967-27.patch, failed testing.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

andypost’s picture

Version: 8.2.x-dev » 8.3.x-dev
Status: Needs work » Needs review
naveenvalecha’s picture

Status: Needs review » Needs work
  1. +++ b/core/lib/Drupal/Core/Mail/Plugin/Mail/HTMLTestMailCollector.php
    @@ -0,0 +1,46 @@
    +/**
    + * @file
    + * Contains \Drupal\Core\Mail\Plugin\Mail\HTMLTestMailCollector.
    + */
    

    Remoe this docblock . this is not needed

  2. +++ b/core/lib/Drupal/Core/Mail/Plugin/Mail/HTMLTestMailCollector.php
    @@ -0,0 +1,46 @@
    +    $message['body'] = implode("\n\n", $message['body']);
    

    use PHP_EOL

claudiu.cristea’s picture

I see here only the test but nothing seems fixed.

  1. diff --git a/core/lib/Drupal/Core/Mail/Plugin/Mail/HTMLTestMailCollector.php b/core/lib/Drupal/Core/Mail/Plugin/Mail/HTMLTestMailCollector.php
    ...
    +++ b/core/lib/Drupal/Core/Mail/Plugin/Mail/HTMLTestMailCollector.php
    ...
    +class HTMLTestMailCollector implements MailInterface {
    

    s/HTMLTestMailCollector/HtmlTestMailCollector. Better TestHtmlMailCollector?

  2. +++ b/core/lib/Drupal/Core/Mail/Plugin/Mail/HTMLTestMailCollector.php
    @@ -0,0 +1,46 @@
    +/**
    + * @file
    + * Contains \Drupal\Core\Mail\Plugin\Mail\HTMLTestMailCollector.
    + */
    

    Should be removed.

  3. +++ b/core/lib/Drupal/Core/Mail/Plugin/Mail/HTMLTestMailCollector.php
    @@ -0,0 +1,46 @@
    +  public function mail(array $message) {
    

    This seem identical to \Drupal\Core\Mail\Plugin\Mail\TestMailCollector::mail(). Why not extending TestMailCollector to reuse the code?

  4. +++ b/core/modules/contact/src/Tests/ContactPersonalTest.php
    @@ -83,7 +83,9 @@ function testSendPersonalContactMessage() {
    +    $message_captured = trim($message_exploded[9] . ' ' . $message_exploded[10]);
    +    $this->assertTrue(strcmp($message_captured, $message['message[0][value]']) !== FALSE, 'Message body is in sent message.');
    

    Probably this deserves a comment, explaining what are we testing here.

manuel garcia’s picture

Issue tags: -Needs reroll
manuel garcia’s picture

Status: Needs work » Needs review
StatusFileSize
new5.18 KB
new1.72 KB

Attempting to address #31.1, #31.2, #32.1, #32.2, #32.3

Please have a look

Still to do #32.4

Status: Needs review » Needs work

The last submitted patch, 34: do_not_encode_a_contact-2223967-34.patch, failed testing.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

replicaobscura’s picture

This applied cleanly on 8.2.x, but no longer applies on 8.3 for me. Not positive why yet, if I get it solved I'll post more information or an updated patch.

replicaobscura’s picture

StatusFileSize
new1.17 KB

I hate to remove work that was done in the previous patch, but it seems core/modules/contact/src/Tests/ContactPersonalTest.php or an equivalent test file no longer exists. Simply removing the portion of the previous patch that modified that test makes it apply cleanly again in 8.3.

Here's a patch that handles that, it's identical otherwise. I didn't hide the old patch yet, because that test might still belong somewhere, but I just needed a patch that applies for now.

manuel garcia’s picture

Status: Needs work » Needs review
jibran’s picture

Version: 8.4.x-dev » 8.3.x-dev
Status: Needs review » Needs work
Issue tags: +Needs reroll

#38 is not correct reroll please reroll the #34 agian.

jofitz’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll
StatusFileSize
new3.92 KB
new5.23 KB

Re-rolled #34 (and threw in an interdiff from #38 for good measure).

Status: Needs review » Needs work

The last submitted patch, 41: 2223967-41.patch, failed testing.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

jofitz’s picture

Status: Needs work » Needs review
StatusFileSize
new1.43 KB
new5.17 KB

Correct test failures.
Fixed coding standards errors.

Status: Needs review » Needs work

The last submitted patch, 44: 2223967-44.patch, failed testing. View results

jofitz’s picture

Status: Needs work » Needs review
StatusFileSize
new1.1 KB
new5.13 KB

Correct test failure.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

jhedstrom’s picture

Status: Needs review » Needs work

All of the patches after #7 seem to be test-only changes, and don't jive with the actual bug fix described in the IS.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

pminf’s picture

Version: 8.6.x-dev » 8.7.x-dev
Status: Needs work » Needs review
StatusFileSize
new5.15 KB

Reroll of #46

Status: Needs review » Needs work

The last submitted patch, 50: 2223967-50.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

gogowitsch’s picture

Title: Do not encode a contact message twice » Add test for contact message
Issue summary: View changes
Status: Needs work » Reviewed & tested by the community
StatusFileSize
new5.15 KB

I changed two things in the patch:

  1. Fixed the deprecation notices in test: Replaced getUsername() with getDisplayName(). I picked getDisplayName() as the human-centric replacement instead of getAccountName() as the database-centric function. In default installations, both return the same string.
  2. Changed the order of subject and message back.
pminf’s picture

Title: Add test for contact message » Do not encode a contact message twice
Issue summary: View changes
Status: Reviewed & tested by the community » Needs review
StatusFileSize
new593 bytes
new5.73 KB

I've addressed #48 by adding missing changes from #7 to patch from #52.
@Gogowitsch I've reverted your issue description changes because I don't think we should separate the original proposed resolution to another issue.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

mxr576’s picture

So I originally reported this problem as a security issue and we could not agree if it is a security issue or not if Drupal allows sending emails with unescapsed XSS (that email clients _should_ block). I used Mailhog when I discovered this which is a stupid email client/collector for sure. https://security.stackexchange.com/questions/12568/is-e-mail-a-direct-ve...

@berdir realised that this issue is in the public issue queue for 6 years so we decided to close the security issue and go public with my patch. This was my original report, just to give you more context what is happening:

This module has an XSS vulnerability.

You can see this vulnerability by:
1. Enabling the module and the Swiftmailer module
2. Configuring the Swiftmailer module on /admin/config/swiftmailer/messages to use HTML message format and DO NOT respect the provided email format. In other words, always send out HTML mails.
3. Register a new user and go to its Contact form, ex: /user/2/contact
4. Type <script>alert('xss');</script> as message body and click on "Send message".

When the addressee opens the email the XSS gets executed because it was not properly escaped by the Contact module. Precisely, it was double escaped. Even if \Drupal\contact\MessageViewBuilder::view() tries to sanitize the string by calling \Drupal\Core\Mail\MailFormatHelper::htmlToText(), when this method gets called, the XSS string is already escaped by the field API. Calling \Drupal\Core\Mail\MailFormatHelper::htmlToText() just re-enables the XSS vulnerability. (The called Xss:filter() ignores the HTML escaped XSS string.)

Swiftmailer trusts the generated message body by the Contact module because of this: https://git.drupalcode.org/project/swiftmailer/blob/8.x-1.0-beta2/src/Pl...

The issue could be also reproduced with the STMP module if the HTML email sending is also enforced.

My patch takes a different direction that the last one, because I think the MailFormatHelper::htmlToText() should still be called if the input is a raw string and not an implementation of the MarkupInterface.

greggles’s picture

One clarification on this point:

we could not agree if it is a security issue or not

The Security Team is as agreed as we get that it is not a security issue and can be fixed in public.

mxr576’s picture

Yes, that is right. I have got a full explanation.

berdir’s picture

I don't see why you'd want to make it dynamic and change the direction of this issue at this point. I think it is a safe assumption at that point that non-Markup html strings are going to be converted later on. Swiftmailer would either do that or apply a basic format, although there's some ongoing discussions about that in the swiftmailer issue queue.

People have been using the existing fix for years and it has explicit test coverage.

mxr576’s picture

People have been using the existing fix for years and it has explicit test coverage.

So if it is stable then why it has not been merged yet?

I think it is a safe assumption at that point that non-Markup html strings are going to be converted later on.

I can also live with that, because the mailer service should decide how it handles the outgoing message. Including the transformation/sanitization of the content. But I think there should be a better/explicit guidance about how mailer implementation should handle this situation and that would solve this problem. When I bumpped into this I remember I was not sure which companent should I blame or patch and based on some digging it seemed reasonable to keep the sanitization in Contact module, just make it work better or similar how others work.

adamps’s picture

I have just found this public issue after I raised another security issue - @mxr576 we think alike! OK fine, it is been agreed to solve this in public but I fully agree with the idea:

So if it is stable then why it has not been merged yet?

I strongly support to commit this ASAP and backport to D8.

I think it is a safe assumption at that point that non-Markup html strings are going to be converted later on.

I can also live with that

Sounds like we have a plan we can all agree on, so I have hidden patch #57 and scheduled patch #53 for retest on D9.1. PhpMail calls MailFormatHelper::htmlToText() without checking instanceof MarkupInterface. Personally I find that a bit strange, but we can't really change it now. So to be consistent the contact module must never convert.

But I think there should be a better/explicit guidance about how mailer implementation should handle this situation and that would solve this problem

Yes I agree with you @mxr576. However this would be a separate issue I think. We all want this patch to be committed ASAP so let's not try and add any changes to it:-)

mxr576’s picture

We all want this patch to be committed ASAP so let's not try and add any changes to it:-)

We all want this patch a real fix to be committed ASAP =]

Since #53 there is a TestHtmlMailCollector in Drupal core provided by mail_html_test module for example and I would also reconsider the need for those changes that has happened in testSendPersonalContactMessage().

It was not a coincidence that the test in #57 was much lightweight than in #53.

adamps’s picture

Status: Needs review » Needs work

It was not a coincidence that the test in #57 was much lightweight than in #53.

Sure it can make sense to improve the patch if there are good reasons. I would say that the key points are

  1. include an interdiff
  2. explain why you made each change (which you did a bit in #64 but not so much in #57)
  3. keep MessageViewBuilder as it was in #53

In any case #53 fails on D9 so it needs work

jofitz’s picture

Status: Needs work » Needs review
StatusFileSize
new4.86 KB

Re-rolled patch from #53 for 9.1.x.

Status: Needs review » Needs work

The last submitted patch, 66: 2223967-66.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

sja112’s picture

Status: Needs work » Needs review
StatusFileSize
new4.9 KB
new1.36 KB

I have updated the patch to fix the failed test case.

adamps’s picture

Status: Needs review » Needs work

Thanks @sja112

In #64 @mxr576 pointed out

Since #53 there is a TestHtmlMailCollector in Drupal core provided by mail_html_test

So I propose that we need to fix this patch to use the existing one, rather than adding a duplicate one.

sja112’s picture

Status: Needs work » Needs review
StatusFileSize
new3.78 KB
new1.13 KB

@adamps thanks for pointing out the issue. I missed this. I have updated the patch.

Status: Needs review » Needs work

The last submitted patch, 70: 2223967-70.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

sja112’s picture

Status: Needs work » Needs review
StatusFileSize
new3.96 KB
new1.26 KB

Updated patch to fix the failing tests.

adamps’s picture

Status: Needs review » Reviewed & tested by the community

Great looks good to me.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs issue summary update

I would be really great if the issue summary can be updated:

  • To describe the problem better - check_plain() doesn't exist anymore
  • Describe the possible BC implications of making this change
  • Describe the security implications of making this change

One thing that would be good to know is if another other mailer is relying on this code for security? Another thought is that if we're agreeing that it is the mailer's responsibility to ensure the message body is safe then we need to make this extremely explicit.

  1. +++ b/core/modules/contact/tests/src/Functional/ContactPersonalTest.php
    @@ -108,6 +108,36 @@ public function testSendPersonalContactMessage() {
    +    $this->assertEqual($mail['to'], $this->contactUser->getEmail());
    +    $this->assertEqual($mail['from'], $this->config('system.site')->get('mail'));
    +    $this->assertEqual($mail['reply-to'], $this->webUser->getEmail());
    +    $this->assertEqual($mail['key'], 'user_mail');
    +    $this->assertEqual($mail['subject'], $subject);
    

    Let's use the PHPunit method here $this->assertEquals()

  2. +++ b/core/modules/contact/tests/src/Functional/ContactPersonalTest.php
    @@ -108,6 +108,36 @@ public function testSendPersonalContactMessage() {
    +    $this->assertTrue(strpos($mail['body'], 'Hello ' . $variables['@recipient-name']) !== FALSE, 'Recipient name is in sent message.');
    +    $this->assertTrue(strpos($mail['body'], $this->webUser->getDisplayName()) !== FALSE, 'Sender name is in sent message.');
    +    $this->assertTrue(strcmp(PlainTextOutput::renderFromHtml($message_captured), trim($message['message[0][value]'])) !== FALSE, 'Message was found in mail.');
    

    $this>assertStringContainsString() and there's no need for a message.

shaktik’s picture

Assigned: Unassigned » shaktik

I am working on it.

Rkumar’s picture

Even href shouldn't be like this

<a href='http://d8.dev'>emails</a>
and Please elaborate in comments about this

$message_exploded = explode(PHP_EOL, $mail['body']);
+    $message_captured = trim($message_exploded[8]) . ' ' . trim($message_exploded[9]) . ' ' . trim($message_exploded[10]);
shaktik’s picture

Status: Needs work » Needs review
StatusFileSize
new5.78 KB
new3.18 KB

updated as per comment #74

shaktik’s picture

Assigned: shaktik » Unassigned
alexpott’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/contact/tests/src/Functional/ContactPersonalTest.php
    @@ -80,20 +83,17 @@ public function testSendPersonalContactMessage() {
    -    $this->assertEqual($mail['to'], $this->contactUser->getEmail());
    -    $this->assertEqual($mail['from'], $this->config('system.site')->get('mail'));
    -    $this->assertEqual($mail['reply-to'], $this->webUser->getEmail());
    -    $this->assertEqual($mail['key'], 'user_mail');
    +    $this->assertEquals($mail['to'], $this->contactUser->getEmail());
    +    $this->assertEquals($mail['from'], $this->config('system.site')->get('mail'));
    +    $this->assertEquals($mail['reply-to'], $this->webUser->getEmail());
    +    $this->assertEquals($mail['key'], 'user_mail');
    ...
    -    $this->assertEqual($mail['subject'], $subject, 'Subject is in sent message.');
    -    $this->assertStringContainsString('Hello ' . $variables['@recipient-name'], $mail['body'], 'Recipient name is in sent message.');
    -    $this->assertStringContainsString($this->webUser->getDisplayName(), $mail['body'], 'Sender name is in sent message.');
    -    $this->assertStringContainsString($message['message[0][value]'], $mail['body'], 'Message body is in sent message.');
    +    $this->assertEquals($mail['subject'], $subject, 'Subject is in sent message.');
    

    These changes are not part of the change. And are out-of-scope. Yes it's a bit odd because the same test has two styles but eventually assertEqual() will be removed so adding more makes more future work.

    Also test assertions unrelated to this change are being removed here.

  2. +++ b/core/modules/contact/tests/src/Functional/ContactPersonalTest.php
    @@ -108,6 +108,36 @@ public function testSendPersonalContactMessage() {
    +    // Assert mail content.
    +    $this->assertTrue(strpos($mail['body'], 'Hello ' . $variables['@recipient-name']) !== FALSE, 'Recipient name is in sent message.');
    +    $this->assertTrue(strpos($mail['body'], $this->webUser->getDisplayName()) !== FALSE, 'Sender name is in sent message.');
    +    $this->assertTrue(strcmp(PlainTextOutput::renderFromHtml($message_captured), trim($message['message[0][value]'])) !== FALSE, 'Message was found in mail.');
    

    These should be assertStringContainsString()

adamps’s picture

Title: Do not encode a contact message twice » Do not decode a contact message twice
Issue summary: View changes
Issue tags: -Needs issue summary update +Security improvements

Thanks @alexpott.

I updated the issue summary and fixed the title: the problem is that we are encoding twice rather the decoding twice.

Describe the possible BC implications of making this change

My feeling is that we are not changing an API; rather we are fixing code to match the existing API. (True the API is poorly defined, but we can deduce what it needs to be through logical analysis and examination of existing code.) So there isn't a direct BC problem. It's true that some sites might be relying on the current behaviour. It's likely not very common and I see no way to avoid it. See the IS for more discussion.

Describe the security implications of making this change. One thing that would be good to know is if another other mailer is relying on this code for security

This change is strictly positive for security. It solves some security risks and doesn't introduce new ones. The previous issue title was misleading here.

Another thought is that if we're agreeing that it is the mailer's responsibility to ensure the message body is safe then we need to make this extremely explicit.

I became maintainer for simplenews and then swiftmailer and for sure I can confirm that the mail API is lightly documented and hard to understand how to use accurately. It's a way bigger problem that just this one aspect though so I'm not sure if it's practical to consider it as part of this issue. Currently the entire API is specified in about 20 lines of code and only 2-3 lines per element, some elements not even mentioned. Could we raise a separate issue to cover this?

shaktik’s picture

Status: Needs work » Needs review
StatusFileSize
new5.56 KB
new3.84 KB
alexpott’s picture

It solves some security risks and doesn't introduce new ones.

What security risk does this solve? That's the bit I don't understand yet. As far as I can see this change results in html now being outputted by the 'mail' view mode of the contact message. Any code that was relying on this now has to expect potentially un-safe HTML there now. This looks like increasing risks, not lowering them.

Is it worth considering to add an html-mail view mode and use that instead. And deprecate the mail view mode mode in favour of this new one?

adamps’s picture

@alexpott I'm happy to have a chat if that would be easier as I fear I am just repeating myself. I already tried to explain in the IS and I'm unsure how to explain much better, however I will try.

You do an amazing job to commit so many Core issues to help the community and I guess that means you likely spend little time in Contrib. Although Core doesn't support sending HTML mails they are working in Contrib. Swiftmailer module is installed on 37000 D8 sites. Modules such as Simplenews (18000 D8 sites) send messages that contain HTML. It all works with the current interfaces. I agree it's not very well documented currently how to do it. However in the case of unclear documentation then it makes sense to accept the widespread current usage. In any case, as far as I can see, the solution proposed in this patch is the only way that works as I tried to explain in the IS. The current code is clearly bugged as per point 2 of the IS problem/motivation.

What security risk does this solve?

I tried to explain that in the IS. The security risk is when using a Contrib mail plugin that generates HTML mails. An anonymous user can send contact form messages containing arbitrary unsafe HTML. The user types a message containing HTML; the contact module encodes it to SafeMarkup, then MessageViewBuilder decodes it again. However because the decode occurred in #post_render the output is still an implementation of SafeMarkup. This is a severe violation of the SafeMarkup interface as the markup is entirely unfiltered. Hence @mxr576 and I independently raised this as security issue but the security team cleared it for a public issue.

Any code that was relying on this now has to expect potentially un-safe HTML there now.

In comparison to the previous paragraph the HTML output is safe - it is a valid implementation of MarkupInterface.

I agree that a mail plug-in code could be missing the call to MailFormatHelper::htmlToText(). However if so then

  • They wrote their code without looking at the reference implementation PhpMail
  • The code is already broken if used with other Contrib modules such as SwiftMailer

Is it worth considering to add an html-mail view mode and use that instead. And deprecate the mail view mode mode in favour of this new one?

I don't really follow how that strategy would work - which view mode would actually be used by contact_mail()? A new view mode sounds like a lot of work for every site that uses the contact module.

Rkumar’s picture

Status: Needs review » Needs work

@shaktik Still, comments are pending to implement.

adamps’s picture

Issue summary: View changes
Status: Needs work » Needs review
StatusFileSize
new4.59 KB
new4.67 KB

New patch that address all the comments from #74, #76, #79.

I fixed the tests which wasn't testing anything in the original patch (it failed to send the message then made some asserts on the message from the first part of the test).

Another thought is that if we're agreeing that it is the mailer's responsibility to ensure the message body is safe then we need to make this extremely explicit.

Good idea. I have improved the comment on MailInterface::format().

Good news for removing an worries about back-compatibility: the interface definition for $message refers to hook_mail() which is very clear that the sender can pass plain text or markup. Therefore it seems there is no doubt that the plug-in must handle either. We are not changing the interface, we are simply improving the documentation.

adamps’s picture

Issue summary: View changes

Status: Needs review » Needs work

The last submitted patch, 85: contact-decode.2223967-85.patch, failed testing. View results

adamps’s picture

Status: Needs work » Needs review
StatusFileSize
new4.84 KB
new489 bytes
replicaobscura’s picture

StatusFileSize
new4.79 KB

Here's a version that applies on Drupal 8.9. I am not sure if it applies on 9.x at this point, or if #88 does for that matter, so I'll just leave this here for now without changing the status or hiding any other patches.

jonathanshaw’s picture

I think the patch is good, and I will RTBC if we can clarify a few things.

Basically I think the documentation introduced is great, but should be even more verbose.

  1. +++ b/core/lib/Drupal/Core/Mail/MailInterface.php
    @@ -15,12 +15,16 @@ interface MailInterface {
    -   * are converted to plain-text by the Drupal\Core\Mail\Plugin\Mail\PhpMail
    

    Elsewhere in this issue this is called the "reference implementation". Maybe it's usfeul to keep referring to it.

  2. +++ b/core/lib/Drupal/Core/Mail/MailInterface.php
    @@ -15,12 +15,16 @@ interface MailInterface {
    +   * converted, joined and wrapped. The string can be either plain text or
    

    The meaning of "converted, joined and wrapped" is not clear.

  3. +++ b/core/lib/Drupal/Core/Mail/MailInterface.php
    @@ -15,12 +15,16 @@ interface MailInterface {
    +   * be returned in $message['plain']. Each line must be converted if it
    +   * doesn't match the desired output.  You can use
    

    I'm not clear what "converted" and "desired output" mean here.

  4. +++ b/core/lib/Drupal/Core/Mail/MailInterface.php
    @@ -15,12 +15,16 @@ interface MailInterface {
    +   * \Drupal\Core\Mail\MailFormatHelper::htmlToText() to convert to plain text
    +   * or \Drupal\Component\Utility\Html\Html::Escape() to convert to HTML.
    

    Presumably whether you can use these depends on what format your original text is?

  5. The IS says in "remaining tasks":

    Raise a follow-on issue because PhpMail converts incorrectly: it runs MailFormatHelper::htmlToText() even if the line is already a plain text string. This causes corruption of any message that resembles HTML (for example a user account mail containing a < character).

    I'd like to see this issue created before RTBC, and a note on how it relates to this issue. Does anything about the changes here make that issue a worse problem?

adamps’s picture

Thanks @jonathanshaw. Here is another patch with an improved comment.

Regarding 5. I will raise it. No the problem has not become worse. There is only a small relationship between the two issues: this issue improves the interface comment and so makes it more obvious that PhpMail does not obey the interface.

jonathanshaw’s picture

Status: Needs review » Reviewed & tested by the community
adamps’s picture

  • catch committed 945d76a on 9.1.x
    Issue #2223967 by jofitz, LKS90, AdamPS, sja112, shaktik, pminf, Berdir...

  • catch committed c2a885f on 9.0.x
    Issue #2223967 by jofitz, LKS90, AdamPS, sja112, shaktik, pminf, Berdir...
catch’s picture

Version: 9.1.x-dev » 8.9.x-dev
Status: Reviewed & tested by the community » Patch (to be ported)
Issue tags: +Needs reroll

Committed/pushed to 9.1.x and backported to 9.0.x. Needs a re-roll for 8.9.x

adamps’s picture

Status: Patch (to be ported) » Needs review
Issue tags: -Needs reroll
StatusFileSize
new5.01 KB

Great many thanks @catch

andypost’s picture

8.9.x branch tests are broken, still waiting for test

andypost’s picture

Status: Needs review » Reviewed & tested by the community
StatusFileSize
new574 bytes

The only change from commited patch is 8.9.x still using public visibility for $modules

Here's interdiff and RTBC

mxr576’s picture

RTBC++

  • catch committed 05083ea on 8.9.x
    Issue #2223967 by AdamPS, jofitz, LKS90, sja112, shaktik, pminf, Berdir...
catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed 05083ea and pushed to 8.9.x. Thanks!

adamps’s picture

Great many thanks @catch

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

chi’s picture

The formating of contact messages is kind of broken now. Note multiple new lines and weird indentation.

admin (http://localhost/d9/docroot/user/1) sent a message using the contact
form at http://localhost/d9/docroot/contact.



      Message
                Test