flawopen.com/bola-idor/Java

● CWE-639 · 緊急
セキュリティ研究 · FlawOpen

脆弱性の解説:Broken Object Level Authorization (BOLA / IDOR) in Java Spring Boot

Eliminate BOLA (CWE-639) / IDOR in Java Spring Boot APIs by binding Spring Data JPA repository queries directly to authenticated security principal context.

💡 わかりやすい解説 (ELI5)

Imagine checking into Room 204 at a hotel and receiving a valid keycard. But you discover that if you scratch out '204' on the door handle and write '205', the lock clicks open and allows you to rummage through another guest's luggage. The hotel confirmed you were a registered guest (authentication), but never checked whether your key matched Room 205 (object authorization). In BOLA/IDOR, an attacker changes an ID in an API request and accesses another user's private records.

主要な概念と専門用語

Authentication vs. Authorization
Authentication proves identity. Authorization confirms whether the user has rights to access a specific entity.
BOLA (OWASP API #1)
Exposing resource identifiers without ownership checks, enabling cross-tenant data access.
Spring Security PreAuthorize
Method-level security evaluating SpEL expressions against the authenticated principal.

ステップ・バイ・ステップの攻撃フロー

Step 1

User Authentication

User signs into Spring Boot application via OAuth2/JWT.

Step 2

Object Request

Client issues GET /api/v1/invoices/{id} to retrieve billing statement.

Step 3

Parameter Manipulation

Attacker supplies an ID belonging to another company.

Step 4

Repository Fetch

Controller calls invoiceRepository.findById(id) without tenant organization filtering.

Step 5

Unauthorized Access

Controller returns another company's invoice with sensitive billing data.

ソースコード比較:脆弱 vs 堅牢化

✕ 脆弱な実装
// VULNERABLE: Spring Data repository lookup by ID alone
@RestController
@RequestMapping("/api/invoices")
public class InvoiceController {

    @Autowired
    private InvoiceRepository invoiceRepository;

    @GetMapping("/{id}")
    public ResponseEntity<Invoice> getInvoice(@PathVariable Long id) {
        // Vulnerable: finds by primary key without checking tenant ownership!
        return invoiceRepository.findById(id)
            .map(ResponseEntity::ok)
            .orElse(ResponseEntity.notFound().build());
    }
}
✓ 堅牢化されたセキュアパッチ
// HARDENED: Query is strictly filtered by BOTH invoice ID and authenticated tenant organization
@RestController
@RequestMapping("/api/invoices")
public class InvoiceController {

    @Autowired
    private InvoiceRepository invoiceRepository;

    @GetMapping("/{id}")
    public ResponseEntity<Invoice> getInvoice(@PathVariable Long id, @AuthenticationPrincipal UserPrincipal principal) {
        // Defense-in-depth: repository query strictly enforces organization boundary
        return invoiceRepository.findByIdAndOrganizationId(id, principal.getOrganizationId())
            .map(ResponseEntity::ok)
            // Return 404 rather than 403 to prevent ID enumeration
            .orElse(ResponseEntity.notFound().build());
    }
}

エンジニアリング&システム堅牢化チェックリスト

References