Closed Bug 1526456 Opened 5 years ago Closed 4 years ago

junk after address can be parsed as the address

Categories

(Thunderbird :: Mail Window Front End, defect)

defect
Not set
normal

Tracking

(thunderbird_esr68+ fixed, thunderbird75 affected, thunderbird76 fixed)

RESOLVED FIXED
Thunderbird 76.0
Tracking Status
thunderbird_esr68 + fixed
thunderbird75 --- affected
thunderbird76 --- fixed

People

(Reporter: whucjj, Assigned: mkmelin)

References

(Regressed 1 open bug)

Details

Attachments

(3 files, 5 obsolete files)

User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36

Steps to reproduce:

When an attacker sends the following email with a malformed email address to a victim,

From: <any@attacker.com>admin@legitmate.com
To: <victim@victim.com>
...

Thunderbird takes admin@legitmate.com as the email sender, but many email providers like hotmail, iCloud, protonmail use any@attacker.com for DMARC authentication.

This bug allows an attacker to spoof any websites, even if those websites deploy strict DMARC policy. Attachment is an example that exploits this bug to spoof Chase bank email.

Actual results:

Thunderbird mistakenly parses <any@attacker.com>admin@legitmate.com as admin@legitmate.com.

Expected results:

Thunderbird should display <any@attacker.com>admin@legitmate.com as any@attacker.com or <any@attacker.com>admin@legitmate.com, so that Thunderbird users could know that this email could be a spoofing or phishing email.

Thunderbird does not enforce DMARC, that's typically enforced by the receiving mail server. The only checking Thunderbird does for sender legitimacy is with S/MIME signed messages (and PGP signed with the Enigmail addon). Otherwise mail is completely forgable.

The standard for mail display is the bit outside the <> is the "pretty name" and should be the primary display piece. That's what thunderbird is doing. I believe your request is something like

If the display name "looks like" an email address, show the "real" email address instead

That might be a reasonable change.

Alternately your request could be read as "Thunderbird should enforce DMARC". Without that the "real" email could be just as fraudulent as the display name.

Group: mail-core-security
Status: UNCONFIRMED → NEW
Ever confirmed: true

Hi Daniel,

Thanks for your reply.

The standard for mail display is the bit outside the <> is the "pretty name" and should be the primary display piece. That's what thunderbird is doing. I believe your request is something like
If the display name "looks like" an email address, show the "real" email address instead

In the above example, Thunderbird actually takes the bit outside the <> as real email address, not display name.

I'm not suggesting Thunderbird should enforce DMARC. The problem here is that Thunderbird is inconsistent with receiving mail server on understanding the malformed email address, which could lead to security issues. The receiving mail server authenticates one domain, but Thunderbird shows another one.

Thanks,
Jianjun

Magnus, any action on this one. (Still looking at bug I haven't seen from the last 365 days.)

Flags: needinfo?(mkmelin+mozilla)
Assignee: nobody → mkmelin+mozilla
Status: NEW → ASSIGNED
Component: Security → Mail Window Front End
Flags: needinfo?(mkmelin+mozilla)
Summary: A email address parsing bug might be used for email spoofing → junk after address can be parsed as the address
Attached patch bug1526456_after_address.patch (obsolete) — Splinter Review

Fixed lint issue.

Attachment #9104311 - Attachment is obsolete: true
Attachment #9104311 - Flags: review?(jorgk)
Attachment #9104738 - Flags: review?(jorgk)

Sorry about the delay here, other fires burning hotter. Here's try, which I did for other purposes:
https://treeherder.mozilla.org/#/jobs?repo=try-comm-central&revision=c26ac5daf508d0c216f86600830904cfde906794

Comment on attachment 9104738 [details] [diff] [review]
bug1526456_after_address.patch

Please look at this test case:
From: <attacker@example.com> <friend@example.com>
TB 68 shows "attacker@example.comfriend"@example.com which is aligned with its usual behaviour. With your patch I get: attacker@example.comfriend example.com.
Attachment #9104738 - Flags: review?(jorgk)
Comment on attachment 9104738 [details] [diff] [review]
bug1526456_after_address.patch

yes, this looks like it doesn't cover Jörg's example from comment 7.
Attachment #9104738 - Flags: review-
Attached patch 1526456-v2.patch (obsolete) — Splinter Review

Magnus, this tries to improve your patch. Was your intention that everything after "name" <address> should get ignored?

With this patch, we ignore the angle brackets after we're done parsing an address.

Also, this code seems to handle multiple addresses separated by comma, so I think we need to reset the new flag after a comma.

Attachment #9125656 - Flags: review?(mkmelin+mozilla)
Attached patch 1526456-v2.patchSplinter Review

Thanks for improving the patch.
I added a little more tests around this. The change to throw away the name when we're after the address was not entirely correct - the new tests cover this.

Attachment #9104738 - Attachment is obsolete: true
Attachment #9125656 - Attachment is obsolete: true
Attachment #9125656 - Flags: review?(mkmelin+mozilla)
Attachment #9126365 - Flags: review?(kaie)
Comment on attachment 9126365 [details] [diff] [review]
1526456-v2.patch

>         } else if (token === "@") {
>+          if (afterAddress) {
>+            //name = "";
>+            continue;
>+          }

Was it intentional that you left in the code commented out?

No, I'll remove that.

Comment on attachment 9126365 [details] [diff] [review]
1526456-v2.patch

r+ with removed //name= "";
Attachment #9126365 - Flags: review?(kaie) → review+

Pushed by mkmelin@iki.fi:
https://hg.mozilla.org/comm-central/rev/cd29db7dc105
junk after address should not be parsed as the address. r=kaie

Status: ASSIGNED → RESOLVED
Closed: 4 years ago
Resolution: --- → FIXED
Target Milestone: --- → Thunderbird 75.0

Jörg, what about comment 8 and 9 ? ;)
I hope we have fixed your scenario.

Hmm, those comments don't state whether
From: <attacker@example.com> <friend@example.com>
is fixed or not. Maybe "With this patch, we ignore the angle brackets after we're done parsing an address" includes that case. But you didn't add it as a test case as far as I can see.

The only reason I created the patch in comment 9 was to fix the scenario you reported in comment 7.
I had tested my patch locally with your example, and it worked.

You're right, we didn't add a test for "trailing junk that also uses <> brackets".

Add more tests. The above case was handled for header parsing already, but not for makeFromDisplayAddress.

Attachment #9126679 - Flags: review?(kaie)

If there's no ">", then indexOf(">") will return -1. If the second (EndIndex) parameter is given to .slice it will use that as (length-1). In other words, if there's no ">", your result will miss the last character.
Instead of cleanName.indexOf(">") should you use the following? (cleanName.indexOf(">") == -1) ? cleanName.length : (cleanName.indexOf(">")

I need to write down your logic, to ensure I understand it.

  • the intention is to search for the first <> block after a potential "" prefix.
  • code removes a part "anything" (but keeps empty double "" or single ").
  • Scenario (A). If, after removal, if there's still a "<", then we use that smaller string (outside quotes) to search for the email address.
  • Scenario (B). Else, after removal, if there's no longer a "<", then an email address is inside the quotes, only. So let's go back, use the full string, and take the address from inside the quotes.
  • You use the first index of "<" to search for the address in the (potentially) reduced string, but you use the last index of "<" of the full string to identify the cut off position for the display name.

What about input: "name" <name@evil.com> <someone-else@bad.com> <name@evil.commercial.org> ?
It's scenario A. The reduced string is <name@evil.com> <someone-else@bad.com> <name@evil.commercial.org>.
addr is name@evil.com.
displayname is "name" <name@evil.com> <someone-else@bad.com>

In scenario B, if the < is inside quotes, then your display name will have a starting quote, but no trailing quote.

input: "full-name <name@evil.com>" something-else
addr = name@evil.com
displayname = "full-name

Also, what happens if there's a > character occurring before the first < ?
It might be good to rewrite this function with more error checking.

I haven't tested this code, but maybe something like this could be used:

  // Construct a single email address from a name <local@domain> token.
  _makeSingleAddress(aToken) {
    let addr = "";
    // Remove optional leading whitespace and an optional section in double quotes.
    let withoutIntroName = aToken.replace(/^\s*"[^"\s]*"/, "");
    let displayName = aToken.replace(/^\s*("[^"\s]*").*$/, "$1");
    let openPos = withoutIntroName.indexOf("<");
    if (openPos != -1) {
      let closePos = withoutIntroName.indexOf(">", openPos+1);
      if (closePos != -1) {
        addr = withoutIntroName.slice(openPos+1, closePos);
      } else {
        // no trailing ">" is illegal, so we keep the emptry string for addr
        return this.makeMailboxObject(aToken, "");
      }
    } else {
      // There was an optional start section with quotes, but there's no more "<" in the remaining text.
      // Go back and allow searching inside the quotes, but remove the surrounding quotes.
      let noQuotes = displayName.slice(1, -1);

      openPos = noQuotes.indexOf("<");
      if (openPos != -1) {
        let closePos = noQuotes.indexOf(">", openPos+1);
        if (closePos != -1) {
          addr = noQuotes.slice(openPos+1, closePos);
          displayName = noQuotes.slice(0, openPos);
        } else {
          // no trailing ">" is illegal, so we keep the emptry string for addr
          return this.makeMailboxObject(aToken, "");
        }
      } else {
        return this.makeMailboxObject(aToken, "");
      }
    }
    return this.makeMailboxObject(displayName, addr);
  },

Note I have edited the above code multiple times. Will stop editing it now.

(In reply to Kai Engert (:KaiE:) from comment #21)

In scenario B, if the < is inside quotes, then your display name will have a starting quote, but no trailing quote.

input: "full-name <name@evil.com>" something-else
addr = name@evil.com
displayname = "full-name

I believe that's how it would be, and correctly so.
makeFromDisplayAddress is used for parsing user input inside the application, not parsing headers from incoming mails. If the input is bad enough, what's right gets hard to say.

Or no, that's incorrect.

Thanks for testing. I adjusted the patch and added some more test cases.

Attachment #9126679 - Attachment is obsolete: true
Attachment #9126679 - Flags: review?(kaie)
Attachment #9126745 - Flags: review?(kaie)

(In reply to Kai Engert (:KaiE:) from comment #20)

  • Scenario (B). Else, after removal, if there's no longer a "<", then an email address is inside the quotes, only. So let's go back, use the full string, and take the address from inside the quotes.

I think we probably don't want to, or at least the input is really invalid then.
I suppose there is a case to be made for that if the whole string is quoted, we could first just strip these outer quotes.

could someone else help to review the follow-up patch?

Flags: needinfo?(mkmelin+mozilla)

Paul, please take a look.

Attachment #9126745 - Attachment is obsolete: true
Attachment #9126745 - Flags: review?(kaie)
Flags: needinfo?(mkmelin+mozilla)
Attachment #9138029 - Flags: review?(paul)

I added the unquoting for the pasting case (e.g. you're pasting an address that happened to be quoted).

Comment on attachment 9138029 [details] [diff] [review]
bug1526456_junk_after-followup.patch

Review of attachment 9138029 [details] [diff] [review]:
-----------------------------------------------------------------

LGTM, with a few nits.

::: mailnews/mime/src/MimeJSComponents.jsm
@@ +427,5 @@
>      }
>      return output;
>    },
>  
>    // Construct a single email address from a name <local@domain> token.

While here, we should more clearly indicate what the aInput token looks like, for example:  
  "...from a |name <local@domain>| token."

Even better would be a doc comment that documents the param and return values.  A "single email address" sounds like a string but a MailboxObject is actually what is returned.

@@ +429,5 @@
>    },
>  
>    // Construct a single email address from a name <local@domain> token.
> +  _makeSingleAddress(aInput) {
> +    // If the whole string within quotes, unquote it first.

nit: "string within" -> "string is within"

@@ +433,5 @@
> +    // If the whole string within quotes, unquote it first.
> +    aInput = aInput.trim().replace(/^"(.*)"$/, "$1");
> +
> +    if (aInput.includes("<")) {
> +      // We don't want to look for address within quotes.

nit: "for address" -> "for the address"

@@ +447,5 @@
> +      let addr = cleanedInput.slice(
> +        cleanedInput.indexOf("<") + 1,
> +        cleanedInput.indexOf(">")
> +      );
> +      let lbracket = aInput.indexOf("<" + addr + ">");

I'd suggest for readability: lbracket -> leftBracketIndex
Attachment #9138029 - Flags: review?(paul) → review+
Pushed by mkmelin@iki.fi:
https://hg.mozilla.org/comm-central/rev/426213bc6cb9
handle additional cases of junk after email addresses. r=pmorris DONTBUILD
Target Milestone: Thunderbird 75.0 → Thunderbird 76.0
Attachment #9138029 - Flags: approval-comm-esr68?
Comment on attachment 9138029 [details] [diff] [review]
bug1526456_junk_after-followup.patch

[Triage Comment]
Attachment #9138029 - Flags: approval-comm-esr68? → approval-comm-esr68+
Regressions: 1628800
Regressions: 1629842
Regressions: 1632120
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: