GnuPG does not check encrypted messages for well-formed composition
Closed, ResolvedPublic

Description

Even if allow-multiple-messages is not used, GnuPG allows multiple messages in the following case:

  • The first message is encrypted and does not have a plaintext packet.
  • The second message is a plaintext packet.

The result is a message that looks as if it were encrypted on the command line, but the plaintext is not encrypted. This is confusing and potentially hazardous.

The status output shows NODATA, unless there is at least a single packet in the encrypted data. A private packet is sufficient to avoid the NODATA line. The only other way for applications to notice this case is that the PLAINTEXT messages come after END_DECRYPTION, but common mail clients do not check this (I tested Evolution, Thunderbird, GpgOL and Mutt, but others are probably affected as well).

It is easy to avoid this problem, for example by bumping literals_seen after END_DECRYPTION even if no literal is seen. Here is the comparable change in NeoPG: https://github.com/das-labor/neopg/commit/8311895c0277423a8330e47753a6b8ae2ad4359e

More info is available at https://neopg.io/blog/encryption-spoof/

How to reproduce:

# Create a private packet that is ignored by GnuPG
echo -n -e '\xfc\x03\x50\x47\x50' > 02-private.pkt

# Create an encrypted message.
gpg --no-literal --compress-level 0 -r Nerd --encrypt < 02-private.pkt > 02-encrypted.pkt 2> /dev/null

# Create a literal data packet.
echo 'This is fine!' | gpg --store --compress-level 0 --faked-system-time 0 > 02-literal.pkt 2> /dev/null

# Store the plaintext after the encrypted packet.
cat 02-encrypted.pkt 02-literal.pkt > 02-message.gpg

cat 02-message.gpg | gpg --enarmor | sed -e "s/ARMORED FILE/MESSAGE/" | sed -e '/^Comment\:/d' > 02-pgp-inline.gpg

Output:

$ cat 02-pgp-inline.gpg | gpg
gpg: WARNING: no command supplied.  Trying to guess what you mean ...
gpg: encrypted with 2048-bit RSA key, ID 66489556790B2E8E, created 2018-03-25
      "twitter://lambdafu"
This is fine!

Status FD output:

GNUPG:] ENC_TO 66489556790B2E8E 1 0
[GNUPG:] KEY_CONSIDERED 013072FB93A232E7C9B1DB3F7EBDF89573BAFB58 0
[GNUPG:] KEY_CONSIDERED 013072FB93A232E7C9B1DB3F7EBDF89573BAFB58 0
[GNUPG:] DECRYPTION_KEY 9669A61C2F57DEC457976E7B66489556790B2E8E 013072FB93A232E7C9B1DB3F7EBDF89573BAFB58 u
[GNUPG:] KEY_CONSIDERED 013072FB93A232E7C9B1DB3F7EBDF89573BAFB58 0
[GNUPG:] BEGIN_DECRYPTION
[GNUPG:] DECRYPTION_INFO 2 1
[GNUPG:] DECRYPTION_OKAY
[GNUPG:] GOODMDC
[GNUPG:] END_DECRYPTION
[GNUPG:] PLAINTEXT 62 0 
[GNUPG:] PLAINTEXT_LENGTH 14
This is fine!

Message composition:

$ cat msg | gpg --list-packet
gpg: encrypted with 2048-bit RSA key, ID 66489556790B2E8E, created 2018-03-25
      "twitter://lambdafu"
# off=0 ctb=85 tag=1 hlen=3 plen=268
:pubkey enc packet: version 3, algo 1, keyid 66489556790B2E8E
	data: [2048 bits]
# off=271 ctb=d2 tag=18 hlen=2 plen=33 new-ctb
:encrypted data packet:
	length: 33
	mdc_method: 2
# off=306 ctb=cb tag=11 hlen=2 plen=20 new-ctb
:literal data packet:
	mode b (62), created 0, name="",
	raw data: 14 bytes
