[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[FD] Apache Struts2 Tiles Plugin <=7.4.0 and >=6.3.0 Preparer Cache Vulnerabilities



The Apache Struts2 Tiles plugin version <=7.4.0 (and >=6.3.0) codebase includes 
a `ViewPreparer` construct, replete with multiple ViewPreparer factories, that 
contains several important vulnerabilities including unsynchronised, unbounded 
caches.  The lack of proper synchronisation means that certain factories will 
create caches that race, risking lost cache entries, corrupted internal bucket 
states under load (this can result in infinite loops that consume large amounts 
of compute or time, among other things), expensive duplicate `put(Object, 
Object)` calls, and similar issues.  The lack of an eviction process means that 
the cache can grow to arbitrary lengths, consume excessive amounts of memory, 
especially since the information system fails to utilise a starting size.  
These problems are compounded by the cache being `protected`, directly exposing 
it across the package and any inheriting classes, as well as attackers being 
able to control the key (if, for example, the cache is dyna
 mically managed and attackers can set part of the key or leveraging the 
vulnerability discussed in `Apache Struts2 Core Arbitrary Code Execution via 
OGNL Sandbox Escape` (01 November 2026) to call to create preparers, call to 
getPreparer, etc.).

Importantly, this file appears to have come from an older, known vulnerable, 
known buggy version of Tiles that was placed into the Apache Struts2 codebase, 
instead of the Tiles 3 version, when the Apaches Tiles project was retired and 
archived into the Apache Attic and, as such, when the Apache Struts2 
maintainers decided to import at least a subset of the Tiles codebase into 
their own.  By sucking in the buggy version, however, the Apache Struts2 Tiles 
plugin contained within said codebase inherits all issues that the imported 
code contained.

Examples
BasicPreparerFactory Race and Lack of an Eviction Process
The Apache Struts2 Tiles container is initialised as an application-scoped 
singleton resource.  Each request to the server utilises a separate thread, 
with each thread having access to the Tiles singleton.  If the requests execute 
Tiles definitions that require view preparers, they will simultaneously invoke 
the factory instance being used, so it is important that the factory utilise 
thread-safe constructs.  The `BasicPreparerFactory`, which is the default Tiles 
`ViewPreparer` factory, however, leverages an unsynchronised `HashMap` keyed by 
the preparer name.

At issue are a couple of things.  First, there is a time-of-check, time-of-use 
(TOCTOU) race condition in `getPreparer(String, Request)` -- this requires a 
proper lock or an atomic/locking call or, in a multi-threaded context, which is 
what happens in a Tiles workflow, it is prone to racing.  Second, there simply 
isn't a limit to the size of the Map and the default capacity is used.

This was the original design of Tiles and was a known issue, which manifested 
itself in denial of service (DoS) conditions in certain use cases, but was 
generally "mitigated" by having Tiles initialised and setup as part of the 
server start up routine (although plenty of systems still leveraged a more 
dynamic approach) -- the service would read the tiles configuration at start 
up, create and stuff the cache then, using a serial process.  This design, 
however, was still suboptimal, buggy, and problematic, even when used in that 
way, resulting in several overhauls and, eventually, settling in Tiles 3.  The 
old process, regardless, was suboptimal even from the most basic of 
perspectives -- since it was a one-time, start-up cost (typically -- again, 
developers and the like were free to, and often did, perform dynamic building), 
there was little reason to utilise the default capacity -- the desired size and 
capacity was trivially calculable and known, so it could and should have been 
set 
 thereto to prevent excessive memory use, with a better-tuned automatic 
resizing threshold.  That, of course, however, was the least of the problems, 
which is why Tiles 3 shifted to a `ConcurrentHashMap` (realistically, a 
sub-optimal solution, as it is generally best to control your own resources 
since such structures can still race via TOCTOU -- e.g., ` if 
(!map.containsKey(key)) map.put(key, value);`-- and similar coding constructs).

```java
    /**
     * Maps a preparer name to the instantiated preparer.
     */
    protected Map<String, ViewPreparer> preparers;

    /**
     * Constructor.
     */
    public BasicPreparerFactory() {
        this.preparers = new HashMap<>();
    }


    /**
     * Create a new instance of the named preparerInstance.  This factory
     * expects all names to be qualified class names.
     *
     * @param name    the named preparerInstance
     * @param context current context
     * @return ViewPreparer instance
     */
    public ViewPreparer getPreparer(String name, Request context) {
        if (!preparers.containsKey(name)) {
            preparers.put(name, createPreparer(name));
        }

        return preparers.get(name);
    }
```
Code snippet from struts2-tiles-plugin's 
plugins/tiles/src/main/java/org/apache/tiles/core/prepare/factory/BasicPreparerFactory.java,
 lines 44-71
_______________________________________________
Sent through the Full Disclosure mailing list
https://nmap.org/mailman/listinfo/fulldisclosure
Web Archives & RSS: https://seclists.org/fulldisclosure/