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