Do Not Trust, Always Verify Twice!

A widespread breach of school platforms highlights how BOLA vulnerabilities expose sensitive data. Learn what BOLA is, why traditional security misses it, and how to secure APIs with robust relationship checks.

Share
Do Not Trust, Always Verify Twice!
Photo by FlyD / Unsplash

Yesterday (8th May) afternoon, my son returned from school feeling frustrated. His entire day was affected because their learning platform, Canvas, was compromised by a security breach. Upon further investigation, I discovered this was not an isolated incident. Schools and universities across the USA experienced similar issues, leading to the exposure of sensitive student data online.

As someone working in tech, I wondered: how are we still so vulnerable to these attacks? After reading forums and bug bounty reports, I suspect BOLA (Broken Object Level Authorization)—a common, often overlooked security flaw—was the main cause of the breach. Other possible factors include social engineering attacks, outdated cloud infrastructure, and missing critical patches. Since BOLA is the #1 API vulnerability, it's crucial to consider it alongside other risks.

By the end of this post, I want you to know how BOLA works and, more importantly, how we can fix it.

What Exactly is BOLA? (The Hotel Room Analogy)

To clarify, consider a hotel.

  • Authentication is like your room keycard. It proves you belong in the building and can access your own room.
  • BOLA is like having a keycard that opens every room in the hotel, not just your own.

In apps, IDs are used to find data. For example, you might visit my-app.com/user/101to view your profile. The server checks if you’re logged in, which is good. But with BOLA, an attacker can change 101to 102 and have the server return someone else’s private information. The server checked if you were logged in, but not if you should see that data.

Why Traditional Security Fails

BOLA is a nightmare because it’s a logic flaw, not a technical error. Standard security scanners and firewalls often miss it because the request looks completely normal. It’s a valid request, just for the wrong object.

Some developers think that switching from simple ID (like 101) to UUID(like f47ac10b...) solves the problem. The idea is that attackers can’t guess the IDs. But this is just security by obscurity, not real authorization. A UUID doesn’t control access; it only makes brute-forcing harder, but not impossible with automated tools.

How I Fix This in the Code

If you use Java or Spring Boot, I’ve noticed that manual checks don’t scale well. We should prefer to make things safe by design by putting authorization directly into the database query (There are other tradeoffs, but that is for another blog).

Let's explore in detail.

I will present actual Java and Spring Boot code to illustrate how BOLA vulnerabilities can occur.

🦺
GitHub Source code for this example. Start with the README.mdfile. URL: https://github.com/dcpanda/bola-security-issue

Here's a classic vulnerable Spring Boot controller:

@GetMapping("/account/{id}")
public Account getAccount(@PathVariable Long id) {
    return accountRepository.findById(id).orElseThrow(NotFoundException::new);
}

Do you see the problem? We’re trusting the ID from the URL. If accountRepository finds an account with that ID, it returns it without checking whether the logged-in user owns it.

Here's a quick fix (service layer check) to stop the immediate bleeding:

@GetMapping("/account/{id}")
public Account getAccount(@PathVariable Long id, Principal principal) {
    Account account = accountRepository.findById(id).orElseThrow(NotFoundException::new);
    if (!account.getOwnerUsername().equals(principal.getName())) {
        throw new ForbiddenException("You do not have permission to view this account.");
    }
    return account;
}

We retrieve the Principal (the logged-in user) and check whether the account’s owner matches the user’s ID. If not, we throw a Forbidden exception. This works for now, but it’s not a long-term fix.

Manual checks don’t scale. You’ll end up repeating this code everywhere, making maintenance harder and increasing the risk of missing something.

Here's what I consider a long-term fix (safe by design):First, using Spring Security's @PreAuthorize annotation to abstract the logic.

@GetMapping("/account/{id}")
@PreAuthorize("@securityService.isOwner(#id, authentication.name)")
public Account getAccount(@PathVariable Long id) {
    return accountRepository.findById(id).orElseThrow(NotFoundException::new);
}

​Here, @securityService.isOwner would be a component that contains the access control logic. It checks if the current user (passed in as authentication.name) owns the account with the specified ID. This is an improvement, but it still relies on a separate service check.

Another solution is to bake it into the database query itself:

