Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Email content tampering vulnerability due to crafted file names #190

Open
motoyasu-saburi opened this issue Oct 18, 2023 · 0 comments
Open

Comments

@motoyasu-saburi
Copy link

I know this package is not already maintained.
However, the vulnerability does exist, and we will describe the issue here, including how to fix it.


The gopkg.in/gomail.v2 package has a problem with lack "filename" escaping in the Attachment File when sending an Email.
This allows "file name/extension tampering" with attached "filename" field.

Essentially, the following characters must be escaped when generating Content-Disposition headers in multipart/mixed

  • \r --> \\r or %0D
  • \n --> \\n or %0A
  • " --> \" or %22 (Not Escaped)

These are also defined as requirements in RFCs and HTTP Requests for Multipart/form-data (What's WG - HTML Spec) where Content-Disposition is used.

RFC 6266:
https://tools.ietf.org/html/rfc6266#appendix-D

Avoid including the "" character in the quoted-string form of the filename parameter, as escaping is not implemented by some user agents, and "" can be considered an illegal path character.

What's WG HTML Spec:
https://html.spec.whatwg.org/#multipart-form-data

For field names and filenames for file fields, the result of the encoding in the previous bullet point must be escaped by replacing any 0x0A (LF) bytes with the byte sequence %0A, 0x0D (CR) with %0D and 0x22 (") with %22. The user agent must not perform any other escapes.

As an example, I describe the normal case and the case where a crafted file is processed.


normal case:
Input Filename:

example.txt

Output Content-Disposition:

Content-Disposition: attachment; filename="example.txt"

Crafted file case:
Input Filename:

a.sh";\r\n\r\ndummy.txt

Output Content-Disposition:

Content-Disposition: attachment; filename="a.sh";

ndummy.txt"

Attack Scenario

if " is not escaped, additional fields can be added,
so that filenames can be tampered with,
Content-Disposition size, modification-date, etc fields can be inserted, and so on.

I think there are two main cases of abuse.

  1. Cases where integrity is destroyed by rewriting emails sent by the victim by file name.
  2. Cases that pass validation such as file extension at the time of upload and are converted
    to malware (extension is changed) at the time of email transmission

(1)
This case requires "strong" involvement of the victim, which I believe is a problem that is quite difficult to exploit.

The condition of the attack is that the filename of the attachment
in the e-mail sent by the victim must be set to the string entered by the attacker.

However, since it is up to the system implementer to decide how to use this package and how to send the mail, we do not believe that it is absolutely certain.

(2)
I believe this is the more likely case.

For example, let's say you have a system that uses this package behind a system such as ChatBot that receives customer communications and transfers files.

Since it is unlikely that customers will always send the correct file, file extension validation should be performed at the time of file upload, and those with no problems should be forwarded via email.

However, if this vulnerability exists, it is possible to rewrite
the File Extension to .txt when uploading and .sh when sending mail.

Remediation

As far as I have been able to find, the following services are escaping by \.

example code:

def escapeContentDispositionFilename(filename: String): String = {
  return filename.replaceAll("\r", "\\r")
                 .replaceAll("\n", "\\n")
                 .replaceAll("\"", "\\\"")
}

example response:


> Before
> Content-Disposition: attachment; filename="malicious.sh";dummy=.txt

> After
> Content-Disposition: attachment; filename="\"malicious.sh\";dummy=.txt"

Also, eliminating CLRF or Double-Quote characters blocking them with Validation can be effective.

Reference

Golang Impliments (safe code)
https://github.com/golang/go/blob/e0e0c8fe9881bbbfe689ad94ca5dddbb252e4233/src/mime/multipart/writer.go#L144

PoC

PoC Environment:

OS: macOS Monterey(12.3)
LANG ver: go1.18.2 darwin/arm64
gomail ver: v2.0.0-20160411212932-81ebce5c23df

Receiver Email Client: Gmail


  1. Issue a Credential for SMTP via Gmail, etc.

e.g.
a. Access: https://myaccount.google.com/apppasswords
b. Select "Mail" & Other
c. Click Generate & Copy "device application password"
(Use the password in step 3)

  1. Install Package
    go get gopkg.in/gomail.v2

  2. Generate Vuln Code & PoC File

Generate PoC File

$ touch 'test.sh\"; dummy=\"a.txt'

Please Edit [Email, SMTP-Credential, FilePath]

package main

import (
	"gopkg.in/gomail.v2"
)

func main() {
	// FIXME
	EMAIL := "kumagoro_alice@yahoo.co.jp"
	CREDENTIAL := "fsfu dyny prtk mnax"
	FILE_PATH := "/Users/kumagoro/work/shopgo/test.sh\"; dummy=\"a.txt"

	m := gomail.NewMessage()
	m.SetHeader("From", EMAIL)
	m.SetHeader("To", EMAIL)
	m.SetHeader("Subject", "Hello!")
	m.SetBody("text/html", "<h1>Hello</h1>")
	m.Attach(FILE_PATH)

	d := gomail.NewPlainDialer("smtp.gmail.com", 587, EMAIL, CREDENTIAL)

	if err := d.DialAndSend(m); err != nil {
		panic(err)
	}
}
  1. Run Code.

  2. Check received mail source.

e.g.
a. Access: https://mail.google.com/mail/u/0/
b. View received email.
c. Click "..." button & Click "Show Original" button.
d. Show "Content-Disposition" header.

multipart/mixed part example:

--680dadae3c58fb4a03d272dc4f68fb901d89541fca9e98e50a5bd97a5d52
Content-Disposition: attachment; filename="test.sh"; dummy="a.txt"
Content-Transfer-Encoding: base64
Content-Type: text/plain; charset=utf-8; name="test.sh"; dummy="a.txt"


--680dadae3c58fb4a03d272dc4f68fb901d89541fca9e98e50a5bd97a5d52--

Except Content-Disposition:

Content-Type: text/plain; charset=utf-8; name="test.sh\"; dummy=\"a.txt"

or

Content-Type: text/plain; charset=utf-8; name="test.sh%22; dummy=%22a.txt"

Actual Content-Disposition:

Content-Type: text/plain; charset=utf-8; name="test.sh"; dummy="a.txt"

Reference:

Relate RFC:
https://datatracker.ietf.org/doc/html/rfc2231
https://datatracker.ietf.org/doc/html/rfc2616
https://datatracker.ietf.org/doc/html/rfc5987
https://datatracker.ietf.org/doc/html/rfc6266
https://datatracker.ietf.org/doc/html/rfc7578
https://datatracker.ietf.org/doc/html/rfc8187

WhatWG HTML Spec > multipart/form-data escape requirements:
https://html.spec.whatwg.org/#multipart-form-data

Golang Impliments:
https://github.com/golang/go/blob/561a5079057e3a660ab638e1ba957a96c4ff3fd1/src/mime/multipart/writer.go#L132-L136

Symphony (PHP Webframework) Impliments:
https://github.com/symfony/symfony/blob/123b1651c4a7e219ba59074441badfac65525efe/src/Symfony/Component/Mime/Header/ParameterizedHeader.php#L128-L133

Spring (Java Webframework) Impliments:
https://github.com/spring-projects/spring-framework/blob/4cc91e46b210b4e4e7ed182f93994511391b54ed/spring-web/src/main/java/org/springframework/http/ContentDisposition.java#L259-L267

Similar problem with another HTTP Client:

httparty: GHSA-5pq7-52mg-hr42
Playframework WS: playframework/playframework#11571
Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=1556711
OWASP ASVS v5 > Content-Disposition escape disscussion:
OWASP/ASVS#1390

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant