The document controller's uploadDocument action used a flawed validation mechanism to prevent template-type documents from being uploaded to stock movements, shipments, purchase orders, and invoices. While the validation intended to restrict these uploads, it relied on DocumentService.getNonTemplateDocumentTypes() to return DocumentType domain objects and used .contains() to check whether the uploaded document type was in the non-template list.
This allowed an authenticated user with document upload access to bypass the validation and successfully attach forbidden template-type documents to restricted records due to object equality issues with Hibernate entities, dynamic service dependencies, or inverted validation logic.
| Title | CWE | Affected Versions | Fixed In | Patch Date |
|---|---|---|---|---|
| Template Document Type Upload Validation Bypass via Object Equality Flaw | CWE-434: Unrestricted Upload of File with Dangerous Type | < v0.9.2 | v0.9.2 (PR #4764, commit e945d6bfc) |
2024-07-23 |
Vector String: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N
| 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 with document upload access |
| User Interaction (UI) | None | No user interaction needed |
| Scope (S) | Unchanged | Impact within application boundary |
| Confidentiality (C) | None | No direct data disclosure |
| Integrity (I) | High | Can upload template documents where forbidden |
| Availability (A) | None | No direct availability impact |
- Files:
grails-app/controllers/org/pih/warehouse/core/DocumentController.groovy - Endpoints:
POST /openboxes/document/uploadDocument
In DocumentController.groovy, the validation check relied on object equality of domain entities, and the stock movement lookup was performed unconditionally without a null check:
// DocumentController.groovy (pre-patch, lines 225-227)
List<DocumentType> nonTemplateDocumentTypes = documentService.getNonTemplateDocumentTypes()
if (!nonTemplateDocumentTypes.contains(documentType)) {
documentInstance.errors.reject("documentType", "Template types are not allowed for this document upload")
}
// Unconditional lookup causing potential NPE
def stockMovement = outboundStockMovementService.getStockMovement(command.stockMovementId)
if (!stockMovement) {
stockMovement = stockMovementService.getStockMovement(command.stockMovementId)
}
This approach failed reliably because Hibernate entity equality can break across different sessions or when entities are proxied, allowing the .contains() check to pass incorrectly.
The fix refactored the validation logic to use deterministic enum values and added proper defensive guards:
// DocumentController.groovy (post-patch)
List<DocumentCode> forbiddenDocumentCodes = DocumentCode.templateList()
if (documentType && forbiddenDocumentCodes.contains(documentType.documentCode)) {
documentInstance.errors.reject("documentType", "Template types are not allowed for this document upload")
}
The validation now explicitly checks an immutable list of forbidden enum values (DocumentCode), introduces a null guard around documentType, and makes the stock movement lookup conditional to prevent null pointer exceptions.
- OpenBoxes instance running a version before commit
e945d6bfc(pre-July 2024). - An authenticated user account with access to document upload functionality.
- An existing Shipment, Stock Movement, Purchase Order, or Invoice record (note its ID).
- Knowledge of the database ID for a template-type
DocumentType(e.g., a row wheredocumentCodeisZEBRA_TEMPLATEorSHIPPING_TEMPLATE).
Automated Testing: Run poc.py <url> <user> <password> to automatically:
- Locate a stock movement and document types.
- Attempt a template document type upload.
- Verify the validation bypass.
Manual Steps:
- Identify a template DocumentType ID. Query the database or inspect the document type configuration in the UI to find the internal ID of a template document type.
- Navigate to the target document upload context. In the browser, open a valid stock movement or shipment page:
https://<host>/openboxes/stockMovement/show/<STOCK_MOVEMENT_ID>
Click the button to open the "Add Document" interface. 3. Intercept the upload request.
Configure your browser developer tools (Network tab) or a proxy tool like Burp Suite to intercept outgoing requests. 4. Modify the request parameters.
Initiate a standard file upload, intercept the payload, and replace the documentType.id parameter value with the template document type ID identified in step 1. 5. Submit and analyze the response.
Expected (pre-patch): The server accepts the upload and successfully attaches the template-type document to the record because the Hibernate object equality check fails to identify it as forbidden.
Expected (post-patch): The server rejects the submission with the error message "Template types are not allowed for this document upload". 6. Verify the uploaded document (pre-patch only).
Refresh the document tab for the target entity to confirm that the template file is listed and linked.
If the targeted application instance is also unpatched against the Server-Side Template Injection (SSTI) flaw resolved in PR #5943, this upload bypass serves as the initial entry point. An attacker can upload a template file containing malicious Groovy code. When the system subsequently processes and renders this template (such as generating a Zebra label), the GroovyPagesTemplateEngine will execute the embedded code, leading to remote code execution (RCE).
Upgrade to version >= v0.9.2
| Date | Event |
|---|---|
| 2024-07-23 | Patch merged as PR #4764 (no security advisory) |
- CWE: CWE-434: Unrestricted Upload of File with Dangerous Type
- PR: openboxes/openboxes#4764
- Commit:
e945d6bfcec29642f514e7d298dfba2cc6cd7cd4 - Release: v0.9.2
- Files Changed:
DocumentController.groovy - Related: PR #5943 (SSTI in Zebra template rendering) - forms an attack chain with this vulnerability