marcus created this task.May 29 2018, 1:34 PM

I would also recommend that GPGME does a sanity check on the status fd output for people with new GPGME but old GnuPG binary.

This looks similar to the "multiple plaintext" issue that we had in Feb. / March 2007.

Can you help me understand what the impact of this is? AFAIK Back in 2007 the problem was that it could be faked that data looked like it was signed.

Here I'm not sure that I see the implications. If I wanted to "fake encrypt" something, what is the advantage of your technique over encrypting it? As I see it even for your technique you would need the pubkey of the recipient.

I agree that it's probably a bug in that it is unintended behavior, but I try to understand what the priority is and if I should e.g. add safeguards in Kleo.

P.S.
Please stop using Outlook 2007 for testing ;-) Ok that would not have made a difference here but I removed the code for Outlook 2007 already in master.

The impact is low to our current understanding, that's why I didn't report it as a security vulnerability. I tried to use this for signatures, but GnuPG has more verification for signatures, so it doesn't work there as far as I can see. So that's good.
If you allow for a BADMDC, you can easily downgrade the content of an encrypted data packet from, for example, compressed to private packet type, and then you don't even need the public key, just an encrypted message. The MDC will notice this, and since Efail the clients should have strict MDC checking, so I didn't include that variation in my report.
By the way, there are other clients I didn't test which are probably affected, such as kmail, claws, gpgtools.
I only have Outlook 2007 and no funds to buy software I don't use, as I am unemployed and using up my savings. So, next time I won't be able to do the testing, sorry!

patrick changed the visibility from "Public (No Login Required)" to "Subscribers".May 30 2018, 5:58 PM

I have changed visibility of the bug, as I think you can do a lot more with this than Marcus imagined.

Correct me if I'm wrong, but I believe that if you manage to place unencrypted literal packets before and after an encrypted message, you can create a variant of the Efail attack. Even more, it would look like a single stream from GnuPG:

  • The literal packet(s) before the encrypted message contain a PGP/MIME message structure that will embed the encrypted message similarly to the Efail scenario (i.e. a 1st HTML part, for example with an open img src=".." tag
  • Then the encrypted message
  • Then another literal packet with some closing stuff to make the MIME parser happy
patrick added a subscriber: werner.May 30 2018, 6:08 PM
werner changed the visibility from "Subscribers" to "Public (No Login Required)".May 30 2018, 8:34 PM
werner triaged this task as High priority.May 30 2018, 8:48 PM
werner added a project: gpgme.

[We do things in the public unless explicitly requested by a bug reporter writing to security.]

That whole thing is old and a common thing when decrypting mails. For example many MUAs decrypt any inline PGP parts they see (common annoyance then are samples in mail to a public ML). So nothing new and no need to hide it.

Th actual problem here is that GPGME does not behave correctly. This needs to be fixed.

BTW: The misconception is to trust non-signed data. It can origin from anyone. This is why MUAs explicitly mark the signed part of a message or hide everything else.

werner edited projects, added gnupg (gpg22); removed gpgme.May 30 2018, 9:51 PM

I need to revise my statement (partly because fixing gpgme would be quite complicated). Marcus is right in that using the the literals_seen counter is the straightforward way to get this right. And it will fix it also for non-GPGME applications.

werner closed this task as Resolved.May 31 2018, 1:14 PM
werner claimed this task.
marcus reopened this task as Open.Jun 2 2018, 12:53 PM

Yeah, that's not good enough. You also need to check if literals_seen is 0 before BEGIN_DECRYPTION to catch the case where the plaintext packet comes before the encrypted packet. See https://github.com/das-labor/neopg/commit/30623bcd436a35125f21fe6f29272a5fa7212d3f

werner changed the task status from Open to Testing.Jun 6 2018, 3:59 PM

Better?

werner closed this task as Resolved.Jun 8 2018, 11:16 AM