Currently accepting new security engagementsRequest a scope
Redacted engagementHighBroken access control and insecure direct object references

Object references that were checked in the interface but not on the server

The application hid what a user should not see and enforced very little of it server side. Interface level restriction is a usability feature. It becomes a security control only when the server independently reaches the same conclusion, and here it did not.

A multi-tenant platform where each customer's data is meant to be invisible to every other customer

Redacted. This describes work that was carried out. The client, the product, the affected endpoints and the exact requests are deliberately absent, and no detail here would help anyone locate or reproduce the issue on the original system. Published with the class and the reasoning intact because that is the part you can act on.

The situation

Tenant isolation was the client's single largest concern, because a failure there is the one finding their own customers would hear about. Testing used two accounts in two separate tenants and a third with a lower role inside one of them.

How it was approached

  1. Recorded the identifiers each account legitimately saw, then replayed each request from every other account and role.
  2. Tested read, write, update and delete separately, since applications frequently guard the read path and forget the others.
  3. Checked indirect exposure paths as well as direct ones, including exports, search results, notification content and error messages that differ depending on whether a record exists.
  4. Confirmed each finding by observing the actual returned data rather than by inferring from a status code.

What was found

Several endpoints authenticated the caller correctly and then acted on whichever identifier was supplied, without checking that the identifier belonged to the caller's tenant. Some responses differed depending on whether a record existed, which allowed identifiers to be confirmed even where the data itself was withheld.

Why it mattered

One customer could read and in places modify another customer's records by changing a value in a request. There is no exploit chain to explain and no unusual tooling involved, which is exactly what makes this class the one most likely to be found by a curious user rather than by a tester.

How it was fixed

  • Scope every query by the caller's tenant at the data access layer, so isolation is the default rather than something each endpoint must remember.
  • Authorize the object, not just the route. Authentication answers who is calling; it does not answer whether this record is theirs.
  • Return the same response for a record that does not exist and a record the caller may not see.
  • Add automated tests that assert cross-tenant access fails, and run them on every release.
Check this on your own system

Three things worth doing this week

None of these needs a purchase order. They are the checks that most often turn up something on a system nobody has looked at from the outside.

If the control only exists in the interface, it does not exist

Hiding a button changes what a user is offered. It changes nothing about what the server will do when the request arrives anyway.

Isolation belongs at the data layer

Enforcement written endpoint by endpoint holds until somebody adds an endpoint in a hurry. Enforcement in the query layer holds by default.

Different errors are still an answer

A 404 for a record that does not exist and a 403 for one that does tells an attacker which identifiers are real. Make them identical.

More in this area

Penetration testing

All case studies
Redacted engagement

OAuth misconfiguration that allowed one account to be linked to another

Single sign-on was configured so that the identity returned by the provider was trusted more than the account it was being attached to. The result was an account linking path that let one identity end up in control of a different user's account. This is the class of issue that reads as a configuration detail and behaves as a full account takeover.

Redacted engagement

Password reset and session handling gaps found by testing the whole account lifecycle

Reviewing authentication as a lifecycle rather than a login page surfaced several issues that individually looked minor and together produced a reliable way to hold access to an account after the owner had tried to lock it down. Most of these do not appear in scanner output at all, because every response involved is a legitimate one.

Want to know whether this applies to you?

Tell us what you are running and which of these worries you. We will tell you plainly whether it is worth testing, what it would cost, and when the answer is that you should fix something yourself first.