Write-Up: Digital Safety Annex — DSA Exploitation
Author: 0xtor4sec | Date: Apr 16, 2025 | Read time: 4 min
Hi everyone,
This challenge presents a seemingly secure application — “Digital Safety Annex” — where users can store messages, sign them, and later verify or download them.
The system is built around the Digital Signature Algorithm (DSA) to ensure message authenticity and protect user data. Usually, I look for web vulnerabilities or bad authentication logic. But this time, I’m facing something different: a mathematical challenge. If I want to retrieve the flag, I need to understand how DSA works — and more importantly, whether the way it’s implemented here leaves anything exploitable.
📘 Preface: Understanding DSA — Theory Before Action
Before diving into the code, I realize I’m unfamiliar with the cryptographic foundations of the application: the Digital Signature Algorithm (DSA). I take a moment to explore how it works and why it’s considered secure. Here’s what I learn:
- DSA (Digital Signature Algorithm) is a FIPS-standard cryptographic scheme introduced by the NSA and adopted by NIST. It’s commonly used to ensure message authenticity and integrity.
- DSA involves three core steps: key generation, signing, and verification.
- The security of DSA hinges on the unpredictability and uniqueness of the nonce
k
.
Notation:
p, q, g
— public parametersx
— private keyy
— public key: y = g^x mod pk
— secret noncer, s
— DSA signatureh
— hash of message
🔍 Step 1: Exploring the Interface and Locating the Flag
I start by reviewing server.py
. It simulates a secure annex where users can store and verify signed messages. One line stands out:
annex.sign("ElGamalSux", FLAG, HTB_PASSWD)
This tells me the flag is stored under the user “ElGamalSux.”
Exploring the menu, I discover that option [4] reveals public DSA parameters and signed logs after admin authentication using the hardcoded password 5up3r_53cur3_P45sw0r6
.
🚨 Step 2: Vulnerability Discovery
In _dsa.py
, I find the nonce generation:
k = random.randint(self.k_min, k_max)
With k_min = 65500
and a user-based k_max
, brute-force becomes feasible.
Using:
x = (s * k - int(h, 16)) * pow(r, -1, q) % q
I can recover the private key once k
is known.
🛠 Step 3: Exploiting the Vulnerabilities
Instead of manual input through the menu, I automate the brute-force with:
def brute_force_k(p, q, g, r_target, k_min=65500, k_max=1000000):
for k in range(k_min, k_max+1):
if pow(g, k, p) % q == r_target:
return k
return None
Once I get k
, I compute x
and submit it to the server to retrieve the signed message.
🏁 Step 4: Retrieving and Validating the Flag
With recovered x
and known k
, I pass verification and access the flag.
🧵 Summary of Identified Vulnerabilities
- Predictable Nonce Generation
- Developer Leak: admin credentials and logs
🧠 Conclusion
This challenge highlights how even with full code access, the real breakthrough comes from understanding how DSA fails with weak randomness. The math was the key.
“Don't listen to the person who has all the answers. Listen to the person who has the questions.”