Welcome to the final lesson! In our previous sessions, we acted like scouts. In the second lesson we mapped the network infrastructure, in the third we enumerated hidden API endpoints, and in the fourth we intercepted live traffic. We have successfully identified where the application lives and how it communicates. Now, it is time to use that knowledge to demonstrate real risk.
In professional penetration testing, you will often find vulnerabilities that seem minor on their own. A developer might dismiss a small information leak or a glitch in a search bar as "low priority." However, hackers do not attack vulnerabilities in isolation. They combine them.
This concept is called Vulnerability Chaining. It involves taking several low-severity issues and connecting them like puzzle pieces to create a high-severity exploit. In this lesson, we will simulate a real-world scenario where we combine a simple IDOR (Insecure Direct Object Reference) with an SQL Injection in a search feature to escalate our privileges and steal the entire database.
Before we build our chain, we need to identify the weak links. During a security assessment, you act as a detective looking for clues. Two very common "clues" you might find are IDOR and overlooked Injection points in non-critical features.
IDOR stands for Insecure Direct Object Reference. This happens when an application exposes a reference to an internal database object, like a User ID (e.g., /users/105), but fails to check if the person requesting it actually has permission to see it. Individually, this might just leak a username or an email address. While not good, it is often not considered a critical emergency by itself.
SQL Injection (SQLi) is a high-severity vulnerability and a staple of the OWASP Top 10 — a list of the most critical security risks in web applications. We often look for this in login forms to bypass passwords. However, developers are getting better at securing login forms. They often forget to secure less critical features, like a Search Snippets bar. They assume that because the search bar only looks at public notes, an injection there will not matter. We are about to prove that assumption wrong.
The first step in our chain is information gathering. We suspect the application uses sequential User IDs (1, 2, 3...) to identify accounts. If the application does not verify who is asking, we can simply ask for every id to see what comes back through User Enumeration.
We will use a simple shell script to loop through numbers 1 to 10 and send a request for each one. We authenticate using our own $TOKEN, but we ask for other users' profiles.
If the application is vulnerable to IDOR, the server will reply with JSON data for each user, even though we are logged in as someone else.
This is our first win. We now know that id: 1 is the admin. We have turned a blind guess into a concrete target. This information is vital for the next steps.
Now that we know who our target is (the admin), we need a way to access their data. We turn our attention to the application's search feature. The application allows users to search through text snippets.
We test for SQL Injection by sending a special character that is significant in database syntax: the single quote '. If the developer has not sanitized the input, this quote will break the SQL query structure, often causing the server to crash or return an error.
In the command above, test' tries to close the text string in the database query early. The -- is a comment character in SQL, which tells the database to ignore everything that comes after our injection. We use --data-urlencode to ensure the special characters are properly encoded for the URL.
If the server returns a database error or behaves unexpectedly (like showing all results instead of none), we have confirmed the vulnerability. We now have a way to talk directly to the database.
We have two pieces of the puzzle: we know the Admin's ID is 1 (from the IDOR), and we have a way to alter database queries (from the SQLi). Now we chain them together.
Normally, when you search for snippets, the backend code likely adds a rule like WHERE userId = current_user. This prevents you from seeing other people's private notes. However, because of the injection, we can rewrite that rule.
We will inject a payload that says: "Search for nothing, OR give me results where the userId equals 1."
Here is what happens inside the database. The original query might look like this:
SELECT * FROM snippets WHERE body LIKE '%search%' AND userId = 4;
After our injection, the query becomes:
SELECT * FROM snippets WHERE body LIKE '%' OR userId=1--' AND userId = 4;
Because we added OR, the database returns any record where userId is 1, ignoring the fact that we are actually user 4. We have successfully bypassed the privacy checks and stolen the admin's private notes.
As a penetration tester, your job is not just to break things, but to explain how you broke them so they can be fixed. This is called Documentation. When reporting a vulnerability chain, you must clearly articulate the steps.
A good report does not just say "You have SQL Injection." It tells a story:
- Recon: "We identified that
User IDenumeration allows attackers to find target accountIDs." - Entry: "We identified an
SQL injectionflaw in the search endpoint." - Exploit: "By chaining these, we bypassed authorization checks."
- Impact: "This allowed full extraction of administrative credentials."
Clear Documentation helps business leaders understand that fixing the "small" bugs is actually a high priority.
Now we put on our defender hats. To stop this attack chain, we need to apply Defense in Depth. This means implementing multiple layers of security so that if one fails, another catches the attack.
Before we look at the fix, let's understand the tool we'll use. Sequelize is an ORM (Object-Relational Mapper) — a library that lets you interact with databases using JavaScript objects instead of writing raw SQL strings. When you use Sequelize's built-in methods, it automatically parameterizes your queries, meaning user input is always treated as data, never as executable code.
Sequelize provides an Op (Operators) object that contains safe equivalents of SQL operators. For example:
Op.likeis the safe version of SQL'sLIKEOp.eqis the safe version of=Op.oris the safe version ofOR
When you write { title: { [Op.like]: '%searchterm%' } }, Sequelize converts this to a parameterized query where searchterm can never break out and become SQL code.
We will modify the backend code to fix both the SQL Injection and the IDOR logic. The key changes are:
- Replace raw SQL with Sequelize's
findAll()method — this automatically parameterizes queries - Always enforce
userIdfrom the authenticated session — this prevents IDOR
Here is the secure implementation. Notice how we extract the userId from the JWT token (the same pattern used throughout our codebase) and pass it directly to the query:
In the code above, even if a user tries to send ' OR userId=1--, Sequelize will treat that entire string as the title to search for. It will not execute it as SQL commands. The userId is strictly set from decoded.userId, which comes from our JWT verification — this is trusted server-side data that the attacker cannot manipulate.
In this lesson, we explored Vulnerability Chaining. We learned that:
- Low-severity bugs like
IDORand minorSQLinjections become critical when combined. - Enumeration allows us to find valid targets (like
Admin IDs). SQL Injectioncan be used to bypass logic checks (OR 1=1) or steal data from other tables (UNION).- Sequelize ORM provides safe database operations through parameterized queries using the
Opoperators. - Layered Defense using parameterized queries and strict user scoping effectively breaks the chain.
You have seen how a few lines of bad code can lead to a total system compromise. Now it is your turn to practice. In the upcoming exercises, you will write the script to perform this attack chain against a test server, and then you will fix the vulnerability to secure the application.
