Skip to content

CVE-2026-83538: Apache OFBiz Unauthenticated Disclosure via Manager Helper Routes

TL;DR

  • An unauthenticated attacker could read organisation charts, employee names, hidden product categories and store-group hierarchies from four manager helper routes.
  • getHRChild returned organisation names and IDs, active positions, and the names and Party IDs of the employees filling them.
  • Catalog Manager exposed product-category and store-group trees. Order Manager's quickadd returned category descriptions, image URLs and active-membership status.
  • Each tree response supplied identifiers for the next request, so one known root could expose the hierarchy beneath it.
  • CVE-2026-83538 affects Apache OFBiz before 24.09.08 and is fixed in 24.09.08, which puts all four routes behind a login and adds a permission check inside the three tree handlers.

Summary

  • CVE: CVE-2026-83538
  • Product: Apache OFBiz (Java ERP suite)
  • Vulnerability: Unauthenticated Information Disclosure via Manager Helper Routes
  • Affected Versions: < 24.09.08
  • Fixed In: 24.09.08
  • Severity: Moderate (per Apache)
  • CVSS Severity: 5.3 (medium, reporter-assessed)
  • Required Privilege: None
  • Advisory: Apache OFBiz CVE Announcements
  • Reported: August 2, 2026
  • NVD Published: November 5, 2026

Introduction

OFBiz manager applications draw their navigation trees with jsTree, which means the tree you see on screen is not rendered server-side in one go. The page loads a root, and every time you expand a node the browser calls a small helper endpoint that answers with JSON for that node's children.

The screens around those trees take permissions seriously. Human Resources will not render its organisation tree without HUMANRES_VIEW, and Catalog Manager guards its body with a catalog permission check before it draws anything.

The helper endpoints that fill the tree are declared separately, further down the same controller file, and four of them say auth="false".

Root Cause Analysis

The excerpts below are from the tested release24.09 snapshot at commit b5ca713e.

The Endpoint Behind the Permission Check

Human Resources gates its main screen on the view permission, and the organisation tree sits inside that gate.

<!-- applications/humanres/widget/CommonScreens.xml -->
<section>
    <condition>
        <if-has-permission permission="HUMANRES" action="_VIEW"/>
    </condition>
    <widgets>
        <container style="lefthalf">
            <include-screen name="OrgTree"/>
        </container>
    </widgets>
</section>

Expanding a node in the gated Human Resources screen calls a different route.

<!-- applications/humanres/webapp/humanres/WEB-INF/controller.xml -->
<request-map uri="getHRChild">
    <security auth="false" https="true"/>
    <event type="java" path="org.apache.ofbiz.humanres.HumanResEvents" invoke="getChildHRCategoryTree"/>
    <response name="success" type="request" value="json"/>
    <response name="error" type="request" value="json"/>

That route's handler mapping points to the code below.

