Hack The Box: Cybersecurity Training
Popular Topics
  • JOIN NOW
ALL Red Teaming Blue Teaming Cyber Teams Education CISO Diaries Customer Stories Write-Ups CVE Explained News Career Stories Humans of HTB Attack Anatomy Artificial Intelligence

Write-Ups

10 min read

Business CTF 2022: Bleichenbacher's '06 RSA signature forgery - BBGun06

This blog post will cover the creator's perspective, challenge motives, and the write-up of the crypto challenge BBGun06 from 2022's Business CTF.

WizardAlfredo avatar

WizardAlfredo,
Nov 25
2022

Hack The Box Article

In this blog post, we'll discuss the solution to the easy difficulty crypto challenge BBGun06, which requires exploiting a deprecated RSA signature verification code using CVE-2006-4339.

Description ๐Ÿ“„

We have received reports from CloudCompany that resources are involved in malicious activity similar to attempting unauthorised access to remote hosts on the Internet. We have since shut down the server and locked the SA. While we were trying to investigate what the entry point was, we discovered a phishing email from CloudCompany's IT department. You've since notified the vendor, and they've provided the source code of the email signing server for a security assessment. We've identified an outdated RSA verification code implementation, which we believe could be the cause of why the threat actors were able to impersonate the vendor. Can you replicate the attack and notify them of any possible misuse?

๐ŸŽฎ PLAY THE TRACK

The idea ๐Ÿ’ก

A digital signature is a type of electronic signature where a mathematical algorithm is routinely used to validate the authenticity and integrity of a message (such as an email, credit card transaction, or digital document). There are many popular external libraries used by developers to implement such security features in their applications, and I thought it would be interesting to investigate them. As I was reading up on new bugs in such libraries, I came across this

Loading Preview...

recent blog post from MEGA describing an attack called GaP-Bleichenbacher. This piqued my interest and I started looking at the PKCS#1 v1.5 padding. Shortly thereafter, I found this

Loading Preview...

article about the Bleichenbacher '06 signature forgery, which indicated that the python-rsa library was vulnerable

Loading Preview...

until 2016. As I was thinking about how to incorporate the vulnerability into a challenge, it occurred to me to create a scenario in which the APT group was able to forge a signature and send a phishing email posing as a cloud company's IT department. While researching how email signatures work, I came across this

Loading Preview...

blog and tried to implement the logic. The challenger's job would be to perform a security audit of the source code and replicate the attack.

The application at-a-glance ๐Ÿ”

When we try to connect to the tcp server with something like nc, an email appears with 2 hearers. 

โ”โ”[~]
โ”—โ”โ” โ–  nc 0.0.0.0 1337
signature: 7685086f956ed78dacd1254a2b8f556ef3da0b7e382fcc27e59bf9cae46171a3f3f61052802f3fb87d210bd582d4f181a511a6bd62009198f7701e7a837ddd9784f0fd5d2f97153d64e92e099e693bec76a3a3ab9da58596aa74b897fdbe2856654628d6ae2bad744a8aa085f71afaf2a55bf6e0d739e4772b0874d60a98184f75651273780b135fbacf7c0ce3d7cbfa88e2942263caa5f4b0501bf70bb91338e375084ac399b157afe942984f759a5283f9d0a0d3bd32899baf4dbf0eece1de0fe4c0cc10212309ed0b77f1f7b9340e5d1090db1408564a8f8d622b1a498023db34bfe4e69598b3db551bde74b01ae32b38c2cdd4115d1def554af755634232
certificate:
-----BEGIN PUBLIC KEY-----
MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKCAQEAphfp+E9AeBWTLQs066Yq
I8uDFbwU3Tj3lnea4FjU7SF6+PEa9qeYvUqFRY57gtRm1kN6LG9XMPZHUOo8qqSq
rMVG715qMWYjUDu6zCBPV3c6WUDik1mNOg4kbO8r3OEb/vrP3lixV0icQM69rEgH
nQPIENnh6fGDM9oXEZy7xl4IKDhoFwp6V33KW0b+HQzxAz1Qe0sty7rZAnHZUvop
H07+KRgTqt2KOrf2HYF9qZHX1ybBB4qrOKQIn8IAs0ErRIGjjCqaDwYUwnz59sT8
qtgIJQoxOifIvLj0oX5aNdtpr2dfddk4RVttSgkYUb15VQlvXCWW60uOnMHA/ue+
4QIBAw==
-----END PUBLIC KEY-----
From: IT Department <it@cloudcompany.com>
To: j.smith@sheesh.gov.edu
Subject: Confirm your identity
...
 

