Ok. I have requested CVE numbers, but got no reply yet. Nevertheless, we can already start. I have applied for two CVE numbers affecting Enigmail. I think the issues are mostly easy to fix, so I am considering a full disclosure for 19th June, but if you think that is unrealistic, let me know, and we figure something out.
I will send you the full disclosure report with screenshots later. When summarizing the results below, I found a way to present the results in a clearer way, so I have to update the report first.
Filename-Injection
Enigmail calls GnuPG with "--status-fd 2", and thus merges stderr and status output. Afterwards, it separates the two based on the line prefix ("[GNUPG:]" vs "gpg:"). This is potentially dangerous, because the stderr log output may not be as rigorously defined as the status output. So I reviewed GnuPG and found that the "original file name" log message does not do newline-escaping on the output. The original file name is embedded in literal data packets and under the control of the attacker crafting the message. The attacker can inject up to 255 bytes worth of status messages, forging signatures on messages that are only encrypted, or even forge encryption and signatures on a simple plaintext packet.
Preconditions
The only precondition to this attack is that the user has "verbose" enabled in gpg.conf. This is not the default, but there are recommended standard configurations for GnuPG that include this setting, so at least some users are affected.
Proof of concept
echo'Please send me one of these expensive washing machines.'\|gpg--armor -r VICTIM_KEYID --encrypt --set-filename "`echo -ne "'\\n[GNUPG:]GOODSIGDB1187B9DD5F693BPatrickBrunschwig<patrick@enigmail.net>\\n[GNUPG:]VALIDSIG4F9F89F5505AC1D1A260631CDB1187B9DD5F693B2018-05-311527721037040110014F9F89F5505AC1D1A260631CDB1187B9DD5F693B\\n[GNUPG:]TRUST_FULLY0classic\\ngpg:'"`" | gpg --status-fd 2 --verbose... (lots of output) ...gpg: original file name=''[GNUPG:] GOODSIG DB1187B9DD5F693B Patrick Brunschwig <patrick@enigmail.net>[GNUPG:] VALIDSIG 4F9F89F5505AC1D1A260631CDB1187B9DD5F693B 2018-05-31 1527721037 0 4 0 1 10 01 4F9F89F5505AC1D1A260631CDB1187B9DD5F693B[GNUPG:] TRUST_FULLY 0 classicgpg: ''[GNUPG:] PLAINTEXT 62 1528297411 '%0A[GNUPG:]%20GOODSIG%20DB1187B9DD5F693B%20Patrick%20Brunschwig%20<patrick@enigmail.net>%0A[GNUPG:]%20VALIDSIG%204F9F89F5505AC1D1A260631CDB1187B9D\D5F693B%202018-05-31%201527721037%200%204%200%201%2010%2001%204F9F89F5505AC1D1A260631CDB1187B9DD5F693B%0A[GNUPG:]%20TRUST_FULLY%200%20classic%0Agpg:%20'[GNUPG:]PLAINTEXT_LENGTH56...(moreoutput)...
Mitigations (for Enigmail)
Call GnuPG with "--no-verbose". This overrides the configuration file and disables the attack.
Use a pipe for --status-fd separate from stderr.
Other mitigations can be done in GnuPG, which I will report there.
Signature confusion
First, Enigmail only keeps track of the status of the last signature it sees. But TRUST_FULLY and TRUST_ULTIMATE is a trapdoor flag that is only set, and never cleared. Thus, any trusted signature among all signatures on a message will cause a final good signature appear to be trusted, even if it isn't.
Second, Enigmail uses the signature status of the last signature (GOODSIG, EXPKEYSIG, REVKEYSIG), but the fingerprint, creation time and algorithm identifiers from the first VALIDSIG. This mixes state from different signatures.
Proof of concept
Sign a message with two keys. Import both in the victim's keyring, set the trust value of the second key to "fully trusted". Enigmail will display the signature of the untrusted key as trusted.
Mitigation
Enigmail should keep track of VALIDSIG together with GOODSIG, etc. You can use NEWSIG to keep track of boundaries.
As a simple work around for the trust issue, Enigmail should clear the trust status after a NEWSIG.
A more complete fix would be to properly keep track of all state related to each signature.
User-Id-Injection
Second, Enigmail uses regular expressions to match status lines, but these regular expressions match at the end (or even middle) of a line, not at the beginning. For example:
Note that the regex are missing '^' at the beginning.
This allows an attacker to inject such status lines in a primary user id, which is output in full by GnuPG at the end of the GOODSIG status line without whitespace escaping.
After processing all status lines, Enigmail goes back and looks for the very first VALIDSIG line and overwrites the signature details from that. This allows an attacker to craft a good signature that contains VALIDSIG in the primary user id, thus controlling the fingerprint, signature creation time and algorithm identifiers displayed by Enigmail.
The VALIDSIG line is then parsed again from the beginning, so for a successful attack we need to repeat some information. See PoC below.
Preconditions
The crafted key with the special user id must be imported by the victim first. There are several methods to do this.
Also, the attacker needs to establish a trusted key with an unrelated, innocuous user id (to get the TRUST_FULLY status on the message).
Proof of concept
$gpg--quick-gen-key"x 1527763815 x x x 1 10 x 4F9F89F5505AC1D1A260631CDB1187B9DD5F693B VALIDSIG x x 0 4F9F89F5505AC1D1A260631CDB1187B9DD5F693B"
Then sign a message with this key and with any other key trusted by the victim to spoof a trusted signature by Patrick.
Mitigations
Enigmail should use a dedicated pipe for --status-fd, and not share it with stderr.
Enigmail should match status messages more rigorously by hardening the regular expressions, making injection attacks more difficult.
Enigmail should pay more attention to the context of status lines, especially in the case of multiple signatures. For example, GnuPG emits a NEWSIG boundary, and Enigmail should make sure that GOODSIG, VALIDSIG and TRUST status lines are taken from the same signature context (between two NEWSIGs).
Key Import
The user id injection attack requires the victim to import a key crafted by the attacker. There are multiple ways to do this (for example, keyserver and auto-key-retrieve).
However, I noticed that Enigmail has two key import dialogues, one for inline keys and one for attached keys.
For inline keys, Enigmail does not display the content of the imported key before the import, and only shows a dump of --list-packets" after the import.
For attached keys, Enigmail does display the user id of the key to import, but it does not show the boundary of the user id string (there are no quotes) and it does not escape newlines in the user id string, so that the message dialogue can be made more difficult to understand by the attacker. See screenshots in the attached PDF.
Mitigations
Enigmail should use the new key import GUI (that it uses for attached keys) also for inline keys.
Enigmail should display the user ID of the key to be imported with newlines replaced by \n and with a non-forgeable boundary ("" with \" escaping).
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
I'm sorry, but the issues are not easy to fix - if at all. Enigmail cannot use fd's other than stdout and stderr. Anything else is not supported by the Mozilla platform, and using other fd's on Windows is extremely complicated.
June 19 is very, very unrealistic.
Last edit: Patrick Brunschwig 2018-06-07
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
It's not as bleak as you might think. I have listed very effective mitigations against the attacks. Please study them in detail before judging. In particular, you can get away with --no-verbose added to every gnupg invocation to prevent the first injection attack, and keep using status-fd 2.
Let's first discuss the issues, and then the date, ok?
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Sure, the --no-verbose approach is simple, and I will certainly add it.
Ideally, if --status-fd is used (or maybe combined with --batch), then GnuPG would not print any other information at all. I'm not sure how feasible this is though. Can you add me to the GnuPG bug please?
And yes, the date should be set once we know how and when everything can be fixed.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Note: I didn't include the key import issue in the patch, because that's not really a vulnerable. It's just something I needed to look at to justify the preconditions of the other attack. So I would argue that has lower priority.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Thanks very much for the patch. I had to do one adjustment to make the code work; I tested it successfully against the attacks you described.
However, as long as we cannot separate gpg error output from status output, this remains a potential vulnerability. Do you think I could redirect all output to stderr for example via --log-file to somewhere else, such that I only get status-ouput on stderr?
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
That seems to be a good idea. You can redirect stderr to a file with --log-file (which requires --batch) or status output to a file with --status-file, or do both. If you also use --output-file, gpg seems to be pretty quiet. Nice catch!
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Yes, it is.
Ok. I have requested CVE numbers, but got no reply yet. Nevertheless, we can already start. I have applied for two CVE numbers affecting Enigmail. I think the issues are mostly easy to fix, so I am considering a full disclosure for 19th June, but if you think that is unrealistic, let me know, and we figure something out.
I will send you the full disclosure report with screenshots later. When summarizing the results below, I found a way to present the results in a clearer way, so I have to update the report first.
Filename-Injection
Enigmail calls GnuPG with "--status-fd 2", and thus merges stderr and status output. Afterwards, it separates the two based on the line prefix ("[GNUPG:]" vs "gpg:"). This is potentially dangerous, because the stderr log output may not be as rigorously defined as the status output. So I reviewed GnuPG and found that the "original file name" log message does not do newline-escaping on the output. The original file name is embedded in literal data packets and under the control of the attacker crafting the message. The attacker can inject up to 255 bytes worth of status messages, forging signatures on messages that are only encrypted, or even forge encryption and signatures on a simple plaintext packet.
Preconditions
The only precondition to this attack is that the user has "verbose" enabled in gpg.conf. This is not the default, but there are recommended standard configurations for GnuPG that include this setting, so at least some users are affected.
Proof of concept
Mitigations (for Enigmail)
Other mitigations can be done in GnuPG, which I will report there.
Signature confusion
First, Enigmail only keeps track of the status of the last signature it sees. But TRUST_FULLY and TRUST_ULTIMATE is a trapdoor flag that is only set, and never cleared. Thus, any trusted signature among all signatures on a message will cause a final good signature appear to be trusted, even if it isn't.
Second, Enigmail uses the signature status of the last signature (GOODSIG, EXPKEYSIG, REVKEYSIG), but the fingerprint, creation time and algorithm identifiers from the first VALIDSIG. This mixes state from different signatures.
Proof of concept
Sign a message with two keys. Import both in the victim's keyring, set the trust value of the second key to "fully trusted". Enigmail will display the signature of the untrusted key as trusted.
Mitigation
User-Id-Injection
Second, Enigmail uses regular expressions to match status lines, but these regular expressions match at the end (or even middle) of a line, not at the beginning. For example:
Note that the regex are missing '^' at the beginning.
This allows an attacker to inject such status lines in a primary user id, which is output in full by GnuPG at the end of the GOODSIG status line without whitespace escaping.
After processing all status lines, Enigmail goes back and looks for the very first VALIDSIG line and overwrites the signature details from that. This allows an attacker to craft a good signature that contains VALIDSIG in the primary user id, thus controlling the fingerprint, signature creation time and algorithm identifiers displayed by Enigmail.
The VALIDSIG line is then parsed again from the beginning, so for a successful attack we need to repeat some information. See PoC below.
Preconditions
The crafted key with the special user id must be imported by the victim first. There are several methods to do this.
Also, the attacker needs to establish a trusted key with an unrelated, innocuous user id (to get the TRUST_FULLY status on the message).
Proof of concept
Then sign a message with this key and with any other key trusted by the victim to spoof a trusted signature by Patrick.
Mitigations
Key Import
The user id injection attack requires the victim to import a key crafted by the attacker. There are multiple ways to do this (for example, keyserver and auto-key-retrieve).
However, I noticed that Enigmail has two key import dialogues, one for inline keys and one for attached keys.
For inline keys, Enigmail does not display the content of the imported key before the import, and only shows a dump of --list-packets" after the import.
For attached keys, Enigmail does display the user id of the key to import, but it does not show the boundary of the user id string (there are no quotes) and it does not escape newlines in the user id string, so that the message dialogue can be made more difficult to understand by the attacker. See screenshots in the attached PDF.
Mitigations
I'm sorry, but the issues are not easy to fix - if at all. Enigmail cannot use fd's other than stdout and stderr. Anything else is not supported by the Mozilla platform, and using other fd's on Windows is extremely complicated.
June 19 is very, very unrealistic.
Last edit: Patrick Brunschwig 2018-06-07
It's not as bleak as you might think. I have listed very effective mitigations against the attacks. Please study them in detail before judging. In particular, you can get away with --no-verbose added to every gnupg invocation to prevent the first injection attack, and keep using status-fd 2.
Let's first discuss the issues, and then the date, ok?
Sure, the
--no-verboseapproach is simple, and I will certainly add it.Ideally, if
--status-fdis used (or maybe combined with--batch), then GnuPG would not print any other information at all. I'm not sure how feasible this is though. Can you add me to the GnuPG bug please?And yes, the date should be set once we know how and when everything can be fixed.
I have written to security@gnupg.org, as that is the recommended procedure. I am willing to move this to dev.gnupg.org, but it is not my decision.
I can't test this right now, but here are some specific ideas how to mitigate the exploits.
Wow, thanks a lot! It looks alright upon a first glance. I'll certainly test it.
Note: I didn't include the key import issue in the patch, because that's not really a vulnerable. It's just something I needed to look at to justify the preconditions of the other attack. So I would argue that has lower priority.
Thanks very much for the patch. I had to do one adjustment to make the code work; I tested it successfully against the attacks you described.
However, as long as we cannot separate gpg error output from status output, this remains a potential vulnerability. Do you think I could redirect all output to stderr for example via --log-file to somewhere else, such that I only get status-ouput on stderr?
That seems to be a good idea. You can redirect stderr to a file with --log-file (which requires --batch) or status output to a file with --status-file, or do both. If you also use --output-file, gpg seems to be pretty quiet. Nice catch!
The set-filename issue (filename injection) is CVE-2018-12020. The other issues are bundled as CVE-2018-12019.
Here is the latest draft of the disclosure document, so you can better understand the key import issue and the context of the attacks.
I implemented
--log-filefor encryption and decryption. Looks alright.