What I discovered from 100+ security assessments and why the very same bugs keep popping up.

I've been conducting penetration tests for nearly two years. I started working in Mumbai, with numerous clients from different sectors and studying towards my Master's in Glasgow. After 100+ penetration tests, I realised something alarming: most companies are still making the same basic security mistakes.

Almost always, the vulnerabilities are the same. I'm not talking about zero-days or complex attack chains. As I said, I'm speaking about basic problems that developers build over and over again. These are the bugs that automated scanners are not always able to find, but manual testing catches every time.

So, I thought, let's share the top 5 vulnerabilities for nearly every engagement I encounter. This will be useful if you're a developer on the other hand to get rid of these mistakes. If you're starting in security, these are the first bugs you should learn to find.

1. IDOR (Insecure Direct Object Reference)

Just Change the ID' Bug That Breaks Everything.

This one is my favourite. Not because it is intricate really it is quite straightforward. But the effect is generally monumental.

What happens basically:

The application includes an ID in the request such as user id, Order ID, Document ID. And it doesn't verify whether you're even permitted access to that resource.

After login there was an endpoint like this:

GET /api/orders/detail?orderid=78432

I simply altered it to orderid=78433 and received a complete order description of another client's name, address, phone number, the purchase made, payment details. Everything. There was no proper authorisation check. The developer presumes that if the user is no longer able to get at this link, he cannot connect further. But of course, Attackers don't rely on UI navigation they inspect and modify requests directly using tools like Burp Suite.

How I usually test this:

• Login to application and capture all requests • Find any parameter that looks like an ID • Change it to some other value (sometimes sequential, sometimes random) • Check if you get data you shouldn't have access to

What developers should do:

Always enforce authorization checks on the server side. Don't just verify if a user is logged in verify that they are authorised to access that specific resource. Don't only see if user logged in. Make sure this user owns this particular data.

# This is wrong
def get_order(request, order_id):
    return Order.objects.get(id=order_id)

# This is correct
def get_order(request, order_id):
    order = Order.objects.get(id=order_id)
    if order.user_id != request.user.id:
        return HttpResponseForbidden()
    return order

Simple change but makes huge difference.

2. Cross-Site Scripting (XSS)

When User Input Turns Into JavaScript.

The most abused kind of vulnerability is XSS and it is likely the most widespread vulnerability in web applications. I see it in perhaps 70–80% of my assessments. Sometimes reflected, sometimes stored, sometimes DOM-based.

The basic idea:

User input is rendered on the page without proper sanitisation. So attacker can inject JavaScript code which will run in victim's browser.

What attackers can do with XSS:

• Steal session cookies • Capture keystrokes • Redirect user to phishing page • Take action as victim • Deface the website

Real scenario I encountered:

There was a search functionality. So, whatever you search for, it tells you "Results for: [your search term]".

I searched for:

<script>alert(document.cookie)</script>

And it popped up. No encoding, no filtering. Direct reflection. Now think about this again, I do not write the alert, I write the code that sends the cookies to my server.In a real attack, this wouldn't be an alert — it would silently exfiltrate session cookies

How I test for XSS:

I don't just attempt `<script>alert(1)</script>`. Modern applications might filter that. I use payloads depending on context:

If input goes inside HTML:

<img src=x onerror=alert(1)>

If input goes inside JavaScript:

';alert(1)//

If input goes inside the attribute:

" onmouseover="alert(1)

In addition, I check for DOM-based XSS by figuring out how JavaScript considers user input on client side.

Prevention:

• Make sure to encode output contextually. • Employ headers set under Content Security Policy. • Never use dangerouslySetInnerHTML or similar functions if you are using React or Angular, except where absolutely necessary. • Server-side input validation. (but don't rely only on this).

3. SQL Injection

When Input Becomes a Database Command.

Yes, SQL injection. In 2026. Still finding it. You'd think this is an old vulnerability, and everyone knew its presence by then. But no, especially in legacy apps and custom CMS, or when developers write raw SQL queries instead of reading from ORM correctly.

How it works:

You directly concatenate the user input into SQL query. Thus the attacker can alter the query.

Classic example:

Login form with this backend code:

query = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'"

So if I input username as `admin' — ` then the query becomes:

SELECT * FROM users WHERE username = 'admin' --' AND password = ''

The ` — ` comments out rest of query. Password check is bypassed. I am logged in as admin.

What I found recently:

There's a search option on a booking app. Database error when I included single quote in search parameter. Classic sign of SQL injection. Using SQLMap, I was able to retrieve the entire database:

sqlmap -u "[target.com](https://target.com/search?q=test)" --dbs --batch

Got all tables, all user information, even admin credentials (hashed but still).

How to prevent:

Use parameterized queries. Always. No exceptions.

# Wrong way
cursor.execute("SELECT * FROM users WHERE id = " + user_id)

# Right way
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))

If you have ORM (like Django or SQLAlchemy), use it properly. Don't write raw queries unless you must, and if needed, use parameters.

4. Security Misconfigurations.

The 'Oops, We Left It Public' Problem.

This is not one vulnerability at all; a category. And to be honest, this is where most of the critical issues have been discovered.

What I commonly find;

— .git folder accessible — can access source code.

— .env file exposed — contains database passwords, API keys.

— Backup files like backup.zip or database.sql sitting in web root.

— phpinfo.php showing server configuration.

Default credentials: It's surprisingly common to find default credentials of admin panels with admin/admin or admin/password type admin panels. Never mind that I usually apply default credentials first.

Verbose error messages: When error occurs, application makes full stack trace. This informs me of the framework version, folder paths, sometimes even code snippets.

Missing security headers: No CSP, no X-Frame-Options, no HSTS. Makes other attacks easier.

Directory listing enabled: Allowing attackers to browse files.

One interesting finding:

I found .git folder on production server. Used the git-dumper.

git-dumper [target.com](https://target.com/.git/) ./output

Got the full git repository. Hardcoded AWS keys in old commit. Those keys remained valid. Had access to their whole cloud infrastructure.

What companies should do: • Create a deployment checklist. • Get rid of all development files before going live. • Use security headers. • Change default credentials (for sure). • Disable directory listing. • Do not display extended error messages to the users. • Regularly look at your own infrastructure.

5. Broken Authentication.

Authentication Gone Wrong.

Authentication is hard to get right. And there are many ways it can fail.

Common issues I find:

No rate limiting: I can try unlimited passwords. Brute force becomes possible.

Weak password policy: App takes "123456" as a password. All users will use weak passwords as long as you allow them.

Session issues: • Session token in URL (leaked into logs, referrer header). • Session not invalidated after logout. • Password change does not invalidate session. • Session timeouts are either too long or not enforced.

Password reset problems: • Token is predictable (timestamp-based or sequential). • Token does not expire. • Token can be reused. • No confirmation of email ownership.

Interesting case:

One app had a password reset function. The reset link looked like:

[target.com](https://target.com/reset?token=MTcwMjM0NTY3OA==)

That token is base64 encoded. Decoded, it was merely Unix timestamp for when reset happened. So if I know about when someone asked it to reset, I can guess that person's token. I requested a password reset for my own account, noted the time window and got another user valid tokens.

How to fix:

• Rate limiting (but beware lockout attacks). • Create strong password policies. • Create random cryptographic tokens:

import secrets
token = secrets.token_urlsafe(32)

• Tokens expire after short time (15–30 minutes). • Invalidate sessions properly. • And you can use MFA, especially for admin accounts.

Why These Keep Happening.

Now that I have done 100+ tests or so, I have some observations:

1. Deadline pressure. Security takes time. Security testing is shortened or skipped when the project is behind schedule. "We fix it post-launch." But post-launch there's a lot else to create.

2. Developer training gap. The vast majority did not learn secure coding. They know how to build features, but not how to build them securely. This is their fault. Security is supposed to be included in the CS curriculum, but it usually is not.

3. False confidence in frameworks. "We are using React so XSS can't happen" Wrong. And frameworks are useful but you make mistakes too. You need to know the mechanism of the protection framework.

4. Security as afterthought. A best method is to design security from design phase. Security teams are almost always involved only at the end, only then, when the application is built already. And fixing architectural bugs becomes very costly by that time.

My Advice. If you are a developer:

Learn OWASP Top 10 correctly. Not just names know how each vulnerability works and how to avoid it. Rehearse for apps designed to be intentionally vulnerable (DVWA, Juice Shop, for example). You develop better code when you get how bad guys think.

If you are starting with security:

First, get this down. I see a lot of newcomers trying new things like kernel exploitation or binary analysis. Well, that is nice, but first learn the basics of web security. These 5 vulnerabilities will be the bread and butter of your career.

If you are a company:

Devise and train some training. Perform regular security assessments. Have a secure SDLC. And no — don't just run an automated scanner and call it penetration test. Get your manual testing done properly.

Conclusion.

Most real-world breaches don't rely on sophisticated attacks they happen because of simple, preventable mistakes.IDOR, XSS, SQL Injection, misconfigurations, and broken authentication continue to dominate real-world findings.

The good news? Every one of these issues is preventable.

With proper awareness, secure coding practices, and regular testing, organisations can eliminate the majority of these risks before attackers exploit them.

I am Tabish, a penetration tester, bug bounty hunter, and someone who enjoys breaking things (legally). Just finished my master's in Cyber Security from the University of Strathclyde. I write here to share what I learn and hopefully help others in their security journey.

Connect with me on linkedin.com or check my github.com.