The server asks us to enter a signature in hex. We can try to enter a random signature. A message appears that an error has occurred.

 
Enter the signature as hex: aaaa

An error occurred

At this point, we need to start looking at the source code to understand how things work.

Analyzing the source code ๐Ÿ“–

There are 4 files available: server.py, encyrption.py, email.txt, logo.png. The email and logo are only used for the story, so we are going to focus on the other 2.

server.py

Looking at the server.py script, we see that the basic workflow is as follows:

  1. An RSA object is created.

  2. The phishing email is parsed.

  3. The email headers are generated and added to the email.

  4. The server asks for a valid signature. If the signature passes the tests, we get the flag.

This is translated into code:

   rsa = RSA(2048)

   user, data = parseEmail() 

   signature = rsa.sign(user)
   rsa.verify(user, signature)

   headers = generateHeaders(rsa, signature)

   valid_email = headers + data 
   sendMessage(s, valid_email + "\n\n")

   try: 
      forged_signature = receiveMessage(s, "Enter the signature as hex: ") 
      forged_signature = bytes.fromhex(forged_signature) 
   if not rsa.verify(user, forged_signature): 
      sendMessage(s, "Invalid signature") 
   if different(rsa, signature, forged_signature): 
      sendMessage(s, FLAG) 
   except: 
      sendMessage(s, "An error occurred") 
 

The objective of the challenge is pretty clear. Our goal is to find a way to forge a signature. To do that, we are going to focus on step 4.

encryption.py

Let's check out how the verification process works. If we look at the encryption.py script, we see that after decrypting the signature, the code uses a regex to check if the padding is correct.

   def verify(self, message, signature):
      keylength = len(long_to_bytes(self.n))
      decrypted = self.encrypt(signature)
      clearsig = decrypted.to_bytes(keylength, "big")

      r = re.compile(b'\x00\x01\xff+?\x00(.{15})(.{20})', re.DOTALL)
      m = r.match(clearsig)

      if not m:
         raise VerificationError('Verification failed')

      if m.group(1) != self.asn1:
         raise VerificationError('Verification failed')

      if m.group(2) != sha1(message).digest():
         raise VerificationError('Verification failed')

      return True
 

For the signature to be valid, the decrypted version must start with \x00\x01, continue with 1 or more \xff bytes, a \x00 byte, and finally the ANSI blob and the SHA1 hash of the signed message.

When testing how the regex works, we observe that after we craft a payload that passes the above checks and append random data, the verification process will succeed. An example of a payload would be:

00 01 FF FF ... FF FF 00 ASN HASH GARBAGE
 

The GARBAGE part will be crucial during the exploitation phase of this verification process. Also, something important to note is the public exponent. In our case it's e = 3. 

Searching for the bugs ๐Ÿ‘พ

After analyzing the source code, it is possible to exploit the vulnerability, but a good strategy would be to search if something similar has been exploited before. If we google BBGun06 signature forgery, we will come across this

Loading Preview...

article which states that it is possible to forge a signature with a special message. I quote the article: 

All you have to do is build a message, take the cube root of that figure, and round up. 

Exploitation ๐Ÿ”“

Connecting to the server

A pretty basic script for connecting to the server with pwntools:

if __name__ == '__main__':
    r = remote('0.0.0.0', 1337)
    pwn()
 

Getting the public key

When someone connects to the server, a signature and the public key are computed. To obtain the public key, we can use:

def getPublicKey(): 
   for _ in range(2): 
   r.recvline() 

   public_key = "" 
   for _ in range(9): 
      public_key += r.recvline().decode()

   key = RSA.import_key(public_key) 
   return key 
 

Forging the signature

To create a valid signature, as mentioned above, we need to find a plaintext whose cubic root passes all tests. For this purpose, we can first create a plaintext that satisfies the regex.

00 01 FF FF ... FF FF 00 ASN HASH
 

Using python: 

   block = b'\x00\x01\xff\x00' + ASN1 + sha1(message).digest()
 

