Debian OpenSSL
This is a compilation of my notes on this matter
Links
- http://www.debian.org/security/2008/dsa-1576
- http://www.debian.org/security/key-rollover/
- http://metasploit.com/users/hdm/tools/debian-openssl/
- http://www.milw0rm.com/exploits/5622
- http://www.yobi.be/files/blacklist.RSA-1024 32-bit Intel platform
misc
OpenSSH
Blacklists
- Current official blacklists cover RSA-2048 and DSA-1024 keys as generated on 32-bit little-endian, 64-bit little-endian and 32-bit big-endian systems
- Version Including 4096bit RSA fingerprints: http://love.hole.fi/atte/openssh-blacklist/openssh-blacklist_0.1.2_all.deb
- debian_ssh_scan_v3.tar.bz2 now including DSA 1024, RSA 2048 and RSA 4096 bit keys. Check tool dusplays also PID so very easy to generate the corresponding key.
- http://www.red-bean.com/~maxb/ contains also RSA-1024 and RSA-1023 but not all archs, to be converted:
cat $1 | sed 's/^............//' | sort > blacklist.$(echo $1|cut -c 1-8|tr a-z A-Z)
DELETE ALL DSA KEYS
Ok there are nice DSA blacklists but actually if a good DSA key was used on a bad machine, the key is compromised! (thanks Roland)
Check
Etch version gives you openssh-blacklist package and ssh-vulnkey in openssh-client
This Etch version has a sshd which checks all client connections against the blacklist so even if the keys are still in authorized_keys you should be safe
On Lenny/Sid, you can extract the Etch /etc/ssh/blacklist* and /usr/bin/ssh-vulnkey and use them
To checks all my vservers I did this [{{#file: ssh-myvuln.sh}} little script]:
#!/bin/bash
function filter () {
sed 's/\(Not blacklisted: \)/\1 /;
s/\(COMPROMISED: \)/\1 /;
s/222$/.broken/;
'
}
function scan () {
#echo $1
ssh-vulnkey $1 | filter
}
function checkpath () {
mypath="$1"
echo "===== server keys at $mypath ====="
for i in $(ls ${mypath}etc/ssh/*_key 2>/dev/null); do scan $i; done
echo "===== discarded broken server keys at $mypath ====="
for i in $(ls ${mypath}etc/ssh/*_key.pub.broken 2>/dev/null); do cp $i ${i%%.broken}222; scan ${i%%.broken}222; rm ${i%%.broken}222; done
echo "===== client keys at $mypath ====="
for i in $(ls ${mypath}root/.ssh/id* 2>/dev/null); do scan $i; done
for i in $(ls ${mypath}home/*/.ssh/id* 2>/dev/null); do scan $i; done
for v in $(ls ${mypath}etc/passwd 2>/dev/null); do
for u in $(cat $v|awk -F: '{print $6}'|sort|uniq|egrep -v "^(/root|/home/[a-z0-9]*)$"|sed 's#^/##'); do
for i in $(ls ${v%%etc/passwd}${u}/.ssh/id* 2>/dev/null); do scan $i; done
done
done
echo "===== authorized external client keys at $mypath ====="
for i in $(ls ${mypath}root/.ssh/*_keys* 2>/dev/null); do scan $i; done
for i in $(ls ${mypath}home/*/.ssh/*_keys* 2>/dev/null); do scan $i; done
for i in $(ls ${mypath}var/lib/backuppc/.ssh/*_keys* 2>/dev/null); do scan $i; done
echo "===== known external server keys at $mypath ====="
for i in $(ls ${mypath}etc/ssh/known_hosts 2>/dev/null); do scan $i; done
for i in $(ls ${mypath}root/.ssh/known_hosts 2>/dev/null); do scan $i; done
for i in $(ls ${mypath}home/*/.ssh/known_hosts 2>/dev/null); do scan $i; done
for i in $(ls ${mypath}var/lib/backuppc/.ssh/known_hosts 2>/dev/null); do scan $i; done
}
checkpath "/"
checkpath "/home/vservers/*/"
To get a resume sortable on the fingerprint:
ssh-myvuln.sh |grep ":..:..:"|sed 's/\(.\).* \(..:..:..:..:..:..:..:..:..:..:..:..:..:..:..:..\) \(.*\)/\2 \1 hostname:\3/'|sort > mykeys
To get a list to check against a blacklist:
cat mykeys |cut -c 19,20,22,23,25,26,28,29,31,32,34,35,37,38,40,41,43,44,46,47|sort|uniq > myfing cat myfing blacklist | sort | uniq -d
Scan
[{{#file: dowkd.pl.patch}} Patch] against dowkd.pl for fetching keys from /etc/ssh/blacklist*
--- dowkd.pl 2008-05-16 16:00:53.000000000 +0200
+++ dowkd.pl 2008-05-16 15:55:23.000000000 +0200
@@ -47,19 +47,29 @@
my $db;
my %db;
+my $blacklistdir='/etc/ssh';
+
sub create_db () {
$db = tie %db, 'DB_File', $db_file, O_RDWR | O_CREAT, 0777, $DB_BTREE
or die "error: could not open database: $!\n";
$db{''} = $db_version;
- while (my $line = <DATA>) {
- next if $line =~ /^\**$/;
- chomp $line;
- $line =~ /^[0-9a-f]{32}$/ or die "error: invalid data line";
- $line =~ s/(..)/chr(hex($1))/ge;
- $db{$line} = '';
+ opendir(my $dh, $blacklistdir) or die "Cannot open dir $blacklistdir : $!\n";
+ while(my $e=readdir($dh)) {
+ next if $e =~ m/^\.\.?$/;
+ next unless $e =~ m/^blacklist/;
+print STDERR "Reading $e\n";
+ open (my $fh, $blacklistdir.'/'.$e) or die "Cannot open file $e : $!\n";
+ while (my $line = <$fh>) {
+ next if $line =~ /^\**$/;
+ next if $line =~ /^#/;
+ chomp $line;
+ $line =~ /^[0-9a-f]{20}$/ or die "error: invalid data line";
+#print STDERR "Line= $line\n";
+ $line =~ s/(..)/chr(hex($1))/ge;
+ $db{$line} = '';
+ }
}
-
$db->sync;
}
@@ -106,6 +116,7 @@
sub check_hash ($$) {
my ($name, $hash) = @_;
++$keys_found;
+ $hash =~ s/^......//;
if (exists $db{$hash}) {
++$keys_vulnerable;
print "$name: weak key\n";
for ip in $(netenum 85.17.183.154/27); do ./dowkd.pl host $ip; done
Netenum is part of irpas
Renew server keys
mv /etc/ssh/ssh_host_dsa_key /etc/ssh/ssh_host_dsa_key.broken
mv /etc/ssh/ssh_host_dsa_key.pub /etc/ssh/ssh_host_dsa_key.pub.broken
mv /etc/ssh/ssh_host_rsa_key /etc/ssh/ssh_host_rsa_key.broken
mv /etc/ssh/ssh_host_rsa_key.pub /etc/ssh/ssh_host_rsa_key.pub.broken
dpkg-reconfigure openssh-server
ssh-keygen -l -f /etc/ssh/ssh_host_dsa_key
ssh-keygen -l -f /etc/ssh/ssh_host_rsa_key
If only the DSA keys must be regenerated (ALL DSA MUST BE REGENERATED!):
mv /etc/ssh/ssh_host_dsa_key.pub /etc/ssh/ssh_host_dsa_key.pub.unsafe
mv /etc/ssh/ssh_host_dsa_key /etc/ssh/ssh_host_dsa_key.unsafe
ssh-keygen -f /etc/ssh/ssh_host_dsa_key -t dsa -N ''
Generate vuln keys & blacklists
To generate yourself the vulnerable key set:
wget http://sugar.metasploit.com/ubunturoot.tar.bz2 wget http://metasploit.com/users/hdm/tools/debian-openssl/dokeygen.sh
Put dokeygen.sh in the root of the ubuntu filesystem Example for RSA 1024 (but RSA keys were upgraded by default to 2048 since Sept 2005)
sudo chroot ubunturoot
for ((i=1;i<32768;i++)); do
echo $i;
/dokeygen.sh $i -t rsa -b 1024 -f /tmp/rsa_1024_$i;
done
Ideally keys & blacklists must be generated on 32 & 64-bit platforms, little & big endian
Then to extract the fingerprints to make the blacklist
for ((i=1;i<32768;i++)); do
if [ -e rsa_1024_$i ]; then
echo $i;
f=$(ssh-keygen -l -f rsa_1024_$i|sed 's/1024 \([0-9a-f:]\+\) rsa.*/\1/;s/://g')
mv rsa_1024_$i $f-$i
mv rsa_1024_$i.pub $f-$i.pub
echo $f |sed 's/^............//'>> blacklist.RSA-1024
fi
done
Exploiting a vuln key:
Example of a fingerprint found in an authorized_keys file:
ssh -i 491f487168134647f111a882c8a04059-21223 bibi@hostname bibi@hostname:~$
OpenSSL
Check
wget https://launchpad.net/ubuntu/hardy/+source/openssl-blacklist/0.1-0ubuntu0.8.04.2/+files/openssl-blacklist_0.1-0ubuntu0.8.04.2.tar.gz tar xzf openssl-blacklist_0.1-0ubuntu0.8.04.2.tar.gz cd openssl-blacklist-0.1 Edit debian/control and cleans the dependence on openssl for Ubuntu fakeroot debian/rules binary cd .. sudo dpkg -i openssl-blacklist_0.1-0ubuntu0.8.04.2_all.deb
Now you have openssl-vulnkey tool
But this works only on private key files.
Here is a [{{#file: openssl-vulnkey.patch}} patch] to get it working against public certificate files (so you can check certifs of your favorite https sites, e.g. by exporting the certifs with FF)
--- openssl-vulnkey 2008-05-16 19:56:51.000000000 +0200
+++ openssl-vulncert 2008-05-16 19:56:36.000000000 +0200
@@ -55,7 +55,7 @@
def get_bits(file):
'''Find bit length of file'''
- rc, report = cmd(['openssl', 'rsa', '-text', '-in', file])
+ rc, report = cmd(['openssl', 'x509', '-text', '-in', file])
if rc != 0:
try:
print >> sys.stderr, "ERROR:\n%s" % (report)
@@ -64,16 +64,16 @@
return ""
for line in report:
- if "Private-Key: (1024" in report:
+ if "Public Key: (1024" in report:
return "1024"
- elif "Private-Key: (2048" in report:
+ elif "Public Key: (2048" in report:
return "2048"
return ""
def get_modulus(file):
'''Find modulus of file'''
- rc, report = cmd(['openssl', 'rsa', '-noout', '-modulus', '-in', file])
+ rc, report = cmd(['openssl', 'x509', '-noout', '-modulus', '-in', file])
if rc != 0:
try:
print >> sys.stderr, "ERROR: %d:\n%s" % (rc, report)
Actually to get their version of a fingerprint out of a public certificate, you can simply do
openssl x509 -noout -modulus -in mycertificate.crt.pem |sha1sum
Scan
Next step is to get the certificates chain from the distant website
Here is [{{#file: fetch-https-1cert.c}} fetch-https-1cert.c] to do the job
/**
* Fetch an HTTPS page and display the certificate chain.
Sources:
http://curl.haxx.se/mail/lib-2005-06/0106.html
http://openvpn.net/archive/openvpn-devel/2005-12/msg00000.html
http://www.mail-archive.com/debian-bugs-closed@lists.debian.org/msg173845.html
*/
#include <assert.h>
#include <stdio.h>
#include <curl/curl.h>
#include <openssl/ssl.h>
char error_buffer[CURL_ERROR_SIZE] = "";
CURLcode go(CURL *curl, char *url);
CURLcode sslctxfun(CURL *curl, SSL_CTX *sslctx, void *parm);
int verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx);
void print_certificate(X509 *cert);
/* arrays for certificate chain and errors */
#define MAX_CERTS 20
X509 *certificate[MAX_CERTS];
long certificate_error[MAX_CERTS];
char site[256];
int count=0;
int main(int argc, char *argv[])
{
unsigned int i;
CURL *curl;
CURLcode code;
assert(argc == 2);
for (i = 0; i != MAX_CERTS; i++) {
certificate[i] = 0;
certificate_error[i] = X509_V_OK;
}
curl = curl_easy_init();
assert(curl);
strncpy(site, argv[1], 256);
site[255]=0;
code = go(curl, argv[1]);
if (code != CURLE_OK)
fprintf(stderr, "Error %u: %s\n%s\n",
code,
curl_easy_strerror(code),
error_buffer);
curl_easy_cleanup(curl);
return 0;
}
CURLcode go(CURL *curl, char *url)
{
CURLcode code;
FILE* devnull = NULL;
devnull = fopen("/dev/null", "w+");
code = curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error_buffer);
if (code != CURLE_OK)
return code;
code = curl_easy_setopt(curl, CURLOPT_URL, url);
if (code != CURLE_OK)
return code;
// code = curl_easy_setopt(curl, CURLOPT_WRITEHEADER, stdout);
// if (code != CURLE_OK)
// return code;
code = curl_easy_setopt(curl, CURLOPT_WRITEDATA, devnull);
if (code != CURLE_OK)
return code;
code = curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5);
if (code != CURLE_OK)
return code;
/* fetch the page even if verifying the certificates fails */
code = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
if (code != CURLE_OK)
return code;
code = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
if (code != CURLE_OK)
return code;
code = curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, sslctxfun);
if (code != CURLE_OK)
return code;
code = curl_easy_perform(curl);
fclose(devnull);
return code;
}
CURLcode sslctxfun(CURL *curl, SSL_CTX *sslctx, void *parm)
{
SSL_CTX_set_verify(sslctx, SSL_VERIFY_PEER, verify_callback);
return CURLE_OK;
}
static void
extract_x509_field_ssl (X509_NAME *x509, const char *field_name, char *out, int size)
{
int lastpos = -1;
int tmp = -1;
X509_NAME_ENTRY *x509ne = 0;
ASN1_STRING *asn1 = 0;
unsigned char *buf = 0;
int nid = OBJ_txt2nid(field_name);
*out = '\0';
do {
lastpos = tmp;
tmp = X509_NAME_get_index_by_NID(x509, nid, lastpos);
} while (tmp > 0);
/* Nothing found */
if (lastpos == -1)
return;
x509ne = X509_NAME_get_entry(x509, lastpos);
if (!x509ne)
return;
asn1 = X509_NAME_ENTRY_get_data(x509ne);
if (!asn1)
return;
tmp = ASN1_STRING_to_UTF8(&buf, asn1);
if (tmp <= 0)
return;
strncpy(out, buf, size);
OPENSSL_free(buf);
}
int verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx)
{
X509 *cert = X509_STORE_CTX_get_current_cert(x509_ctx);
int depth = X509_STORE_CTX_get_error_depth(x509_ctx);
int err = X509_STORE_CTX_get_error(x509_ctx);
FILE *peercert_file;
char s[256];
char ii[256];
/* save the certificate by incrementing the reference count and
* keeping a pointer */
if (depth < MAX_CERTS && !certificate[depth]) {
certificate[depth] = cert;
certificate_error[depth] = err;
cert->references++;
}
extract_x509_field_ssl (X509_get_subject_name(cert), "CN", s, 256);
/* write peer-cert in tmp-file */
if (s[0]==0)
{
strcpy(s, site+8);
}
sprintf(ii, "%d", count++);
strcat(s, ii);
printf("Writing cert in: %s\n", s);
peercert_file = fopen(s, "w+");
if(!peercert_file)
{
printf("Failed to open file : %s", s);
return 1;
}
if(PEM_write_X509(peercert_file,cert)<0)
{
printf("Failed to write peer certificate in PEM format");
fclose(peercert_file);
return 1;
}
fclose(peercert_file);
return 1;
}
So now empowered with Google we can do sth like this:
#!/bin/bash
REQ=$1
REQ=${REQ:-site:be}
start=0
for ((i=$start;i<10;i++)); do
echo "Fetching 100 results starting from ${i}00"
for site in $(wget -q -O - --header "User-Agent: Mozilla/5.0" "http://www.google.fr/search?q=inurl%3Ahttps+$REQ&start=${i}00&num=100" |\
egrep -o "https://[a-z0-9.-]+"|\
sort|\
uniq); do
./fetch-https-1cert $site |\
cut -c 18- |\
xargs -d '\n' -l1 -r ./openssl-vulncert | sed "s#\$# (from $site)#"
done
done
./scanhttps |grep COMPROMISED|uniq -w 53
COMPROMISED: 942c267f69f53567053ad77f980f2d8980270759 leclea.be1 (from https://leclea.be) COMPROMISED: 9c5526d7d2d152e8ac437a669741a6ed6bed78a9 www.phpcompta.be0 (from https://phpcompta.be) COMPROMISED: 97c7fdf8136f5c47d3aa2b9667dd252f6af7ebf6 phpcompta.be2 (from https://phpcompta.be) COMPROMISED: fb62587b6fd13b6b7de54fcf10de77f586ac3362 Dr. Robert Necesseter0 (from https://tirna.nog.be) COMPROMISED: a7ae2ec301f7b7b844371bc76c20ee0747c32f15 www.pptickets.be4 (from https://www.pptickets.be) COMPROMISED: bbc4679d1ddccb716593c069708ad2cb21115a47 a248.e.akamai.net1 (from https://www.nokia.be) COMPROMISED: 4da0544ef1cf3cac7e166592e0900d6be2a92fae www.smartsalto.be2 (from https://www.smartsalto.be) COMPROMISED: 1b7da681c247784a6b9e1a1b2c15dcd091b5aa75 securehomes.esat.kuleuven.be2 (from https://securehomes.esat.kuleuven.be)
OpenVPN
It's not about the SSL keys, those can be checked with openssl-vulnkey.
It's about the shared static keys (openvpn -genkey)
wget https://launchpad.net/ubuntu/hardy/+source/openvpn-blacklist/0.1-0ubuntu0.8.04.1/+files/openvpn-blacklist_0.1-0ubuntu0.8.04.1.tar.gz tar xzf openvpn-blacklist_0.1-0ubuntu0.8.04.1.tar.gz cd openvpn-blacklist-0.1 fakeroot debian/rules binary cd .. sudo dpkg -i openvpn-blacklist_0.1-0ubuntu0.8.04.1_all.deb
Now you have openvpn-vulnkey tool
Others
- encfs
- My key is older, ouf!
- pwsafe, depends also on libssl0.9.8, to be assessed!!
Testing other applications using OpenSSL
We saw that there was a chroot environment for ssh-keygen but putting all applications you want to test in the chroot + all the dependencies can be painful so I did a script to use the ubuntu libraries but from the host, without need to chroot.
wget http://sugar.metasploit.com/ubunturoot.tar.bz2
Actually you don't need all the filesystem, but just:
./usr/lib/libcrypto.so.0.9.8 ./usr/lib/libgnutls-openssl.so.13.0.6 ./usr/lib/libgnutls-openssl.so.11.1.16 ./usr/lib/libssl.so ./usr/lib/libcrypto.so ./usr/lib/libcrypto.so.0.9.7 ./usr/lib/libk5crypto.so.3 ./usr/lib/libk5crypto.so ./usr/lib/libgnutls-openssl.so.11 ./usr/lib/libk5crypto.so.3.0 ./usr/lib/libgnutls-openssl.so.13 ./usr/lib/libgnutls-openssl.so.12 ./usr/lib/libssl.so.0.9.7 ./usr/lib/libgnutls-openssl.so.12.3.8 ./usr/lib/libssl.so.0.9.8 ./getpid.so
Now my script [{{#file: execwithpid.sh}} execwithpid.sh]:
#!/bin/bash
MAGICPID=$1
shift
LD_PRELOAD=./getpid.so
LD_LIBRARY_PATH=./usr/lib/
export MAGICPID
export LD_PRELOAD
export LD_LIBRARY_PATH
$@
Example: (no chroot!)
$ ./execwithpid.sh 1 ssh-keygen -f test Generating public/private rsa key pair. Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in test. Your public key has been saved in test.pub. The key fingerprint is: 65:23:e0:30:76:cb:9c:f6:a9:72:64:1c:f0:4d:90:b9 phil@mercure
$ ssh-vulnkey test COMPROMISED: 2048 65:23:e0:30:76:cb:9c:f6:a9:72:64:1c:f0:4d:90:b9 test.pub
Bingo :-)
$ ./execwithpid.sh 100 openssl genrsa -out test 1024 Generating RSA private key, 1024 bit long modulus ...............................++++++ ..........................................++++++ e is 65537 (0x10001)
$ openssl-vulnkey test COMPROMISED: 7a8981b3333b29be27b03867d253bb3680957e1f test
Bingo :-)
pwsafe
Small subtility: pwsafe can be +s (for using safe RAM) but suid executables don't take injected libraries so we've to remove the suid bit to inject getpid.so
$ sudo chmod -s /usr/bin/pwsafe $ ./execwithpid.sh 1 pwsafe -f test --createdb no passphrase $ ./execwithpid.sh 1 pwsafe -f test2 --createdb no passphrase $ md5sum test* 23421a4fa62c127ad6c08a43afa8f4af test 23421a4fa62c127ad6c08a43afa8f4af test2
First issue: the random seed of the DBs is now the same!
Hopefully apparently the passphrase is not there to decrypt the random seed but to be mixed with the clear random seed so the only practical attack against a pwsafe DB is e.g. to construct a generic rainbow with the 32000 possible random seeds and your favorite character class. Once done it could attack any pwsafe DB generated with a broken OpenSSL.
$ ./execwithpid.sh 1 pwsafe -f test2 -a test ... Generate random password? [y] Use bFewpf^uIIZ?fS@u&PkF\>Hb5-QFXJKTxMyx $ ./execwithpid.sh 1 pwsafe -f test2 -a test ... Generate random password? [y] Use bFewpf^uIIZ?fS@u&PkF\>Hb5-QFXJKTxMyx
Second issue: all nice passwords proposed by pwsafe when creating a new entry are now broken!
So are all the accounts you created around based on those pwds
No matter the DB/user/group/... you are using
Of course no matter how decently you seeded the RANDFILE ~/.rnd (cf man)
Status
- zeus
- hera
- themis
- olympe
- mercure
- venus