• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 9+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » How We Audited a High-Traffic Ruby Enterprise Stack on OVH and Mitigated Broken Object Level Authorization (BOLA) in API gateway endpoints

How We Audited a High-Traffic Ruby Enterprise Stack on OVH and Mitigated Broken Object Level Authorization (BOLA) in API gateway endpoints

Understanding the Threat: Broken Object Level Authorization (BOLA)

Broken Object Level Authorization (BOLA) is a critical security vulnerability where an authenticated user can access, modify, or delete resources they are not authorized to interact with. In a high-traffic enterprise environment, particularly one leveraging a microservices architecture and an API gateway, BOLA can have devastating consequences, ranging from data breaches to service disruptions. Our recent audit of a Ruby-based enterprise stack hosted on OVH revealed several instances of BOLA, primarily stemming from insufficient authorization checks at the API gateway level and within individual microservices.

Audit Methodology: From OVH Infrastructure to API Endpoints

Our audit began with a comprehensive review of the OVH infrastructure, focusing on network segmentation, firewall rules, and access control mechanisms. While the OVH environment itself was robust, the primary attack surface was identified as the API gateway, which served as the single entry point for all client requests to our Ruby microservices. We employed a multi-pronged approach:

  • Infrastructure Scan: Utilized OVH’s native tools and third-party vulnerability scanners to identify misconfigurations in load balancers, firewalls, and server access.
  • API Gateway Analysis: Deep-dived into the API gateway’s configuration (we were using Kong at the time) to scrutinize authentication and authorization plugins, rate limiting, and request routing logic.
  • Code Review (Ruby Microservices): Performed static and dynamic analysis of critical Ruby microservices, focusing on endpoints that handled sensitive data or performed state-changing operations.
  • Penetration Testing: Conducted targeted penetration tests simulating various user roles and attack vectors to actively exploit potential BOLA vulnerabilities.

Identifying BOLA in the API Gateway Layer

The API gateway is the first line of defense. If authorization checks are weak here, even strong authentication is rendered moot. We found that while our API gateway (Kong) was configured to authenticate users via JWTs, the authorization logic for specific resource access was often delegated entirely to the downstream microservices. This created a gap where a user with a valid JWT could potentially craft requests to access resources belonging to other users if the microservice didn’t perform its own rigorous checks.

A common pattern we observed was the use of generic endpoints like /users/{user_id}/orders. The gateway would authenticate the JWT, extract the `user_id` from the token, and pass the request to the orders microservice. If the orders microservice then blindly used the `user_id` from the JWT to fetch orders without verifying if the authenticated user *actually owned* that `user_id`, BOLA would occur.

Example: Kong Gateway Configuration Gap

Consider a simplified Kong configuration snippet. The JWT validation plugin is active, but it doesn’t enforce object-level ownership:

# kong.conf (simplified)
plugins = jwt-authenticator, acl, rate-limiting

# JWT Authenticator configuration (example)
jwt_authenticator.anonymous = false
jwt_authenticator.keys = ...
jwt_authenticator.header_name = Authorization
jwt_authenticator.algorithm = RS256

# ACL plugin configuration (example)
acl.allow_any_authenticated = true # This is the problematic setting for BOLA

In this scenario, the acl.allow_any_authenticated = true setting, while convenient, bypasses granular access control at the gateway. The responsibility is pushed entirely to the backend services.

Mitigation Strategy 1: Enhancing API Gateway Authorization

The most effective mitigation is to implement authorization checks as early as possible, ideally at the API gateway. For Kong, this involves leveraging custom plugins or more sophisticated configurations. We opted for a combination of Kong’s built-in ACLs (configured more restrictively) and a custom Lua plugin for fine-grained checks.

Implementing Custom Lua Plugin for BOLA Checks

We developed a custom Lua plugin for Kong that intercepts requests *after* JWT authentication but *before* routing to the upstream service. This plugin inspects the request path and parameters, compares the authenticated user’s ID (extracted from the JWT claims) with the resource owner ID specified in the request, and denies the request if they don’t match.

-- /usr/local/share/lua/5.1/kong/plugins/bola-checker/handler.lua

local log = require "kong.tools.log"
local http = require "kong.http"
local json = require "kong.tools.json"

local BolaChecker = {}

-- Mapping of resource types to their corresponding JWT claim/request parameter for owner ID
-- This needs to be dynamically managed or configured.
local resource_owner_mapping = {
    orders = { jwt_claim = "user_id", path_param_index = 2 }, -- e.g., /users/{user_id}/orders/{order_id}
    accounts = { jwt_claim = "account_id", path_param_index = 1 }, -- e.g., /accounts/{account_id}/details
    -- Add more resource types as needed
}

