[ 
https://issues.apache.org/jira/browse/WW-5624?focusedWorklogId=1014865&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-1014865
 ]

ASF GitHub Bot logged work on WW-5624:
--------------------------------------

                Author: ASF GitHub Bot
            Created on: 11/Apr/26 01:05
            Start Date: 11/Apr/26 01:05
    Worklog Time Spent: 10m 
      Work Description: tranquac commented on PR #1657:
URL: https://github.com/apache/struts/pull/1657#issuecomment-4227548508

   Follow-up commit `df533b3` fixes three additional correctness issues found 
during an independent review of the v3 patch:
   
   ---
   
   ### Fix 1 — Collection/Map type preservation in deep copy helpers
   
   `deepCopyAuthorizedCollection` was always returning `new ArrayList()`. If 
the action field is typed as `Set<Pojo>`, the setter has signature `void 
setTags(Set<Pojo> s)` — invoking it with an `ArrayList` throws 
`IllegalArgumentException` at runtime.
   
   Same class of issue in `deepCopyAuthorizedMap` (always returned 
`LinkedHashMap`).
   
   Fix: inspect the source collection/map type and instantiate the appropriate 
concrete type:
   
   ```java
   // Collection:
   if (source instanceof SortedSet) {
       result = new TreeSet(((SortedSet) source).comparator());
   } else if (source instanceof Set) {
       result = new LinkedHashSet();
   } else {
       result = new ArrayList();
   }
   
   // Map:
   if (source instanceof SortedMap) {
       result = new TreeMap(((SortedMap) source).comparator());
   } else {
       result = new LinkedHashMap();
   }
   ```
   
   ---
   
   ### Fix 2 — Identity-based visited set in 
`scrubUnauthorizedPropertiesRecursive`
   
   The circular reference guard used `Set<Integer>` + 
`System.identityHashCode(target)`. `System.identityHashCode` is NOT guaranteed 
unique — two distinct live objects can have the same identity hash code. A 
collision would cause a valid nested object to be skipped during scrubbing, 
leaving unauthorized nested properties un-nulled.
   
   Fix: replaced with `Collections.newSetFromMap(new IdentityHashMap<>())`, 
which uses reference equality (`==`) and has no collision risk:
   
   ```java
   Collections.newSetFromMap(new IdentityHashMap<>())  // identity set
   // ...
   if (!visited.add(target)) { return; }  // target object itself, not its hash
   ```
   
   ---
   
   ### Fix 3 — `isNestedBeanType` now excludes all standard-library leaf 
packages
   
   Previously only `java.lang.*` and `java.math.*` were excluded. Types like 
`UUID`, `Currency`, `Locale` (in `java.util`), `URI`/`URL` (in `java.net`), and 
`Path`/`File` (in `java.nio`/`java.io`) returned `true` from 
`isNestedBeanType`, causing the code to attempt to recurse into their internal 
fields. This would silently drop fields of those types (no `@StrutsParameter` 
annotation on internal JDK fields → rejected → value not copied).
   
   Fix: broadened exclusions:
   
   ```java
   // java.util.* non-Collection/Map leaf types (UUID, Currency, Locale, Date, 
etc.)
   if (clazz.getName().startsWith("java.util.") && 
!Collection.class.isAssignableFrom(clazz)
           && !Map.class.isAssignableFrom(clazz)) {
       return false;
   }
   // All java.time.* temporal types
   if (clazz.getName().startsWith("java.time.")) return false;
   // I/O and network value types
   if (clazz.getName().startsWith("java.net.") || 
clazz.getName().startsWith("java.io.")
           || clazz.getName().startsWith("java.nio.")) {
       return false;
   }
   ```
   
   ---
   
   ### Test results
   
   ```
   plugins/json:  124 tests, 0 failures
   plugins/rest:   76 tests, 0 failures
   ```




Issue Time Tracking
-------------------

    Worklog Id:     (was: 1014865)
    Time Spent: 2h 20m  (was: 2h 10m)

> Request body population bypasses @StrutsParameter contract outside 
> ParametersInterceptor
> ----------------------------------------------------------------------------------------
>
>                 Key: WW-5624
>                 URL: https://issues.apache.org/jira/browse/WW-5624
>             Project: Struts 2
>          Issue Type: Bug
>          Components: Plugin - JSON, Plugin - REST
>    Affects Versions: 7.1.1
>            Reporter: Tran Quac
>            Priority: Major
>             Fix For: 7.2.0
>
>          Time Spent: 2h 20m
>  Remaining Estimate: 0h
>
> h2. Summary
> {{@StrutsParameter}} enforcement is currently implemented in 
> {{ParametersInterceptor}} for standard request parameter binding, but 
> request-body based binders in some plugins bypass that authorization model 
> and populate action/model objects directly.
> This creates inconsistent behavior between URL/form parameters and JSON/XML 
> request bodies, and may allow mass assignment of properties that would 
> normally be rejected by {{ParametersInterceptor}}.
> h2. Affected areas currently identified
> * JSON plugin:
> {{JSONPopulator.populateObject()}} sets properties via direct reflection and 
> does not follow the full {{@StrutsParameter}} authorization rules.
> * REST plugin:
> {{JacksonJsonHandler.toObject()}} updates target objects directly via Jackson 
> and does not follow the full {{@StrutsParameter}} authorization rules.
> h2. Problem scope
> The issue is broader than checking whether a setter is annotated. The current 
> core contract in {{ParametersInterceptor}} also includes:
> * permitted nesting depth
> * authorization based on the exposed root member
> * ModelDriven handling
> * transition mode semantics
> * related allowlisting behavior
> Any request-body binding implementation should align with that same contract, 
> otherwise Struts applies different security rules depending on how input 
> reaches the action/model.
> h2. Expected direction
> Instead of implementing separate partial checks in each plugin, Struts should 
> reuse or extract the shared parameter-binding authorization logic from 
> {{ParametersInterceptor}} and apply it consistently across request-body 
> binders.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to