Improved: Sanitize all widget xml resource loading - #1586
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces a centralized sanitizer (WidgetSecureLocation) intended to harden widget XML resource loading by only allowing component:// locations (without traversal) or explicitly permitted filesystem paths via security.properties.
Changes:
- Added
WidgetSecureLocationand integrated it into multiple widget factories before resolving/loading XML resources. - Added a new default-deny
allowFilePathsproperty and implementedUtilValidate.isAllowedPath(...)to enforce it. - Updated one screen include to use
component://...instead of a relative path.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| framework/widget/src/main/java/org/apache/ofbiz/widget/model/WidgetSecureLocation.java | Adds a centralized location sanitizer for widget resource loading. |
| framework/widget/src/main/java/org/apache/ofbiz/widget/model/TreeFactory.java | Uses sanitizer before resolving/loading tree XML resources. |
| framework/widget/src/main/java/org/apache/ofbiz/widget/model/ScreenFactory.java | Uses sanitizer for referenced screen includes. |
| framework/widget/src/main/java/org/apache/ofbiz/widget/model/MenuFactory.java | Uses sanitizer before resolving/loading menu XML resources. |
| framework/widget/src/main/java/org/apache/ofbiz/widget/model/GridFactory.java | Uses sanitizer before resolving/loading grid XML resources. |
| framework/widget/src/main/java/org/apache/ofbiz/widget/model/FormFactory.java | Uses sanitizer before resolving/loading form XML resources. |
| framework/security/config/security.properties | Introduces allowFilePaths configuration knob (default blank). |
| framework/base/src/main/java/org/apache/ofbiz/base/util/UtilValidate.java | Adds isAllowedPath and changes URL detection behavior used by loaders/sanitizer. |
| applications/commonext/widget/ofbizsetup/ProfileScreens.xml | Migrates include-screen locations to component://.... |
Suppressed comments (4)
framework/widget/src/main/java/org/apache/ofbiz/widget/model/TreeFactory.java:65
- After resolving a sanitized component:// location,
treeFileUrl.toString()is usuallyfile:/.... With the currentisUrlInStringAndDoesNotStartByComponentProtocolbehavior, that check can reject valid local file URLs and abort tree loading. Also, pass the sanitized (normalized) location through to the model to avoid cache/model inconsistencies.
if (treeFileUrl == null || UtilValidate.isUrlInStringAndDoesNotStartByComponentProtocol(treeFileUrl.toString())) {
throw new IllegalArgumentException("Could not resolve location to URL: " + resourceName);
}
Document treeFileDoc = UtilXml.readXmlDocument(treeFileUrl, true, true);
modelTreeMap = readTreeDocument(treeFileDoc, delegator, dispatcher, resourceName);
framework/widget/src/main/java/org/apache/ofbiz/widget/model/MenuFactory.java:127
menuFileUrl.toString()for resolved component:// resources is typicallyfile:/..., so usingisUrlInStringAndDoesNotStartByComponentProtocol(...)here can reject valid local URLs. UseisUrlInString(...)(or only validate the original string before resolving) and propagate the sanitized location to downstream model parsing.
if (menuFileUrl == null || UtilValidate.isUrlInStringAndDoesNotStartByComponentProtocol(menuFileUrl.toString())) {
throw new IllegalArgumentException("Could not resolve location to URL: " + resourceName);
}
Document menuFileDoc = UtilXml.readXmlDocument(menuFileUrl, true, true);
modelMenuMap = readMenuDocument(menuFileDoc, resourceName, visualTheme);
framework/widget/src/main/java/org/apache/ofbiz/widget/model/GridFactory.java:87
- For sanitized component:// locations,
gridFileUrl.toString()is generallyfile:/.... UsingisUrlInStringAndDoesNotStartByComponentProtocol(...)here can therefore reject valid local URLs and abort grid loading. Also prefer using the sanitized/normalized location consistently in error messages and model creation.
URL gridFileUrl = FlexibleLocation.resolveLocation(sanitizedLocation);
if (gridFileUrl == null || UtilValidate.isUrlInStringAndDoesNotStartByComponentProtocol(gridFileUrl.toString())) {
throw new IllegalArgumentException("Could not resolve location to URL: " + resourceName);
}
Document gridFileDoc = UtilXml.readXmlDocument(gridFileUrl, true, true);
framework/widget/src/main/java/org/apache/ofbiz/widget/model/FormFactory.java:85
- For sanitized component:// locations,
formFileUrl.toString()is generallyfile:/.... UsingisUrlInStringAndDoesNotStartByComponentProtocol(...)can reject valid local URLs and abort form loading. Also use the sanitized/normalized location consistently in messages and model creation to avoid inconsistencies.
URL formFileUrl = FlexibleLocation.resolveLocation(sanitizedLocation);
if (formFileUrl == null || UtilValidate.isUrlInStringAndDoesNotStartByComponentProtocol(formFileUrl.toString())) {
throw new IllegalArgumentException("Could not resolve location to URL: " + resourceName);
}
Document formFileDoc = UtilXml.readXmlDocument(formFileUrl, true, true);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (isEmpty(s) || s.startsWith("component://")) { | ||
| return false; | ||
| } | ||
| return s.indexOf("://") != -1; | ||
| return s.indexOf("://") != -1 || s.startsWith("file:/"); | ||
| } |
| private static Pattern initAllowedPathPattern() { | ||
| return Pattern.compile(UtilProperties.getPropertyValue("security", "allowFilePaths", "")); | ||
| } | ||
|
|
||
| /** | ||
| * isAllowedPath takes a String representing a filePath, normalizes it and checks it if allowed | ||
| * @param rawPathString | ||
| * @return true if it's an allowed path, false otherwise | ||
| */ | ||
| public static boolean isAllowedPath(String rawPathString) { | ||
| String allowPatternStr = UtilProperties.getPropertyValue("security", "allowFilePaths", ""); | ||
| if (isEmpty(allowPatternStr)) { | ||
| return false; | ||
| } | ||
| if (allowedPathsPattern == null) { | ||
| allowedPathsPattern = initAllowedPathPattern(); | ||
| } | ||
| return UtilValidate.isNotEmpty(rawPathString) | ||
| && allowedPathsPattern.matcher(Paths.get(rawPathString) | ||
| .normalize().toString()) | ||
| .matches(); | ||
| } |
| #-- RegExp for ofbiz to allow some file access denied by default like function to UtilValidate::isAllowedPath | ||
| allowFilePaths= |
| public static String sanitize(String location) { | ||
| if (UtilValidate.isEmpty(location) || UtilValidate.isUrlInStringAndDoesNotStartByComponentProtocol(location)) { | ||
| Debug.logWarning(String.format("Unable to sanitize location: [%s]", location), MODULE); | ||
| return null; | ||
| } | ||
| if (location.startsWith(COMPO_TYPE) && location.length() > 12) { | ||
| if (location.indexOf("..") > 0) { | ||
| Debug.logWarning(String.format("For security raison traversal sequence '..' is not allowed : [%s]", location), MODULE); | ||
| return null; | ||
| } | ||
| return COMPO_TYPE + Paths.get(location.substring(12)).normalize(); | ||
| } | ||
|
|
||
| return UtilValidate.isAllowedPath(location) | ||
| ? location | ||
| : null; | ||
| } |
f204150 to
78eed22
Compare
…cation resolution (#1624) This PR builds on [PR #1586](Krishnauprit18:secure-widget-resource-loading) and keeps the WidgetSecureLocation approach, adding two related changes in RequestHandler: 1. View state persistence check (_LAST_VIEW_NAME_): Validates candidate last view names against ControllerConfig before they are stored in the session, so view navigation falls back cleanly. 2. Parameter-level attribute filtering (_LAST_VIEW_PARAMS_): Filters dynamic resource and location parameters (*Location, *Screen, *Template, *Uri) during session persistence and view-last attribute restoration. Thank you Krishna for the contribution.
…owlist (#1650) Add WidgetSecureLocation as a single gatekeeper for screen, form, grid, menu, and tree resource locations. Reject any file: scheme location regardless of letter case, reject '..' traversal inside component:// locations, and deny non-component locations by default unless allowed via the new security.allowFilePaths pattern. Also stop anonymous JSON request bodies from overriding request attributes that already exist as trusted ServletContext attributes, which is how a request-controlled value could shadow a webapp's configured decorator location. Adds unit tests for the new checks. Thank you Krishna Uprit(@Krishnauprit18) and Nicolas Malin(@nmalin) for your help. PR from @nmalin - #1552 PR from @Krishnauprit18 - #1586
…cation resolution (apache#1624) This PR builds on [PR apache#1586](Krishnauprit18:secure-widget-resource-loading) and keeps the WidgetSecureLocation approach, adding two related changes in RequestHandler: 1. View state persistence check (_LAST_VIEW_NAME_): Validates candidate last view names against ControllerConfig before they are stored in the session, so view navigation falls back cleanly. 2. Parameter-level attribute filtering (_LAST_VIEW_PARAMS_): Filters dynamic resource and location parameters (*Location, *Screen, *Template, *Uri) during session persistence and view-last attribute restoration. Thank you Krishna for the contribution. (cherry picked from commit 9ca27f4)
…owlist (apache#1650) Add WidgetSecureLocation as a single gatekeeper for screen, form, grid, menu, and tree resource locations. Reject any file: scheme location regardless of letter case, reject '..' traversal inside component:// locations, and deny non-component locations by default unless allowed via the new security.allowFilePaths pattern. Also stop anonymous JSON request bodies from overriding request attributes that already exist as trusted ServletContext attributes, which is how a request-controlled value could shadow a webapp's configured decorator location. Thank you Krishna Uprit(@Krishnauprit18) and Nicolas Malin(@nmalin) for your help. PR from @nmalin - apache#1552 PR from @Krishnauprit18 - apache#1586 (cherry picked from commit 4afb9c9)
…cation resolution (apache#1624) This PR builds on [PR apache#1586](Krishnauprit18:secure-widget-resource-loading) and keeps the WidgetSecureLocation approach, adding two related changes in RequestHandler: 1. View state persistence check (_LAST_VIEW_NAME_): Validates candidate last view names against ControllerConfig before they are stored in the session, so view navigation falls back cleanly. 2. Parameter-level attribute filtering (_LAST_VIEW_PARAMS_): Filters dynamic resource and location parameters (*Location, *Screen, *Template, *Uri) during session persistence and view-last attribute restoration. Thank you Krishna for the contribution. (cherry picked from commit 9ca27f4)
…owlist (apache#1650) Add WidgetSecureLocation as a single gatekeeper for screen, form, grid, menu, and tree resource locations. Reject any file: scheme location regardless of letter case, reject '..' traversal inside component:// locations, and deny non-component locations by default unless allowed via the new security.allowFilePaths pattern. Also stop anonymous JSON request bodies from overriding request attributes that already exist as trusted ServletContext attributes, which is how a request-controlled value could shadow a webapp's configured decorator location. Thank you Krishna Uprit(@Krishnauprit18) and Nicolas Malin(@nmalin) for your help. PR from @nmalin - apache#1552 PR from @Krishnauprit18 - apache#1586 (cherry picked from commit 4afb9c9)
…cation resolution (#1624) (#1653) This PR builds on [PR #1586](Krishnauprit18:secure-widget-resource-loading) and keeps the WidgetSecureLocation approach, adding two related changes in RequestHandler: 1. View state persistence check (_LAST_VIEW_NAME_): Validates candidate last view names against ControllerConfig before they are stored in the session, so view navigation falls back cleanly. 2. Parameter-level attribute filtering (_LAST_VIEW_PARAMS_): Filters dynamic resource and location parameters (*Location, *Screen, *Template, *Uri) during session persistence and view-last attribute restoration. Thank you Krishna for the contribution. (cherry picked from commit 9ca27f4) Co-authored-by: Krishna Uprit <125099508+Krishnauprit18@users.noreply.github.com>
…owlist (apache#1650) Add WidgetSecureLocation as a single gatekeeper for screen, form, grid, menu, and tree resource locations. Reject any file: scheme location regardless of letter case, reject '..' traversal inside component:// locations, and deny non-component locations by default unless allowed via the new security.allowFilePaths pattern. Also stop anonymous JSON request bodies from overriding request attributes that already exist as trusted ServletContext attributes, which is how a request-controlled value could shadow a webapp's configured decorator location. Thank you Krishna Uprit(@Krishnauprit18) and Nicolas Malin(@nmalin) for your help. PR from @nmalin - apache#1552 PR from @Krishnauprit18 - apache#1586 (cherry picked from commit 4afb9c9)
|
Hello @Krishnauprit18, Your changes have been merged using this PR - #1654 Thank you for your kind support. Please close this PR whenever you can. Thanks, |
This PR builds upon PR #1552 by preserving
WidgetSecureLocationarchitecture and introducing two Defense-in-Depth security add-ons inUtilValidate:Protocol-Layer Rejection (
file:/): ExtendsisUrlInStringAndDoesNotStartByComponentProtocolto explicitly rejectfile:/schemes at the entry point, preventing single-slash URL bypasses.Default-Deny Policy: Enforces an explicit default-deny check when
allowFilePathsinsecurity.propertiesis unconfigured or blank.