DEV Community

CAISD
CAISD

Posted on

Your Password is Already Cracked. Here's Why — OWASP A02 Deep Dive


I cracked 240,000 passwords in 4 minutes.

Not with some exotic zero-day. Not with nation-state tooling.
With a consumer GPU, a wordlist, and one command:

hashcat -m 0 vaultbank_hashes.txt rockyou.txt

That was it. 99.4% of a production database — recovered.

This is OWASP A02:2021 — Cryptographic Failures.
And it's sitting in your application right now.


What Is Cryptographic Failures?

Previously called "Sensitive Data Exposure," the OWASP team renamed it
in 2021 to target the root cause, not just the symptom.

The symptom is data exposure. The cause is failing to protect it
with proper cryptography — or failing to use it at all.

It's the #2 most critical vulnerability in the OWASP Top 10.
It's found in 40%+ of tested applications.
And it requires zero hacking skill to exploit.


The Four Ways VaultBank Failed

I built a simulated banking application called VaultBank. It had
240,000 customers, real-looking data, and four cryptographic
failures that exist in production systems today.

Failure 1 — HTTP Login (No TLS)

The login page was served over HTTP.

That's it. That's the vulnerability.

When a victim submitted their credentials on a shared Wi-Fi
network, Wireshark captured this in real time:

12:04:21.882  POST /login
DATA: username=b@vaultbank.io&password=MyBank#2024!
Enter fullscreen mode Exit fullscreen mode

No interception proxy. No special setup. Just Wireshark with
tcp.port==80 and a cup of coffee.

The password traveled as plain ASCII across every router, every
ISP node, every network hop between the user and the server.

The fix:

# Redirect all HTTP to HTTPS
server {
  listen 80;
  return 301 https://$host$request_uri;
}

# TLS 1.2 and 1.3 only — no legacy
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;

# Force HTTPS for 2 years
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
Enter fullscreen mode Exit fullscreen mode

Failure 2 — MD5 Password Hashing

The users table looked like this:

username password_hash
b.shahabi 5f4dcc3b5aa765d61d8327deb882cf99
m.ahmadi e10adc3949ba59abbe56e057f20f883e
admin d8578edf8458ce06fbc5bb76a58c5ca4

MD5. Unsalted. In 2024.

A modern RTX 4090 GPU computes 12 billion MD5 hashes per second.
The full rockyou.txt wordlist has ~14 million passwords.
That's a complete crack in 0.001 seconds.

But it gets worse. Those hashes above? I didn't even need hashcat.
I looked them up in a rainbow table:

  • 5f4dcc3b...password
  • e10adc39...123456
  • d8578edf...qwerty

Three lookups. Three admin-level accounts. Zero GPU time.

The algorithm comparison:

Algorithm Speed (RTX 4090) Crack time (14M wordlist)
MD5 12,000,000,000/s 0.001 seconds
SHA-256 8,500,000,000/s 0.002 seconds
bcrypt (cost 12) 12,000/s 19 minutes
Argon2id 400/s 9.7 hours

The OWASP recommendation for 2025 is Argon2id.

const argon2 = require('argon2');

// Hashing — takes ~300ms intentionally
const hash = await argon2.hash(password, {
  type: argon2.argon2id,
  memoryCost: 65536,  // 64MB — GPU killer
  timeCost: 3,
  parallelism: 4
});

// Migration — upgrade on next login
if (user.hashType === 'md5') {
  if (md5(password) === user.password) {
    user.password = await argon2.hash(password);
    user.hashType = 'argon2id';
    await user.save();
  }
}
Enter fullscreen mode Exit fullscreen mode

No forced password reset. Users get upgraded silently on next login.


Failure 3 — Credit Cards in Plaintext

The payments table:

SELECT card_number, cvv, expiry FROM payments LIMIT 3;

4111 1111 1111 1111 | 737 | 12/27
5500 0000 0000 0004 | 912 | 08/26
3714 496353 98431   | 044 | 03/25
Enter fullscreen mode Exit fullscreen mode