interface AccountRepository extends JpaRepository<Account, Long> {
    Optional<Account> findByIdAndOwnerUsername(Long id, String ownerUsername);
}

And the controller:

@GetMapping("/account/{id}")
public Account getAccount(@PathVariable Long id, Principal principal) {
    return accountRepository.findByIdAndOwnerUsername(id, principal.getName())
           .orElseThrow(NotFoundException::new);
}

Now, the database query includes the authorization check. If the user doesn’t own the account, the database just returns “404 Not Found.” This is better than a 403 Forbidden, which tells an attacker that the object exists, but they can’t see it. A 404 just says it doesn’t exist, so less information is leaked and security is improved.

💡
It is important to note that in real-world applications, multiple users may need access to the same resource, such as a project file, a shared account, or support staff with delegated access. Authorization logic should account for all permitted users or roles, not only the primary owner. For instance, the repository query might consider shared permissions through a join table listing authorized users. This prevents unintentional exclusion of users who legitimately require access.

The Future of Security

Things are changing. Attackers now use AI bots to quickly find these flaws. To keep up, I think we’re moving toward:

  • Self-Healing APIs: Using AI agents as permanent "pen-testers" in our development process to catch anomalies in real-time.
  • Relationship-Based Access Control (ReBAC): Going beyond static roles to dynamic systems that look at context, such as the time of day or past behavior, before allowing access.

Wrap Up

Always verify user-data relationships before granting access. It’s simple in theory, but difficult to maintain across a large, complex codebase. This is essential to prevent BOLA vulnerabilities, which remain a common entry point for attackers.

Tomorrow, review your codebase and try changing the ID Parameter or a Query of an API endpoint. If you can access someone else’s data, your system may be vulnerable. I urge you to treat BOLA seriously—attackers frequently exploit it. Robust relationship checks are the first defense.

Update:

May 10, 2026: Added SKILL.md under docs/skills for BOLA Security Scan. You can use this with any tool to check whether the source code has a BOLA vulnerability and to find the right fix. Always use plan mode when you run any SKILLS from the internet. For even better safety, run it in a sandbox so you can be sure the SKILL is tested as expected.

May 11, 2026: After reading more technical news and research blogs, others have identified a likely root cause, but it is not yet fully confirmed. The attackers misused the Free-For-Teacher (FFT) program, which allowed teachers to create Canvas accounts without verifying that they actually belonged to a school. Because this signup process was so easy, it weakened the separation between FFT accounts and official school accounts, even though they all used the same underlying system.

References

(May 7, 2026). Canvas school login portals hacked as Instructure hack apparently gets even worse. TechRadar. https://www.techradar.com/pro/security/canvas-school-login-portals-hacked-as-instructure-hack-apparently-gets-even-worse

(2023). API1:2023 Broken Object Level Authorization. OWASP API Security Top 10. https://owasp.org/API-Security/editions/2023/en/0xa1-broken-object-level-authorization/(n.d.). BOLA: The API Vulnerability Hiding in Plain Sight. Snyk. https://snyk.io/articles/bola-the-api-vulnerability-hiding-in-plain-sight/

(2026). WSTG - Latest | OWASP Foundation. OWASP Foundation. https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/12-API_Testing/02-API_Broken_Object_Level_Authorization

(2026). API Security Testing in 2026: The 10 Checks Every API Needs Before Going to Production. BugHunterTools. https://bughuntertools.com/articles/api-security-testing-10-checks-2026/

(2026). Broken Object Level Authorization (BOLA) Vulnerability Detection. Cloudflare API Shield Docs. https://developers.cloudflare.com/api-shield/security/bola-vulnerability-detection/

(2026). Broken Object Level Authorization vulnerability detection. Cloudflare API Shield docs. https://developers.cloudflare.com/api-shield/security/bola-vulnerability-detection/

(2026). Spring Security. https://docs.spring.io/spring-security/reference/servlet/authorization/method-security.html

(2026). The Canvas Free-for-Teacher account might be the reason. https://www.rescana.com/post/shinyhunters-launches-second-major-attack-on-instructure-canvas-lms-via-free-for-teacher-accounts-may-2026-breach-analys/

(2026). The Canvas breach. https://www.jamf.com/blog/what-the-canvas-breach-tells-us-about-the-state-of-education-security/