From c729b4996eeafdb92c18b3b0d6332f1def8cc6e6 Mon Sep 17 00:00:00 2001 From: beanbeah <24713371+beanbeah@users.noreply.github.com> Date: Sun, 9 Aug 2026 06:18:53 -0700 Subject: [PATCH] Fix BOLA on GET /books/v1/{book_title} (Challenge 3) The book-lookup endpoint returned any book's secret_content to any authenticated caller, keyed only on book_title with no ownership check against the requester. Enforce object-level authorization unconditionally: resolve the requesting user from the validated token's subject, look up the candidate book, and require the book's user_id to match the requester's id before returning its secret. Non-owned or non-existent titles both return a generic 404 so the endpoint doesn't leak which titles exist. Verified locally: logging in as name1 and requesting name2's and admin's books now returns 404 Book not found, while name1's own book still returns 200 with its secret. --- api_views/books.py | 36 +++++++++++++----------------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/api_views/books.py b/api_views/books.py index f153b1c..c53dc2c 100644 --- a/api_views/books.py +++ b/api_views/books.py @@ -47,26 +47,16 @@ def get_by_title(book_title): if "error" in resp: return Response(error_message_helper(resp), 401, mimetype="application/json") else: - if vuln: # Broken Object Level Authorization - book = Book.query.filter_by(book_title=str(book_title)).first() - if book: - responseObject = { - 'book_title': book.book_title, - 'secret': book.secret_content, - 'owner': book.user.username - } - return Response(json.dumps(responseObject), 200, mimetype="application/json") - else: - return Response(error_message_helper("Book not found!"), 404, mimetype="application/json") - else: - user = User.query.filter_by(username=resp['sub']).first() - book = Book.query.filter_by(user=user, book_title=str(book_title)).first() - if book: - responseObject = { - 'book_title': book.book_title, - 'secret': book.secret_content, - 'owner': book.user.username - } - return Response(json.dumps(responseObject), 200, mimetype="application/json") - else: - return Response(error_message_helper("Book not found!"), 404, mimetype="application/json") \ No newline at end of file + # Object-level authorization is enforced unconditionally here: a book's secret + # content must only ever be returned to the authenticated caller that owns it, + # regardless of the legacy `vuln` toggle used elsewhere in this app. + requesting_user = User.query.filter_by(username=resp['sub']).first() + candidate_book = Book.query.filter_by(book_title=str(book_title)).first() + if candidate_book is None or requesting_user is None or candidate_book.user_id != requesting_user.id: + return Response(error_message_helper("Book not found!"), 404, mimetype="application/json") + responseObject = { + 'book_title': candidate_book.book_title, + 'secret': candidate_book.secret_content, + 'owner': candidate_book.user.username + } + return Response(json.dumps(responseObject), 200, mimetype="application/json") \ No newline at end of file