Fix BOLA (Broken Object Level Authorization) in Ktor
BOLA (Broken Object Level Authorization), formerly known as IDOR, remains the most critical vulnerability in modern API architectures. In Ktor, this occurs when a route handler processes an object identifier from the request path or body without verifying that the authenticated principal has the requisite permissions to access that specific resource. Attackers exploit this by iterating IDs to scrape data or manipulate records belonging to other users.
The Vulnerable Pattern
get("/api/invoices/{id}") {
val id = call.parameters["id"]?.toInt() ?: return@get call.respond(HttpStatusCode.BadRequest)
// VULNERABLE: Only checks if the object exists, not who owns it
val invoice = db.findInvoiceById(id)
if (invoice == null) {
call.respond(HttpStatusCode.NotFound)
} else {
call.respond(invoice)
}
}
The Secure Implementation
The fix shifts authorization logic from 'Does this object exist?' to 'Does this object belong to the requester?'. By extracting the 'user_id' directly from the cryptographically signed JWTPrincipal, we establish a trusted identity. The database layer is then modified to include this 'userId' in the 'WHERE' clause of the query. This ensures that even if an attacker guesses a valid 'invoiceId', the application layer will fail to retrieve the record because the ownership check fails at the data access level.
authenticate("auth-jwt") { get("/api/invoices/{id}") { val principal = call.principal() val userId = principal?.payload?.getClaim("user_id")?.asInt() ?: return@get call.respond(HttpStatusCode.Unauthorized) val invoiceId = call.parameters["id"]?.toInt() ?: return@get call.respond(HttpStatusCode.BadRequest) // SECURE: Query explicitly filters by the authenticated user's ID val invoice = db.findInvoiceForUser(invoiceId, userId) if (invoice == null) { // Use 404 to avoid leaking resource existence to unauthorized parties call.respond(HttpStatusCode.NotFound) } else { call.respond(invoice) } }
}
About this page
Framework notes in /guides are generated sketches kept for URL stability. They are not human pentest reports and they are not GuardAPI scan output. The product is a GET-only BOLA merge gate. Maintained by GuardAPI. Questions: [email protected]