After that, we can append random bytes so that the cubic root of the output does not change the important parts. 

   key_length = len(bin(n)) - 2 
   garbage = (((key_length + 7) // 8) - len(block)) * b'\xff'
 

Finally we can calculate the cubic root and send the signature to the server. The final function is: 

def forge_signature(message, n, e): 
   key_length = len(bin(n)) - 2
   block = b'\x00\x01\xff\x00' + ASN1 + sha1(message).digest()
   garbage = (((key_length + 7) // 8) - len(block)) * b'\xff'
   block += garbage

   pre_encryption = bytes_to_long(block)
   forged_sig = iroot(pre_encryption, e)[0]

   return long_to_bytes(forged_sig)
 

Remember that one of the reasons this attack works is that the exponent is very small. This gives us the ability to completely ignore the public key and the operation in .

Sending the signature

def sendForgedSignature(forged_signature): 
   forged_signature = forged_signature.hex().encode()   
   r.sendlineafter(b"Enter the signature as hex: ", forged_signature) 
 

Getting the flag

A final summary of everything said above:

  1. We obtain the public key.

  2. We forge the signature.

  3. We send the forged signature and get the flag.

This summary can be represented by code using the pwn() function.

def pwn(): 
   public_key = getPublicKey() 
   n = public_key.n 
   e = public_key.e 
   user = b"IT Department <it@cloudcompany.com>"
   forged_signature = forge_signature(user, n, e) 
   assert verify(user, forged_signature, n, e)     
   sendForgedSignature(forged_signature) r.interactive()
   r.interactive()
 

The final script is:

from Crypto.Util.number import bytes_to_long, long_to_bytes
from Crypto.PublicKey import RSA
from hashlib import sha1
from gmpy2 import iroot
from pwn import *
import re

ASN1 = b"\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14"


class CryptoError(Exception):
    """Base class for all exceptions in this module."""


class VerificationError(CryptoError):
    """Raised when verification fails."""


def getPublicKey():
    for _ in range(2):
        r.recvline()

    public_key = ""
    for _ in range(9):
        public_key += r.recvline().decode()

    key = RSA.import_key(public_key)
    return key


def forge_signature(message, n, e):
    key_length = len(bin(n)) - 2
    block = b'\x00\x01\xff\x00' + ASN1 + sha1(message).digest()
    garbage = (((key_length + 7) // 8) - len(block)) * b'\xff'
    block += garbage

    pre_encryption = bytes_to_long(block)
    forged_sig = iroot(pre_encryption, e)[0]

    return long_to_bytes(forged_sig)


def verify(message, signature, n, e):
    keylength = len(long_to_bytes(n))
    encrypted = bytes_to_long(signature)
    decrypted = pow(encrypted, e, n)
    clearsig = decrypted.to_bytes(keylength, "big")

    r = re.compile(b'\x00\x01\xff+?\x00(.{15})(.{20})', re.DOTALL)
    m = r.match(clearsig)

    if not m:
        raise VerificationError('Verification failed')

    if m.group(1) != ASN1:
        raise VerificationError('Verification failed')

    if m.group(2) != sha1(message).digest():
        raise VerificationError('Verification failed')

    return True


def sendForgedSignature(forged_signature):
    forged_signature = forged_signature.hex().encode()
    r.sendlineafter(b"Enter the signature as hex: ", forged_signature)


def pwn():
    public_key = getPublicKey()
    n = public_key.n
    e = public_key.e
    user = b"IT Department <it@cloudcompany.com>"
    forged_signature = forge_signature(user, n, e)
    assert verify(user, forged_signature, n, e)
    sendForgedSignature(forged_signature)
    r.interactive()


if __name__ == "__main__":
    r = remote('0.0.0.0', 1337)
    pwn()
 

Unintended solutions and patching ๐Ÿงฏ

Last-minute changes

A patch for the challenge was released at CTF. Let's look at the differences between BBGun06 and BBGun06 Revenge (the patched version) and try to figure out what went wrong.
BBGun:

    try:
        forged_signature = receiveMessage(s, "Enter the signature as hex: ")
          forged_signature = bytes.fromhex(forged_signature)

          if not rsa.verify(user, forged_signature):
            sendMessage(s, "Invalid signature")

          if (forged_signature != signature[len("signature: "):]):
            sendMessage(s, FLAG)
    except:
        sendMessage(s, "An error occurred")
 

Where the signature is generated using:

def generateHeaders(rsa, user):
    signature = rsa.sign(user)
    rsa.verify(user, signature)
    signature = f"signature: {signature.hex()}\n"
 

In contrast BBGun06 Revenge:

    try:
        forged_signature = receiveMessage(s, "Enter the signature as hex: ")
          forged_signature = bytes.fromhex(forged_signature)

          if not rsa.verify(user, forged_signature):
            sendMessage(s, "Invalid signature")

          signature = signature[len("signature: "):].strip()
          if (forged_signature.hex() != signature):
            sendMessage(s, FLAG)
    except:
        sendMessage(s, "An error occurred")
 

In making some last-minute changes, I overlooked the type checking on the signature verification routine. This resulted in players being able to submit existing signatures from the email headers, bypass the verification process, and retrieve the flag. Thanks to Hilbert, I was able to quickly fix the bug during the CTF, and everything ran smoothly after that. Or did they?

Maths gone wrong

Unfortunately, I found another bug after the patch. I didn't know it during the CTF, but apparently in the patched version it was possible to send the already computed signature plus n (the public exponent) and get the flag. This is true, because if we look at the following lines of the revenge challenge:

          if not rsa.verify(user, forged_signature):
            sendMessage(s, "Invalid signature")

          signature = signature[len("signature: "):].strip()
          if (forged_signature.hex() != signature):
            sendMessage(s, FLAG)
 

The new payload signature + n would pass the first if statement since it would be reduced by n, so , and it would pass the second if statement because signature + n != signature. Lesson learned: don't change the source code 1 day before the CTF. And thatโ€™s a wrap for this challenge write-up!

GET A DEMO FREE TRIAL

Contents

  • Description ๐Ÿ“„
  • The idea ๐Ÿ’ก
  • The application at-a-glance ๐Ÿ”
  • Analyzing the source code ๐Ÿ“–
    • server.py
    • encryption.py
  • Searching for the bugs ๐Ÿ‘พ
  • Exploitation ๐Ÿ”“
    • Connecting to the server
    • Getting the public key
    • Forging the signature
    • Getting the flag
  • Unintended solutions and patching ๐Ÿงฏ
    • Last-minute changes
    • Maths gone wrong

Latest News

Hack the Box Blog

Education

6 min read

Your pentest found nothing. Hereโ€™s what to do next.

HTB-Bot avatar HTB-Bot, May 15, 2025

Hack the Box Blog

News

2 min read

Hack The Box invites all corporate teams to benchmark their skills through the Global Cyber Skills Benchmark 2025

Noni avatar Noni, May 12, 2025

Hack the Box Blog

Artificial Intelligence

6 min read

AI Red Teaming explained: Adversarial simulation, testing, and capabilities

b3rt0ll0 avatar b3rt0ll0, May 09, 2025

Hack The Blog

The latest news and updates, direct from Hack The Box

Read More
Hack The Box: Cybersecurity Training

The #1 platform to build attack-ready
teams and organizations.

Get a demo

Forrester wave leader Forrester wave leader
ISO 27001 ISO 27701 ISO 9001
G2 rating Capterra rating

Products
Teams
Courses & Certifications Cyber Ranges Enterprise Attack Simulations Cloud Infrastructure Simulations Capture The Flag Tabletop Exercises Talent Sourcing
Individuals
Courses & Certifications Hacking Labs Defensive Labs Red Team Labs Capture The Flag Job Board
Solutions
Job Roles
Red Teams Blue Teams Purple Teams
Industries
Government Higher Education Finance Professional Services
Use Cases
Technical Onboarding Team Benchmarking Candidate Assessment Threat Management Code Vulnerability Crisis Simulation Governance & Compliance
Resources
Community Blog Industry Reports Webinars AMAs Learn with HTB Customer Stories Cheat Sheets Compliance Sheets Glossary Guides & Templates Parrot OS Help Center
Programs
Channel & Resellers Ambassador Program Affiliate Program SME Program
Company
About us Careers Brand Guidelines Certificate Validation Trust Center Product Updates Status
Contact Us
Press Support Enterprise Sales
Partners
Become a Partner Register a Deal
Store
HTB Swag Buy Gift Cards
Products
Teams
Courses & Certifications Cyber Ranges Enterprise Attack Simulations Cloud Infrastructure Simulations Capture The Flag Tabletop Exercises Talent Sourcing

Individuals

Courses & Certifications Hacking Labs Defensive Labs Red Team Labs Capture The Flag Job Board
Solutions
Job Roles
Red Teams Blue Teams Purple Teams

Industries

Government Higher Education Finance Professional Services

Use Cases

Technical Onboarding Team Benchmarking Candidate Assessment Threat Management Code Vulnerability Crisis Simulation Governance & Compliance
Resources
Community Blog Industry Reports Webinars AMAs Learn with HTB Customer Stories Cheat Sheets Compliance Sheets Glossary Guides & Templates Parrot OS Help Center

Programs

Channel & Resellers Ambassador Program Affiliate Program SME Program
Company
About us Careers Brand Guidelines Certificate Validation Trust Center Product Updates Status

Contact Us

Press Support Enterprise Sales

Partners

Become a Partner Register a Deal

Store

HTB Swag Buy Gift Cards
Cookie Settings
Privacy Policy
User Agreement
ยฉ 2025 Hack The Box