function BolaChecker.new()
    return BolaChecker
end

function BolaChecker:access(conf)
    local req = ngx.req
    local uri = req.get_uri_args()
    local headers = req.get_headers()
    local jwt_token = headers["authorization"] or headers["Authorization"]

    if not jwt_token or not jwt_token:match("^Bearer ") then
        return ngx.exit(401) -- Unauthorized: No JWT
    end

    -- Assuming JWT is validated by another plugin and claims are available in ngx.ctx
    local claims = ngx.ctx.jwt_claims
    if not claims then
        log.err("JWT claims not found in ngx.ctx")
        return ngx.exit(500) -- Internal Server Error
    end

    local path_parts = ngx.req.get_path_parts()
    -- path_parts might look like: {"users", "123", "orders", "456"}

    for resource_type, mapping in pairs(resource_owner_mapping) do
        -- Simple check: if the path contains the resource type and has enough parts
        for i = 1, #path_parts do
            if path_parts[i] == resource_type then
                if #path_parts >= mapping.path_param_index + 1 then -- Ensure parameter exists
                    local requested_owner_id = path_parts[mapping.path_param_index]
                    local authenticated_user_id = claims[mapping.jwt_claim]

                    if not requested_owner_id or not authenticated_user_id then
                        log.warn("Missing owner ID or authenticated user ID for resource: ", resource_type)
                        return ngx.exit(400) -- Bad Request
                    end

                    if requested_owner_id ~= authenticated_user_id then
                        log.warn("BOLA detected: User ", authenticated_user_id, " attempting to access resource of ", requested_owner_id)
                        return ngx.exit(403) -- Forbidden
                    end
                    -- If we found a match and it's authorized, we can break this inner loop
                    -- and continue to the next request phase.
                    goto continue_next_phase
                end
            end
        end
    end

    ::continue_next_phase::
    return ngx.OK
end

return BolaChecker

To deploy this, you would typically compile it into Kong and then configure a route to use this plugin. The resource_owner_mapping would need to be carefully defined to match your API’s URL structure and the claims present in your JWTs. This requires a deep understanding of your API’s resource hierarchy and how user identifiers are represented.

Mitigation Strategy 2: Robust Authorization in Ruby Microservices

While offloading authorization to the gateway is ideal, it’s crucial that microservices also implement their own authorization checks. This provides a defense-in-depth strategy. If the gateway is compromised or a direct service-to-service call bypasses the gateway, the microservice must still protect its resources.

Example: Ruby on Rails Controller Logic

In a typical Rails application, authorization checks should be performed within controllers or dedicated policy objects.

# app/controllers/api/v1/orders_controller.rb

class Api::V1::OrdersController < ApplicationController
  before_action :authenticate_user!
  before_action :set_order, only: [:show, :update, :destroy]
  before_action :authorize_order_owner!, only: [:show, :update, :destroy]

  def show
    # If we reach here, the order is authorized for the current user
    render json: @order
  end

  # ... other actions

  private

  def set_order
    # Fetch the order, potentially using a more specific query if available
    # Example: If user_id is available from JWT claims in `current_user.id`
    @order = Order.where(user_id: current_user.id).find_by(id: params[:id])
    render json: { error: "Order not found" }, status: :not_found unless @order
  end

  def authorize_order_owner!
    # This check is redundant if set_order already filters by user_id,
    # but serves as an explicit safeguard.
    unless @order.user_id == current_user.id
      render json: { error: "Unauthorized access to this order" }, status: :forbidden
    end
  end

  # Assuming `current_user` is set by `authenticate_user!`
  # and `authenticate_user!` populates `current_user` with the authenticated user's details,
  # including their ID, from the JWT claims.
end

In this Rails example, set_order is modified to *only* fetch orders belonging to the `current_user`. This is a crucial BOLA mitigation. The authorize_order_owner! is a belt-and-suspenders approach, ensuring that even if set_order had a bug, this explicit check would catch it. The key is ensuring that `current_user` is correctly populated with the authenticated user’s identity and that all resource fetches are scoped by this identity.

Testing and Validation

Post-implementation, rigorous testing is paramount. We used a combination of automated tests and manual verification:

  • Automated API Tests: Developed integration tests using tools like Postman or custom scripts (e.g., Python with requests) to simulate requests from different user roles, attempting to access resources they shouldn’t.
  • Manual Penetration Testing: Engaged our security team to perform black-box and grey-box testing, actively trying to bypass the implemented controls.
  • Log Analysis: Monitored API gateway logs and microservice application logs for any denied requests or suspicious activity patterns that might indicate attempted BOLA exploits.

