1. A03 Injection - login builds its SQL query by string concatenation so it is vulnerable to SQL injection
3. A07 Identification and Authentication Failures - session tokens are a random number 0-10 so they can be guessed
4. A01 Broken Access Control - GET /api/notes/:id has no ownership check and GET /api/users needs no authentication at all
This project has 5 intentionally created vulnerabilities from OWASP top 10 list. So it actually isn't secure at all. 🤯🤯🤯
Welcome to SecureApp, a not so secure app where you can register, log in and save private notes. The stack is Typescript with React.js acting as frontend and Node.js as backend. The database is a SQLite file (backend/data.db) that gets created and seeded automatically on the first run. I am using the OWASP 2021 list.
This project was made using Typescript so you need to have Node.js and a package manager such as npm installed. The backend uses Node's built in SQLite module so you need Node.js version 22.5 or newer. You can check your version with node -v.
-
YOU CAN SKIP THIS STEP IF YOU HAVE NODE 22.5+ AND NPM INSTALLED Check if you have installed Node.js and npm. Type
node -vto find out if you have node installed. If this command returns a version number that is 22.5 or higher, you have it installed. Check npm installation by typingnpm -vinto the console. If you have npm installed you get a version number. In order to install Node.js and npm, follow instructions found here. -
Clone the repo with command
git clone https://github.com/Melimet/cybersecproject.gitIf you somehow are missing git, installation instructions can be found here -
Go inside the cloned repo with
cd cybersecproject. -
Install project dependencies with
npm install -
Start the project by running command
npm run start. The app opens up in http://localhost:3000/. Backend is hosted on http://localhost:3001/. You can now play around with the project. The databasebackend/data.dbis created and seeded on the first run. You can register your own account, or use one of the seeded ones below.
Usernames and Passwords
raimo1952:hunter2
root:root
ville_vastaamo:asd123
HOX: these seeded accounts only work as is. If you enable the flaw 2 fix (bcrypt hashing), their plain text passwords no longer match. As a workaround register your own account to log in.
EXACT LINK: https://github.com/Melimet/cybersecproject/blob/main/backend/src/services/userService.ts#L43
DESCRIPTION: The login query is built by pasting the username and password straight into a SQL string. This means you can log in without knowing any password. Type the username ' OR '1'='1' -- and any password into the login form and you are logged in as the first user in the database.
FIX: Use a parameterized query instead of string concat so the database treats the input as data and not as code. The commented fix is on the line right above the flaw.
Before fix:
After fix:
EXACT LINK: https://github.com/Melimet/cybersecproject/blob/main/backend/src/services/userService.ts#L27
DESCRIPTION: When you register, the password you chose is written into the database in plaintext. So anyone who can read the database file, or who reaches the open /api/users endpoint (flaw 4), or who exploits the SQL injection (flaw 1), instantly gets everyones password in plain text. The login response also hands the password back to the client.
FIX: Don't store the password in plaintext. Instead store a salted bcrypt hash of it and compare them when logging in. The commented fix is on the lines right above the flaw and in the login function. Note: the fix only hashes passwords created through registration, the seeded accounts stay plain text, so test the fix with a newly registered account.
Before fix:
After fix:
EXACT LINK: https://github.com/Melimet/cybersecproject/blob/main/backend/src/services/userService.ts#L54
DESCRIPTION: The session token is String(Math.round(Math.random() * 10)), so there are only 11 possible tokens (0-10) in the whole system. After logging in you get a token like 4, and the backend trusts it as your identity. An attacker just sends Authorization: Bearer 7 and if anyone currently holds that token they are logged in as them, no password needed.
My implementation is kind of an extreme case, but the token could be for example a rolling integer instead, which would be just as easy to guess for a malicious user.
FIX: Issue the token from a cryptographically secure source with crypto.randomBytes(32) so it can not be guessed. The commented fix is on the line right above the flaw.
Before fix:
After fix:
EXACT LINK: https://github.com/Melimet/cybersecproject/blob/main/backend/src/routes/notes.ts#L22
DESCRIPTION: In the app you open a note by clicking it, which sends GET /api/notes/:id with your token. The problem is the endpoint returns any note to any logged in user, it never checks that the note actually belongs to you. So with your own valid token you just change the id in that request (in the browser devtools network tab or with curl) and read other peoples private notes. This is called IDOR (Insecure Direct Object Reference). BONUS FLAW: GET /api/users needs no authentication at all and returns every users password and card data. See also https://github.com/Melimet/cybersecproject/blob/main/backend/src/routes/user.ts#L14
FIX: Require a valid session and only return the note if user_id also matches the logged in user. Lock /api/users behind authentication so it only returns the callers own record. This is a real auth check and not just hiding the endpoint. The commented fixes are above each flaw.
Before fix:
After fix:
EXACT LINK: https://github.com/Melimet/cybersecproject/blob/main/backend/src/app.ts#L12
DESCRIPTION: The app has no way to log the traffic and events coming into it. That means a token brute force (flaw 3), a bunch of failed logins or someone reading other peoples notes (flaw 4) all happen completely silently. Hosts of this project would have no idea anyone was attacking it.
This might sound kind of farfetched but from my own experience people tend to forget to log very important or useful stuff.
FIX: Add morgan to log every request to a file, and log the security relevant events (failed logins, new registrations, denied note access) with the securityLog helper. The commented fixes are in app.ts and next to the events in userService.ts and notes.ts.
Before fix:
After fix:









