What a penetration test actually finds on a small business website
Nobody clever is targeting your plumbing company. Someone patient with a scanner is — and they are looking for the same short list of mistakes on every site. Here is that list, what each one costs you, and three checks you can run yourself this afternoon.
Most small businesses that get breached were not chosen. They were found — by a script walking through a few hundred thousand domains, checking each one for the same handful of mistakes, and stopping where it got a hit. That is genuinely good news, because it means the things that get you breached are boring, well understood, and usually cheap to fix.
This is what an application penetration test on an ordinary small-business website turns up, roughly in the order we find it, and what each finding is actually worth to you.
First: a test is not a scan
A vulnerability scan is a tool run. It fires known signatures at your site and prints what matched. It is useful, it is fast, and you should run one — but it can only find bugs that somebody has already written a signature for.
A penetration test is a person using those tools and then reasoning about your application. A scanner has no idea that customer number 4,412 should not be able to open invoice number 3,118. It does not know what a booking is, or which of your pages is supposed to require a login. Every serious finding on the list below comes from someone understanding your business rules and then deliberately breaking them.
The findings that show up on almost every site
1. Logins and forms with no rate limit
You can submit the login form as fast as your connection allows. So can everyone else. Attackers do not sit there guessing passwords one at a time any more — they take username and password pairs from other companies' breaches and replay them against your site, thousands per minute, on the reasonable assumption that some of your customers reuse passwords. It is called credential stuffing and it is almost entirely automated.
The password-reset form is usually worse. If it says "no account with that email" for one address and "check your inbox" for another, an attacker can build a clean list of your real customers before trying a single password. Your contact form, left without a rate limit, becomes somebody's spam relay.
The fix is not glamorous: rate limit per IP address and per account, return the same response whether or not the email exists, and put a challenge in front of anything that sends mail. Usually an afternoon of work.
2. Authorization that trusts the URL
This is the one we most want to find, because it is the one that ends up in a newspaper. Your site shows a logged-in customer their invoice at /invoices/3118. Change the number to 3117 and you are looking at someone else's invoice — name, address, amount, sometimes the last four digits of a card.
This happens because authentication and authorization get confused for each other. Authentication asks *who are you*, and almost everyone gets that right. Authorization asks *are you allowed to see this specific record*, and that check is frequently missing — because during development the only person logged in was the developer, and the developer owned everything.
OWASP ranks broken access control at the top of both its web and API risk lists, and it is not close. Hiding the link in the interface does nothing; the browser is not where the check belongs. Every route that returns a record has to ask, on the server, whether the session requesting it owns that record.
3. A component with a public exploit
An abandoned WordPress plugin. A theme two major versions behind. A JavaScript library from 2019 still loaded on every page. The exploit is published, the proof-of-concept is on GitHub with instructions, and your version number is sitting in a script path or a generator tag where any scanner reads it in milliseconds.
Nothing about this requires skill to exploit, which is exactly why it gets exploited. The awkward cases are plugins whose authors have stopped shipping updates — there is no patch coming, and the honest answer is to replace or remove the plugin rather than pretend a WAF rule covers it.
4. Things on the server that were never meant to be public
This category is not about your code at all. It is about what got left behind on the way to production:
- A
.envfile served as plain text, containing the database password, the mail credentials and the payment API key. /backup.sqlin the web root, from the migration two years ago.- An exposed
.gitdirectory, which hands over your entire source code and its full history. - A staging copy of the site, indexed by Google, running last year's code with test logins that still work.
- Directory listing switched on for
/uploads, quietly publishing every document your customers ever sent you.
None of these are hard to fix. All of them are found by a script in seconds, because the script only has to try the same forty filenames on every domain it meets.
5. The low-severity tail
Missing security headers. Session cookies without HttpOnly, Secure or SameSite. An error page printing a stack trace with your filesystem paths and framework version. Old TLS configuration.
On their own these rarely get anyone in, and a report that leads with them is a report padded to look thorough. They matter for a different reason: they are the difference between a small bug and a bad day. A cross-site scripting flaw with HttpOnly set steals nothing; the same flaw without it walks off with the session. This section is also the cheapest to close — most of it is configuration, not code.
What we usually do not find
The dramatic ones. Classic SQL injection is far rarer than it was, because modern frameworks parameterize queries by default and you have to work to defeat that. Remote code execution on a managed host is uncommon. If someone hands you a report full of Hollywood vulnerabilities on a brochure site, read it very carefully.
The real risk to a small business is quieter and more expensive: data one customer can read about another, an account that can be taken over, an admin panel with a guessable password and no second factor. Those are the findings that turn into a disclosure letter.
Three checks you can run this afternoon
None of these are a substitute for a test. All three take under a minute, and if any of them fails you have learned something worth knowing today rather than next quarter.
curl -sI https://example.com/.git/HEAD | head -n 1
# HTTP/2 404 good — nothing there
# HTTP/2 200 your repository is being served to the internet.
# Fix this today, then rotate every secret in it.curl -sI https://example.com | grep -iE \
'strict-transport-security|content-security-policy|x-frame-options|x-content-type-options'
# Nothing returned means none of them are set.
# On most stacks this is a config change, not a rebuild.The third one needs no tools. Log in as one test customer and copy the URL of an order, invoice or booking. Open a private window, log in as a different test customer, and paste that URL. If the record loads, stop reading and call your developer — you have a broken access control finding, and it is the expensive kind.
What the report has to contain
A finding you cannot act on is not worth paying for. Whoever you hire, this is the minimum:
| What you should get | Why it matters |
|---|---|
| A CVSS score on every finding | It is the shared vocabulary your insurer, auditor and enterprise customer already speak. |
| Steps to reproduce | Your developer can confirm the bug in five minutes instead of arguing about whether it is real. |
| The specific fix, not the name of the bug | "Broken access control" is a category. "Add an ownership check to this route" is something someone can do on Tuesday. |
| A summary a non-technical owner can read | You are the one deciding what gets budget. You should not need a translator. |
| A retest after you have fixed it | An unverified fix is a hope. We include one retest as standard. |
The goal of a test is not a clean report. It is a report you can hand to a developer on Monday morning and see closed by Friday.
If you only do one thing
Check your authorization. Take the three or four pages on your site that show a customer their own data, and make sure the server verifies ownership on every one of them. That single class of bug accounts for more real-world small-business data exposure than everything else on this page combined, and it is invisible to every automated scanner you can buy.
Then patch what you are running, and delete what you are not.
If you want a second opinion on any of this, that is what we do — web and API penetration testing against the OWASP Top 10, scored, reproducible, and retested once you have fixed it. Or just book 20 minutes and we will tell you honestly whether you need a test yet.

Subin Sunder Raj
Subin leads security and infrastructure at Quantis Sphere. He runs the web and API penetration tests, hardens the Linux and cloud environments client systems run on, and builds the Wazuh SIEM deployments the security practice is built around — the same person scopes the engagement and does the work.