240,000 complete card records. Stored verbatim. No encryption.
CVV included — which PCI-DSS 3.2.1 prohibits storing at all,
under any circumstances, encrypted or not.

A single SQL injection on the /api/transactions endpoint
returned every row in one request.

The fix:

Stop storing card data entirely. Use tokenisation.

// WRONG — you receive and store raw card data
app.post('/payment', (req, res) => {
  const { cardNumber, cvv } = req.body;
  db.insert({ card_number: cardNumber, cvv }); // never do this
});

// RIGHT — Stripe tokenises before it reaches your server
// Your server only ever sees: tok_1NmC8p2eZvKYlo2C3fL9H5Kj
// That token is useless to an attacker
Enter fullscreen mode Exit fullscreen mode

If you must store card data (you almost certainly don't):
AES-256-GCM, envelope encryption, keys in AWS KMS — never
in the application code.


Failure 4 — Hardcoded Secrets in GitHub

Committed 18 months ago. Never noticed. Never rotated.

// config.js — in the public GitHub repository
module.exports = {
  database: {
    password: "VaultDB_Pr0d_2023!!"   // production DB password
  },
  stripe: {
    secretKey: "sk_live_4xKjNmP8qR2vL9wT6uY1sD" // live Stripe key
  },
  jwtSecret: "vault_jwt_secret_2023"  // forge admin tokens with this
};
Enter fullscreen mode Exit fullscreen mode

With the Stripe key: charge any stored card.
With the JWT secret: forge an admin token and access all 240,000 accounts.
With the DB password: connect directly to production.

Tools like trufflehog and GitHub Advanced Security find these
in seconds. Attackers run them constantly.

The fix:

# Never in code. Never in .env committed to repo.
# Use a secrets manager.

# HashiCorp Vault
const secret = await vault.read('secret/prod/stripe')
const stripeKey = secret.data.secret_key

# .gitignore
.env
*.env.*
config/secrets.js
Enter fullscreen mode Exit fullscreen mode

And once a secret is exposed: rotate everything immediately.
Assume all secrets in that repository are compromised.


The Full Impact

Four failures. One application. Combined result:

  • 240,000 passwords recoverable in 4 minutes
  • 240,000 credit card numbers downloadable in one SQL query
  • $192 million in potential card fraud
  • Stripe live key → charge any stored card
  • JWT secret → impersonate any user including admins
  • AWS keys → download nightly database backups
  • GDPR breach notification required within 72 hours
  • Fine exposure: up to 4% of global annual revenue

None of this required a novel exploit. No CVE. No zero-day.
Just knowledge of what to look for — and the patience to look.


The Checklist

Before shipping anything that handles user data:

  • [ ] HTTPS enforced everywhere — HTTP redirects to HTTPS
  • [ ] TLS 1.2+ only — SSLv3, TLS 1.0, TLS 1.1 disabled
  • [ ] HSTS with preload directive
  • [ ] Passwords hashed with Argon2id (not MD5, not SHA-256)
  • [ ] No sensitive data stored in plaintext
  • [ ] Card data tokenised — never stored raw, CVV never stored
  • [ ] Secrets in Vault / Secrets Manager — not in code or .env
  • [ ] SSL Labs score: A+
  • [ ] Pre-commit hooks scanning for secrets

One Final Thought

The most dangerous assumption in software is:

"No one will bother attacking us — we're not big enough."

The attacker running hashcat on your leaked database
doesn't know your name. They downloaded the dump from a
breach aggregator and queued it alongside 200 other databases.

Your users reuse passwords. Their Gmail, their PayPal, their bank —
same password as your app. When your MD5 database leaks,
it's not just your users who suffer.

Cryptographic failures are invisible until they're catastrophic.
The fix is boring. The alternative is not.

Reconstructed by CAISD — Cyberscope Advanced Intelligence & Security Directorate
📺 Full interactive simulation: youtube.com/@CAISD_Official
📧 caisd.ofc@gmail.com


Enter fullscreen mode Exit fullscreen mode

Top comments (0)