// applications/humanres/src/main/java/org/apache/ofbiz/humanres/HumanResEvents.java
public static String getChildHRCategoryTree(HttpServletRequest request, HttpServletResponse response) {
    Delegator delegator = (Delegator) request.getAttribute("delegator"); // [1] Request delegator.
    String partyId = request.getParameter("partyId"); // [2] Caller-supplied root.
    // ... presentation parameters omitted; they only shape the JSON.
    //check employee position
    try {
        categoryList.addAll(getCurrentEmployeeDetails(paramMap)); // [3] Employee detail query.

In HumanResEvents.getChildHRCategoryTree(), the handler gets the delegator at [1]. It never reads a security object, userLogin or a permission service. [2] takes the identifier directly from the request. [3] follows the current EmplPosition and EmplPositionFulfillment records, then adds each fulfilling party's ID and formatted name to the response. Further down, the handler does the same for GROUP_ROLLUP children and active positions.

An anonymous caller can render the tree without loading the protected page.

The Same Shape in Catalog Manager

Two more routes in the Catalog controller are declared the same way.

<!-- applications/product/webapp/catalog/WEB-INF/controller.xml -->
<request-map uri="getProductStoreGroupRollupHierarchy">
    <security auth="false" https="true"/>
    <event type="java" path="org.apache.ofbiz.product.store.ProductStoreEvents" invoke="getChildProductStoreGroupTree"/>
    <response name="success" type="request" value="json"/>
    <response name="error" type="request" value="json"/>
<!-- applications/product/webapp/catalog/WEB-INF/controller.xml -->
<request-map uri="getChild">
    <security auth="false" https="true"/>
    <event type="java" path="org.apache.ofbiz.product.category.CategoryServices" invoke="getChildCategoryTree"/>
    <response name="success" type="request" value="json"/>
    <response name="error" type="request" value="json"/>

The store-group and category routes have handlers that open exactly like the HR one.

// applications/product/src/main/java/org/apache/ofbiz/product/category/CategoryServices.java
public static String getChildCategoryTree(HttpServletRequest request, HttpServletResponse response) {
    Delegator delegator = (Delegator) request.getAttribute("delegator");
    String productCategoryId = request.getParameter("productCategoryId"); // [4] Caller-supplied category.
    String isCatalog = request.getParameter("isCatalog");
    String isCategoryType = request.getParameter("isCategoryType");

In CategoryServices.getChildCategoryTree(), the parameter at [4] selects a ProductCategory or a ProdCatalog, and the response carries each related category's ID, localised name, sequence number and whether it has children of its own. showInSelect=N controls picker visibility, not read access, so hidden categories appear with everything else.

Order Manager Answers a Different Question

Order Manager's quickadd route and view are public, and the script behind them takes a category ID from the request.

// applications/order/src/main/groovy/org/apache/ofbiz/order/entry/catalog/QuickAdd.groovy
currentCatalogId = CatalogWorker.getCurrentCatalogId(request) // [5] Current catalog lookup.
categoryId = parameters.category_id ?: CatalogWorker.getCatalogQuickaddCategoryPrimary(request)
// ... quick-add category list omitted.
if (categoryId) {
    fields = [productCategoryId: categoryId, defaultViewSize: 10,
              limitView: false, prodCatalogId: currentCatalogId, checkViewAllow: true] // [6] Catalog-dependent check.
    result = runService('getProductCategoryAndLimitedMembers', fields)
    // ... result copied into the render context.
    productCategory = from('ProductCategory').where('productCategoryId', categoryId).queryOne() // [7] Direct category read.

In QuickAdd.groovy, a fresh anonymous Order Manager session has no website or product store from which to resolve a catalog at [5], so currentCatalogId is null. The access check at [6] runs only when prodCatalogId is non-null. [7] reads the category either way, so the template gets its name, ID, image URL and long description.

The member list decides one more thing. The template renders a bulk-add form when the category has current members and a no-products message when it does not, which answers whether a category is actively stocked. The membership check filters membership dates. It ignores product introduction and release dates, so a category containing an unannounced product still answers yes.

Pair that with the category tree and the two routes complement each other. getChild gives up a hidden category's ID, and quickadd turns that ID into its description.

Impact

An unauthenticated attacker can recover organisation names and IDs, position titles, employee names and Party IDs. Catalog Manager also exposes category and store-group names, IDs and hierarchy, including categories flagged as hidden from selection, their long descriptions and whether they are stocked.

The tree routes need a root identifier with children, and a wrong guess looks much like a leaf. Each successful response supplies the IDs for the next level, while records created without an identifier use numeric values starting at 10000. The endpoints had no rate limiting.

Exploitation

Preconditions

  • Apache OFBiz before 24.09.08 with Human Resources, Catalog Manager or Order Manager reachable over the network.
  • A starting identifier, either a known one or a numeric range to scan.

The routes answer an ordinary anonymous session. Where the stronger CSRF strategy is enabled, the client first takes the AJAX token from the login response, which any unauthenticated caller is issued.

PoC

The PoC queries the hr, category, store-group or category-detail surface with one identifier or a numeric --scan range.

python3 cve_2026_83538.py https://target.example:8443 hr Company
  ___  _____ ____ ___ _____
 / _ \|  ___| __ )_ _|__  /
| | | | |_  |  _ \| |  / /
| |_| |  _| | |_) | | / /_
 \___/|_|   |____/___/____|
  unauthenticated manager disclosure  ·  CVE-2026-83538
  discovered by CryptoCat · cryptocat.me

 [*] Surface: Human Resources organisation and employee tree
 [*] /humanres/control/main returned HTTP 401
 [+] anonymous AJAX CSRF token obtained
 [+] disclosed 1 protected record(s) without authentication
  ------------------------------------------------------------

The PoC prints the full hrTree array. This abridged response shows the fields used to continue the walk.

{
  "hrTree": [
    {
      "title": "Finance and Treasury",
      "id": "HRSEC_FINANCE",
      "rel": "Y"
    }
  ]
}

Each jsTree node nests its label and ID under data and attr. HRSEC_FINANCE returned the payroll sub-organisation and a CFO position, and that position returned the fulfilling employee. The login route answered HTTP 401 while still issuing the anonymous session used for the requests.

Catalog Manager gives up categories flagged as hidden from selection through the category surface, and feeding a disclosed category ID to the category-detail surface returns the detail behind it.

{
  "categoryDetails": [
    {
      "categoryId": "QA_AVAIL_CAT_260802",
      "categoryName": "Restricted Operations Supplies",
      "categoryImageUrl": null,
      "longDescription": "CONFIDENTIAL: internal-only emergency operations supplies and negotiated pricing.",
      "hasActiveMembers": true
    }
  ]
}

Patch Diffing

Apache fixed this in two commits. The three tree routes move to auth="true" and gain a permission check inside their handlers. quickadd instead has authentication restored on its request map and its view. Both commits are ancestors of the final 24.09.08 release tag.

Human Resources went first, in 5b2870c7d4.

// applications/humanres/webapp/humanres/WEB-INF/controller.xml (getHRChild)
 <request-map uri="getHRChild">
-    <security auth="false" https="true"/>
+    <security auth="true" https="true"/>
     <event type="java" path="org.apache.ofbiz.humanres.HumanResEvents" invoke="getChildHRCategoryTree"/>

The route now needs a login, and the handler no longer takes that on trust.

// applications/humanres/src/main/java/org/apache/ofbiz/humanres/HumanResEvents.java (getChildHRCategoryTree)
 public static String getChildHRCategoryTree(HttpServletRequest request, HttpServletResponse response) {
+    Security security = (Security) request.getAttribute("security");
+    if (security == null || !security.hasEntityPermission("HUMANRES", "_VIEW", request.getSession())) {
+        String errMsg = UtilProperties.getMessage(RES_ERROR, "HumanResViewPermissionError", UtilHttp.getLocale(request));
+        request.setAttribute("_ERROR_MESSAGE_", errMsg);
+        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+        Debug.logWarning("Unauthorized attempt to access getHRChild by partyId param [%s]", MODULE, request.getParameter("partyId"));
+        return "error";
+    }
     Delegator delegator = (Delegator) request.getAttribute("delegator");

HUMANRES_VIEW is the permission the screen already required, so the endpoint and the page it fills now agree. An anonymous request still arrives with a security object, so the refusal comes from hasEntityPermission returning false. The null check is fail-closed defence on top.

The Catalog and Order Manager routes followed in 764f989972, with getChild and getProductStoreGroupRollupHierarchy moving to auth="true" and picking up the same guard against CATALOG_VIEW. quickadd lost its exemption in both places it had one.

// applications/order/webapp/ordermgr/WEB-INF/controller.xml (quickadd)
 <request-map uri="quickadd">
-    <security https="false" auth="false"/>
+    <security https="true" auth="true"/>
     <response name="success" type="view" value="quickadd"/>
 </request-map>
// applications/order/webapp/ordermgr/WEB-INF/controller.xml (view-map)
-<view-map name="quickadd" type="screen" page="component://order/widget/ordermgr/OrderEntryCatalogScreens.xml#quickadd" auth="false"/>
+<view-map name="quickadd" type="screen" page="component://order/widget/ordermgr/OrderEntryCatalogScreens.xml#quickadd"/>

Both halves needed changing. Leaving auth="false" on the view would have kept it reachable through any request map that can select it, which is the same weakness CVE-2026-73577 turns into a bypass.

Remediation

Upgrade to Apache OFBiz 24.09.08 or later.

Grep your controller files for auth="false", then review every matching route that accepts a record identifier and returns data.

Disclosure Timeline

  • August 2, 2026: Reported four unauthenticated manager helper routes to the Apache OFBiz security team.
  • August 13, 2026: Apache committed the Human Resources fix.
  • August 25, 2026: Apache committed the Catalog Manager and Order Manager fixes.
  • August 31, 2026: Apache assigned CVE-2026-83538 and shared the draft advisory.
  • November 3, 2026: Apache announced OFBiz 24.09.08, the first released version containing both fixes.
  • November 4, 2026: Apache publicly announced CVE-2026-83538 in its consolidated OFBiz CVE notice.
  • November 5, 2026: CVE.org and NVD published CVE-2026-83538.

Conclusion

The four helper routes exposed data without enforcing the permissions used by their manager screens. Apache now requires a login on all four and checks the matching permission inside the three tree handlers.

References