Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 45 additions & 4 deletions THREAT_MODEL.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@ perimeter. Struts is **in-process** with the application; it has no daemon, no
listening socket of its own, and no trust boundary against the application code
it runs inside. *(documented — it is a framework, not a server.)*

**Second deployment shape — Struts Boot (`struts2-boot-starter`, since 7.5.0).** The
application is a plain `main()` that calls `StrutsBoot.run(...)`; the starter starts an
embedded Tomcat, registers the Struts filter itself, and opens one HTTP listening socket.
Actions come from a Java `ActionRegistry` instead of (or alongside) `struts.xml`. The
request pipeline behind the filter is unchanged, so everything in §4–§9 applies as-is.
What moves is the **container's defaults** (§5, §5a): with Boot they are chosen by the
framework, not by the operator. *(documented — `boot/`, `apps/boot-hello/README.md`.)*

**Caller roles.**

- **Untrusted HTTP client** — sends requests (parameters, headers, cookies,
Expand All @@ -105,6 +113,9 @@ it runs inside. *(documented — it is a framework, not a server.)*
section of the security guidance.)*
- **Operator** — deploys the WAR, sets `devMode` off, restricts dev-only plugins,
configures the container and JVM. **Trusted.** *(documented.)*
With Boot, the operator still owns the JVM, the host, the network perimeter and the
`struts.boot.*` settings (port, bind address, context path); the container defaults
the starter applies are Struts' (§5).

**Component families.**

Expand Down Expand Up @@ -149,7 +160,8 @@ links to them and assigns each a triage disposition (§13):
- **Findings that only manifest with a documented-insecure / non-default setting**
(`devMode=true`, Config Browser Plugin exposed in production, DMI enabled, or a
§5a hardening knob turned off) → `OUT-OF-MODEL: non-default-config`. *(documented.)*
- **The servlet container, JVM, JDK, and OS**, and the application's own
- **The servlet container, JVM, JDK, and OS** — except the embedded-container defaults
the Boot starter itself applies (§5), which are in model — and the application's own
authentication, authorization, session management, CSRF token storage, and
transport (TLS). Struts is "a pure web framework," not a security framework.
*(documented / inferred — §14 Q-env.)*
Expand Down Expand Up @@ -210,8 +222,21 @@ documented anti-pattern is `OUT-OF-MODEL`. *(documented/inferred — §14 Q-defa
Versions table.)*
- The operator runs production with `devMode=false` and dev-only diagnostics (Config
Browser Plugin) disabled or access-controlled. *(documented.)*
- Struts opens no sockets and makes no outbound connections of its own; any network
egress is the application's. *(inferred — §14 Q-egress.)*
- Struts makes no outbound connections of its own; any network egress is the
application's. In the WAR shape it opens no sockets. The Boot starter opens exactly
one inbound HTTP listener (§2); it makes no outbound connections either.
*(inferred — §14 Q-egress, Q-boot.)*
- **Boot container defaults are in model.** When `struts2-boot-starter` starts Tomcat,
the framework owns these defaults:
- listens on all interfaces, port 8080 (override `struts.boot.address` /
`struts.boot.port`)
- error responses render without exception report or server version
- `DefaultServlet` serves only an empty per-run docBase
- Tomcat's base directory is a fresh owner-only temp directory, deleted on shutdown
- `devMode` is never switched on

A deviation from these defaults is a Struts finding. Anything the developer adds on
top (TLS, proxies, a different container) is theirs. *(documented — §14 Q-boot.)*

## §5a Build-time and configuration variants — **the central knob set**

Expand Down Expand Up @@ -245,6 +270,12 @@ reproduced here.** Only the triage-load-bearing facts:
- The **FetchMetadata / COOP / COEP** interceptors are in core's `defaultStack` and
enabled since 6.0, COEP in report-only mode; an application opts *out*, per action or
stack (§8.5).
- **Registry-defined actions (Boot) are never broader than the `struts.xml`
equivalent.** They extend `struts-default`, keep Strict Method Invocation on, are
added to the OGNL allowlist exactly as XML-declared action classes are, and allow
only their registered method — narrower than XML, which also inherits the package's
global allowed methods. A registry action reachable where the XML equivalent would
not be is a finding. *(documented — WW-5791 spec.)*

**Insecure-default question (wave 1).** Because the secure posture is the **7.0
default set**, the triage rule needs ratifying: is "a finding that only works with a
Expand Down Expand Up @@ -386,7 +417,9 @@ Struts' security work.)*
permanently disabled in JDK 24; see §5a). *(documented.)*
- **Auto-generated error pages do not escape action names** (historical S2-006) — the
app must define custom error pages; XSS in the default error page is a documented
hardening item, not a defended property. *(documented.)*
hardening item, not a defended property. *(documented.)* Boot apps have no
`web.xml`, so the starter suppresses Tomcat's exception report and server info itself
(§5); custom error *content* is still the application's.
- **Well-known classes (framework):** OGNL/expression injection, multipart/file-upload
parsing bugs, and parameter-pollution are the highest-severity recurring classes — but
the recent record adds two more that are currently the *more frequent* ones:
Expand Down Expand Up @@ -464,6 +497,10 @@ authoritative list; §14 Q12.)*
- **Dependency-tail CVEs** (a transitive jar, e.g. a logging or XML library) from an
SCA scan — triage upstream unless Struts' own code reaches the vulnerable path with
untrusted input.
- **"Boot listens on 0.0.0.0."** That's the documented default, matching standalone
Tomcat and Spring Boot. `struts.boot.address` restricts it. →
`OUT-OF-MODEL: non-default-config` only if the operator set a wider exposure; the
default itself is by design.

