A complete security audit: five real vulnerabilities, five documented exploits, and the fixes that closed them all. Java 21. MySQL 8. OWASP Top 10:2025.

The codebase looked fine at first glance. It had user authentication, role-based access, database connectivity. It was a Bookshop Management System — four roles, clean UI, working CRUD operations. Then I started looking harder.

Five minutes into the audit, I was logged in as admin without knowing the password.

This is the full breakdown of what I found, what I did with it, and how I fixed every single flaw — as part of my MSc in Cyber Security at National College of Ireland.

The Application

Built in Java 21 with Swing, backed by MySQL 8, connected through JDBC. Four user roles: Admin, Librarian, Customer, and Guest. Each role gets its own dashboard with scoped permissions.

None

Admins manage the full user list and have unrestricted system access. Librarians handle book loans and approvals. Customers browse, buy, and request loans. Guests get read-only catalogue access.

None
None
None

On the surface — a perfectly working application. Under the surface — a case study in how not to build software.

The Five Vulnerabilities

I went through the codebase systematically and documented five security flaws. Each one was assigned a tracking ID and mapped to OWASP Top 10:2025.

V-001 | SQL Injection — OWASP A03:2021

The login query was built by concatenating raw user input directly into a SQL string. No parameterisation. No sanitisation. Just open brackets waiting to be closed by an attacker.

One line of input bypassed authentication entirely.

V-002 | Plaintext Password Storage — OWASP A02:2021

Every password in the database was stored as plain text. One query to the users table and every credential for every account was readable — admin, librarian, customer — all of it visible in seconds.

None

V-003 | No Password Policy — OWASP A07:2021

The registration flow accepted any password, including a single character. No length check. No complexity requirement. No validation of any kind.

V-004 | No Input Validation on Stock — OWASP A03:2021

Stock quantities had no bounds checking. Setting a book's stock to -10 worked without error. Trivial on its own — but a clear signal of the broader design pattern: trust all input, validate nothing.

None

V-005 | No Brute Force Protection — OWASP A07:2021

No lockout. No rate limiting. No delay after failed attempts. The login endpoint responded to ten thousand requests exactly as fast as it responded to one.

Attack Log: What I Did to It

I ran documented proof-of-concept attacks against the codebase and recorded every result.

SQL injection login bypass. A SQL payload in the username field logged me in as the admin account instantly. No valid credentials. No guessing. Authentication completely bypassed.

Password exposure. Opened MySQL Workbench, ran one query against the users table. Every password in plain text next to every username and role. No effort required.

Negative stock. Entered -10 as the stock value for Clean Code and saved. The system accepted it without complaint.

Brute force. Ran repeated failed login attempts. Same error message every time. Same response speed. No threshold. No consequences.

Search injection. Sent a SQL payload through the book search field. The vulnerable version returned every record in the database.

Then I ran the same attacks on the secured version. Nothing got through.

None
None

The Fixes

V-001 Fixed — PreparedStatements

Every query now uses parameterised inputs. The ? placeholder separates user input from query logic completely. SQL characters in the input field are treated as data — not instructions. The injection returns nothing.

V-002 Fixed — BCrypt Hashing at Cost Factor 12

Passwords are never stored. The hash is stored. Login verifies the attempt against the hash using BCrypt.checkpw(). A full database dump now returns a wall of hashed strings that are computationally infeasible to reverse.

V-003 Fixed — Password Policy Enforcement

Every new password is validated before it touches the database: minimum 8 characters, at least one uppercase, one number, one special character. Weak passwords are rejected with a clear error before the DAO layer is ever called.

V-004 Fixed — Validation at the DAO Layer

Negative stock values are rejected in code — not just in the UI. Bypassing the interface does not help. The check runs in the data access layer, where the data is actually written.

V-005 Fixed — Account Lockout

Five consecutive failed attempts locks the account. Failures are tracked per username in a HashMap. After the threshold, the login screen returns a lockout message and the account stays locked for a defined window. Brute force becomes futile.

Beyond the Five Fixes

Closing the five specific vulnerabilities was the core task. But I also hardened the broader codebase.

Structured logging. java.util.logging.Logger replaced every printStackTrace() call. Stack traces in production expose class names, line numbers, and library versions — exactly the kind of recon an attacker uses to map your system.

Role enforcement at the DAO layer. UI-level button hiding is cosmetic. The real access control has to live in the code that actually executes the query. Even if someone manipulated the client, the backend would still reject unauthorised operations.

None

Audit logging. Every significant action now gets recorded — login events, failed attempts, admin operations. If something goes wrong, there is a trail.

None

Full input sanitisation. Not just the known vulnerable fields. Every text input that feeds a database query goes through validation.

Standards referenced throughout: OWASP Top 10:2025, ASVS 4.0, CWE classifications, and NIST SSDF SP 800–218.

What This Taught Me

None of the fixes were complicated.

PreparedStatements are not hard to write. BCrypt is a single library import. Input validation is ten lines of code. A HashMap-based lockout is a few dozen more.

The vulnerabilities existed not because someone was careless — but because security was never part of the design. It was something to think about later. It never got thought about.

That is how real breaches happen. Not exotic zero-days. The same five vulnerability classes, over and over again, in production systems used by real people.

The gap between writing Java and writing secure Java is not technical. It is a mindset shift. This audit made that very clear.

The Code Is on GitHub

All three versions are in the repository — the original baseline, the vulnerable build, and the secured version. Full source code, MySQL schema, Maven configuration.

https://github.com/shreeshk07/application_security_audit

If you want to see these attacks work rather than just read about them — clone it, run the vulnerable version, and try the injection yourself. The best way to understand SQL injection is to actually exploit it.

Finishing my MSc in Cyber Security at National College of Ireland. Currently looking for application security and security engineering roles. Connect with me on LinkedIn or follow me here on Medium.