🔐 Cybersecurity · Student Project · Web Security

We Built a Banking App That Hacks Itself

SecureBank is a university project where we showed real attacks — SQL injection, XSS, CSRF — happening live. Then we stopped them. Here's what we learned.

MT
AH
HH
HA
⏱ 6 min read
🏦

SecureBank — Attack Lab

Flask · SQLite · AES · CSRF · XSS · SQL Injection

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 goal was to build something that shows how attacks work — not just describe them."

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.

Python Flask SQLite pycryptodome AES-128 DES SHA-512 HTML/CSS/JS

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:

# Attack mode — dangerous query = f"SELECT * FROM users WHERE username = '{input}'" # If you type: admin'-- # The query becomes: # SELECT * FROM users WHERE username = 'admin'--' # The -- comments out the password check. You're in.
💀 Attack Mode

User types admin'-- as username. The password check is commented out. Login succeeds with no password needed.

🛡 Secure Mode

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.

💀 Attack Mode

Script tags stored as-is. Every visitor's browser executes the injected JavaScript.

🛡 Secure Mode

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.

"The attack works because the server doesn't check where the request came from — only that you're logged in."
💀 Attack Mode

Fake page posts a transfer form. Server accepts it — the session cookie proves you're logged in. Money moved.

🛡 Secure Mode

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.

✅ AES-128

128-bit key. Current global standard. Never cracked. Still the right choice for anything you want to actually protect.

⚠️ DES (Legacy)

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:

X-Frame-Options: DENY # Can't be embedded in iframes → stops clickjacking X-Content-Type-Options: nosniff # Browser won't guess the content type X-XSS-Protection: 1; mode=block # Browser's built-in XSS filter enabled Content-Security-Policy: ... # Restricts script/style sources Referrer-Policy: ... # Controls what referrer info is sent

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

MT
Mohammad Talha 23F-0661 · Frontend UI — login, dashboard, messages pages
AH
Abdul Hadi 23F-0743 · Flask backend, routing, DB design, SQL injection & CSRF
HH
Huzaifa Hammad 23F-0514 · Crypto lab — AES, DES, SHA-512 integration
HA
Haider Abbas 23F-0632 · Security headers, cookie flags, access control & testing
· · ·

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.