## §12 Conditions that would change this model

Expand Down Expand Up @@ -533,6 +570,10 @@ so what remains is ratifying the *wording*, not the substance.
- **Q-egress.** Confirm Struts opens no sockets and makes no outbound connections of
its own, so any network egress (and the SSRF surface it implies) is the
application's. (§5/§7.)
- **Q-boot.** Confirm the Boot starter's embedded-container defaults (§5) are in model
— a framework-shipped container default that leaks or exposes is a Struts finding,
not `OUT-OF-MODEL: environment` — and that the example app `apps/boot-hello` stays
out of scope like the other showcase apps. (§2/§3/§5.)

**Wave 2 — mechanism confirmations**

Expand Down
79 changes: 79 additions & 0 deletions apps/boot-hello/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Struts Boot Hello

The smallest Struts Boot application: one action, one FreeMarker template, no `web.xml`, no `struts.xml`.

## Three steps

1. Depend on `org.apache.struts:struts2-boot-starter`, at the same version as the rest of Struts. Importing
`org.apache.struts:struts2-bom` in `<dependencyManagement>` keeps the versions aligned so the starter needs no
`<version>` of its own.
2. Implement `StrutsBootApplication` and register actions:

```java
public class HelloApp implements StrutsBootApplication {

public static void main(String[] args) {
StrutsBoot.run(HelloApp.class, args);
}

@Override
public void actions(ActionRegistry actions) {
actions.action("hello", HelloAction.class).success("hello.ftl");
}
}
```

3. Put the view in `src/main/resources/templates/hello.ftl` and run `main()`.

Then open http://localhost:8080/hello.action.

## Running the jar

```
mvn package -pl apps/boot-hello -am -DskipAssembly -DskipTests
java -jar apps/boot-hello/target/struts2-boot-hello-<version>.jar --struts.boot.port=9090
```

`<version>` is the Struts version being built, for example `struts2-boot-hello-7.5.0-SNAPSHOT.jar`. The example is
not deployed to Maven repositories; build it from source.

## Settings

Settings are read from `struts.properties` on the classpath. A `--key=value` argument overrides the file.

| Key | Default | Meaning |
|----------------------------|---------|--------------------------------------------|
| `struts.boot.port` | `8080` | HTTP port; `0` picks a free port |
| `struts.boot.context-path` | `""` | Context path the application is served at |
| `struts.boot.address` | all | Interface to listen on, e.g. `127.0.0.1` |

## Defaults worth knowing

- Tomcat listens on all interfaces, not only on localhost; set `struts.boot.address` to restrict it.
- Error pages show the status only, without stack traces or the Tomcat version.
- `struts.devMode` stays off.
- Each start uses a fresh temporary base directory (`struts-boot-<uuid>`) that is deleted on shutdown.
- Views are FreeMarker only; a result path such as `hello.ftl` resolves to `/templates/hello.ftl` on the classpath.

## Shading and plugins

The jar is built with `maven-shade-plugin`. Every Struts plugin ships its own `META-INF/struts-plugin.xml`, and shading
keeps only one file with that name, so with two or more plugins on the classpath all but one would be silently lost.
This example uses no plugins. If your application needs them, package it differently instead of shading: a thin jar
with its dependencies in a `lib/` directory (`maven-dependency-plugin` copies them, `maven-jar-plugin` writes the
`Class-Path` manifest entry), or a distribution built with `maven-assembly-plugin`.

## Logging

Struts logs through the Log4j 2 API, and the starter ships no logging backend, so warnings and errors from Struts are
not printed. Add one to your application, for example (version managed by `org.apache.logging.log4j:log4j-bom`):

```xml
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<scope>runtime</scope>
</dependency>
```

with a `log4j2.xml` on the classpath, or `log4j-slf4j2-impl` to route to an SLF4J backend.
96 changes: 96 additions & 0 deletions apps/boot-hello/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-apps</artifactId>
<version>7.5.0-SNAPSHOT</version>
</parent>

<artifactId>struts2-boot-hello</artifactId>
<packaging>jar</packaging>
<name>Struts 2 Boot Hello</name>
<description>Struts Boot example application packaged as a runnable jar</description>

<properties>
<maven.deploy.skip>true</maven.deploy.skip>
</properties>

<dependencies>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<finalName>${project.artifactId}-${project.version}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.apache.struts2.boot.hello.HelloApp</mainClass>
<manifestEntries>
<Multi-Release>true</Multi-Release>
</manifestEntries>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts2.boot.hello;

public class HelloAction {

public String execute() {
return "success";
}

public String getMessage() {
return "Hello from Struts Boot";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts2.boot.hello;

import org.apache.struts2.boot.ActionRegistry;
import org.apache.struts2.boot.StrutsBoot;
import org.apache.struts2.boot.StrutsBootApplication;

public class HelloApp implements StrutsBootApplication {

public static void main(String[] args) {
StrutsBoot.run(HelloApp.class, args);
}

@Override
public void actions(ActionRegistry actions) {
actions.action("hello", HelloAction.class).success("hello.ftl");
}
}
21 changes: 21 additions & 0 deletions apps/boot-hello/src/main/resources/templates/hello.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<#--
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
-->
<h1><@s.property value="message"/></h1>
Loading
Loading