Example: Python Script for BOLA Testing

import requests
import json

BASE_URL = "https://api.your-enterprise.com"
USER_A_TOKEN = "eyJhbGciOiJSUzI1Ni..." # JWT for User A
USER_B_TOKEN = "eyJhbGciOiJSUzI1Ni..." # JWT for User B

USER_A_ORDER_ID = "order_123"
USER_B_ORDER_ID = "order_456"

def get_order(user_token, order_id):
    headers = {
        "Authorization": f"Bearer {user_token}",
        "Content-Type": "application/json"
    }
    try:
        response = requests.get(f"{BASE_URL}/api/v1/orders/{order_id}", headers=headers)
        return response
    except requests.exceptions.RequestException as e:
        print(f"Request failed: {e}")
        return None

# Test Case 1: User A accessing their own order (should succeed)
print(f"--- Testing User A accessing Order {USER_A_ORDER_ID} ---")
response_a_own = get_order(USER_A_TOKEN, USER_A_ORDER_ID)
if response_a_own:
    print(f"Status Code: {response_a_own.status_code}")
    if response_a_own.status_code == 200:
        print("Success: User A accessed their own order.")
    else:
        print("Failure: User A could not access their own order.")
else:
    print("Request failed.")

print("\n" + "="*30 + "\n")

# Test Case 2: User A accessing User B's order (should fail with 403 Forbidden)
print(f"--- Testing User A accessing Order {USER_B_ORDER_ID} ---")
response_a_other = get_order(USER_A_TOKEN, USER_B_ORDER_ID)
if response_a_other:
    print(f"Status Code: {response_a_other.status_code}")
    if response_a_other.status_code == 403:
        print("Success: User A was correctly denied access to User B's order.")
    elif response_a_other.status_code == 404:
        print("Warning: User A received 404 instead of 403. This might indicate information leakage.")
    else:
        print(f"Failure: User A unexpectedly accessed User B's order (Status: {response_a_other.status_code}).")
else:
    print("Request failed.")

print("\n" + "="*30 + "\n")

# Test Case 3: User B accessing User A's order (should fail with 403 Forbidden)
print(f"--- Testing User B accessing Order {USER_A_ORDER_ID} ---")
response_b_other = get_order(USER_B_TOKEN, USER_A_ORDER_ID)
if response_b_other:
    print(f"Status Code: {response_b_other.status_code}")
    if response_b_other.status_code == 403:
        print("Success: User B was correctly denied access to User A's order.")
    elif response_b_other.status_code == 404:
        print("Warning: User B received 404 instead of 403. This might indicate information leakage.")
    else:
        print(f"Failure: User B unexpectedly accessed User A's order (Status: {response_b_other.status_code}).")
else:
    print("Request failed.")

This Python script automates the process of verifying that a user can only access their own resources. It’s crucial to have a comprehensive suite of such tests covering all sensitive endpoints and resource types.

Conclusion: A Layered Defense Against BOLA

Auditing and securing a high-traffic enterprise stack on a platform like OVH requires a meticulous approach. Broken Object Level Authorization is a pervasive threat that can be mitigated effectively through a layered security strategy. By enhancing the API gateway’s authorization capabilities with custom logic and ensuring that individual microservices rigorously enforce ownership checks, we significantly reduced our attack surface. Continuous monitoring, automated testing, and regular security audits are essential to maintain a strong security posture against evolving threats.

Primary Sidebar

A little about the Author

Having 9+ Years of Experience in Software Development.
Expertised in Php Development, WordPress Custom Theme Development (From scratch using underscores or Genesis Framework or using any blank theme or Premium Theme), Custom Plugin Development. Hands on Experience on 3rd Party Php Extension like Chilkat, nSoftware.

Recent Posts

  • Step-by-Step: Diagnosing thread pools deadlock during concurrent ActiveRecord transaction processing on Linode Servers
  • Securing Your E-commerce APIs: Preventing SQL Injection (SQLi) in customized checkout queries in WooCommerce Implementations
  • Disaster Recovery 101: Architecting Auto-Failovers for MySQL and Ruby Deployments on Linode
  • High-Throughput Caching Strategies: Scaling MySQL for Perl Application APIs
  • Disaster Recovery 101: Architecting Auto-Failovers for DynamoDB and Laravel Deployments on DigitalOcean

Copyright © 2026 · Vinay Vengala