Most university security projects end up as a PDF. Ours ended up as a web application that can attack itself. SecureBank is a banking app we built from scratch as our Information Security semester project at FAST-NUCES Chiniot-Faisalabad. The twist? You can flip a switch and watch real attacks play out in real time — then flip it back and see every attack get blocked.
It sounds simple. It was not. This post walks through what we built, how the attacks actually work, and what defending against them really looks like in code.
The Big Idea: Two Modes, One App
The app runs in two modes you can switch between at any time. Attack Mode turns all the defenses off. Real vulnerabilities are active. You can inject SQL into the login form, post a malicious script in the messages page, and trigger a fake bank transfer from another website. Secure Mode turns every defense on. The same attacks get stopped cold.
We built this contrast on purpose — you can open your browser's DevTools and watch the differences in response headers, database queries, and behavior. Learning security by seeing it break is very different from reading about it.
Attack 1: SQL Injection
This is one of the oldest and most common web attacks. The login form sends your username to the database as part of a query. In attack mode, the app builds that query by directly pasting your input into the SQL string:
User types admin'-- as username. The password check is commented out. Login succeeds with no password needed.
We use parameterized queries. The input is passed as a value, not part of the query string. The database escapes it automatically.
Attack 2: Cross-Site Scripting (XSS)
XSS is when someone injects a script into a page and it runs in other users' browsers. Our messages page lets users post text. In attack mode, whatever you type gets stored as raw HTML and rendered directly.
If you post <script>alert('hacked')</script>, every user who opens that messages page will see that alert pop up. In a real attack this would steal session tokens, redirect users to phishing pages, or silently log keystrokes.
Script tags stored as-is. Every visitor's browser executes the injected JavaScript.
Python's html.escape() converts < and > to harmless text. The browser shows the tag — it never runs it.
Attack 3: CSRF — The Invisible Request
CSRF is the sneaky one. We built a separate fake webpage — disguised as a prize voucher site — that sits outside the banking app. When a logged-in user visits that fake page and clicks a button, it silently sends a bank transfer request on their behalf. The bank server sees a valid logged-in session and processes it. The user never knew.
Fake page posts a transfer form. Server accepts it — the session cookie proves you're logged in. Money moved.
Every transfer form includes a secret CSRF token generated when the page loaded. Fake pages don't have it. Server returns 403.
Passwords & Role-Based Access
Passwords are never stored as plain text in our database. When a user registers, their password goes through SHA-512 hashing before it's saved. Even if someone dumped the database, they'd see only hashes — not usable passwords.
We also built two roles: admin and user. Normal users (alice, bob) can only see their own dashboard. The /admin route checks the role stored in the session and returns a 403 error for anyone who isn't an admin. No workarounds.
The Crypto Lab: AES vs DES
We added an interactive cryptography section inside the app. You type any text, choose AES-128 or DES, and the app encrypts it on the server using the pycryptodome library. The decryption form auto-fills with the ciphertext, key, and IV so you can verify the round-trip works.
128-bit key. Current global standard. Never cracked. Still the right choice for anything you want to actually protect.
56-bit key. Broken in 1999. Modern hardware can brute-force it in under 24 hours. We show it so you can see why it was retired.
The SHA-512 section at the bottom lets you hash any string and see the full output. Good for understanding what "hashing" actually looks like — a fixed-length fingerprint that you can't reverse.
Security Headers — The Invisible Shield
Most people never notice HTTP response headers. They're invisible — but they tell your browser exactly what it's allowed to do with the page. In secure mode, every response from SecureBank includes these:
The session cookie is set with HttpOnly (JavaScript can't read it) and SameSite=Lax (it won't travel with cross-origin requests). In attack mode, all of these are removed. You can open DevTools → Network and see the difference instantly.
What We'd Do Differently
This was a semester project, so some things were scoped out on purpose. Here's what we'd add with more time:
HTTPS everywhere. The app runs over HTTP locally. In any real deployment, every page must be served over TLS and the session cookie needs the Secure flag set.
Rate limiting on login. Right now there's nothing stopping someone from trying thousands of passwords. A real app locks accounts after a few failed attempts.
Two-factor authentication. SHA-512 hashing is good, but 2FA is the next layer. A stolen password alone shouldn't be enough.
A real database in production. SQLite is perfect for demos. PostgreSQL or MySQL would handle concurrent users and proper access controls for a live deployment.
The Team
The best way to understand security is to break something first. SecureBank gave us exactly that — a safe sandbox where we could watch real attacks land, understand why they work, and then build the defenses ourselves. Every line of fix code means more than any textbook definition.
If you're studying web security and want to see these attacks in action rather than just reading about them, build something like this. The gap between "I know what SQL injection is" and "I watched it bypass my login form" is enormous.