Skip to content

Conversation

@JinwooHwang
Copy link
Contributor

@JinwooHwang JinwooHwang commented Dec 9, 2025

Secure Session Deserialization with Application-Level Security Model

Overview

This PR implements Application-Level Security Model for Apache Geode session management using Java's standard ObjectInputFilter API (JEP 290). This approach provides Application-Level Security Model isolation, aligning with industry standards and Geode's existing fine-grained authorization model.

An alternative architecture is implemented in [GEODE-10515] Enhance Session Attribute Handling with Input Validation (#7941).

Testing: 52 comprehensive tests (all passing)
Security Coverage: 26 gadget classes + 10 dangerous package patterns blocked


Architecture

Application-Level Security Model

┌─────────────────────────────────────────────────────────────────┐
│                  Web Application A (WAR)                        │
├─────────────────────────────────────────────────────────────────┤
│  Configuration (web.xml)                                        │
│  ┌───────────────────────────────────────────────────┐          │
│  │ <context-param>                                   │          │
│  │   <param-name>serializable-object-filter          │          │
│  │   <param-value>com.payment.**;!*</param-value>    │          │
│  │ </context-param>                                  │          │
│  └───────────────────────────────────────────────────┘          │
│         ↓                                                       │
│  GemfireHttpSession.java                                        │
│  ├─ Reads filter pattern from ServletContext                    │
│  ├─ Creates ObjectInputFilter using JEP 290 API                 │
│  └─ Passes filter to ClassLoaderObjectInputStream               │
│         ↓                                                       │
│  ClassLoaderObjectInputStream.java                              │
│  └─ Applies filter during deserialization                       │
│         ↓                                                       │
│  JDK ObjectInputFilter (Standard Java API)                      │
│  ├─ ALLOWS: com.payment.** classes                              │
│  └─ BLOCKS: Everything else (gadgets, exploits)                 │
└─────────────────────────────────────────────────────────────────┘
         ↓ Session data
┌─────────────────────────────────────────────────────────────────┐
│              Geode Cluster (No config needed)                   │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│                  Web Application B (WAR)                        │
├─────────────────────────────────────────────────────────────────┤
│  Configuration (web.xml)                                        │
│  ┌───────────────────────────────────────────────────┐          │
│  │ <context-param>                                   │          │
│  │   <param-name>serializable-object-filter          │          │
│  │   <param-value>com.analytics.**;!*</param-value>  │          │
│  │ </context-param>                                  │          │
│  └───────────────────────────────────────────────────┘          │
└─────────────────────────────────────────────────────────────────┘

Key Principle: Each application enforces its own security boundary, independent of cluster configuration.


Security Guarantees

Protection Against Critical Vulnerabilities

Remote Code Execution (RCE)

  • Gadget Chains: InvokerTransformer, TemplatesImpl, MethodClosure
  • JNDI Injection: JdbcRowSetImpl, InitialContext lookups
  • Remote Class Loading: RMI, URLClassLoader exploits
  • Scripting Engines: JavaScript, Groovy, Nashorn
  • XSLT Execution: TemplatesImpl bytecode injection
  • Spring Exploits: BeanFactory manipulation
  • C3P0 Attacks: Connection pool JNDI attacks

Denial of Service (DoS)

  • Depth Bombs: maxdepth=50
  • Array Bombs: maxarray=10,000
  • Reference Bombs: maxrefs=10,000
  • Byte Bombs: maxbytes=10MB

Severity Assessment

  • Attack Vector: Network-accessible
  • Attack Complexity: Low (simple exploit chains available)
  • Authentication: Not required
  • Impact: Critical (Remote Code Execution + Denial of Service)
  • Scope: Can affect entire cluster from single compromised session

Architecture Comparison

1. Application-Level Security Model

┌──────────────┐  ┌──────────────┐  ┌──────────────┐
│   App A      │  │   App B      │  │   App C      │
│              │  │              │  │              │
│ Filter:      │  │ Filter:      │  │ Filter:      │
│ payment.**   │  │ analytics.** │  │ cms.**       │
│ (web.xml)    │  │ (web.xml)    │  │ (web.xml)    │
└──────┬───────┘  └──────┬───────┘  └──────┬───────┘
       │                 │                 │
       └─────────────────┴─────────────────┘
                         │
                  ┌──────▼──────┐
                  │    Geode    │
                  │   Cluster   │
                  │  (no config)│
                  └─────────────┘

- Application-Level Security Model policies
- Principle of Least Privilege
- No cluster configuration needed
- Aligns with Geode SecurityManager (per-region auth)
- Standard JEP 290 API

2. TCCL Approach - Cluster-Level Security

┌──────────────┐  ┌──────────────┐  ┌──────────────┐
│   App A      │  │   App B      │  │   App C      │
│  (no config) │  │  (no config) │  │  (no config) │
└──────┬───────┘  └──────┬───────┘  └──────┬───────┘
       │                 │                 │
       └─────────────────┴─────────────────┘
                         │
                  ┌──────▼────────────────────────┐
                  │    Geode Cluster              │
                  │ gemfire.properties:           │
                  │ serializable-object-filter=   │
                  │   payment.**,analytics.**,... │
                  └───────────────────────────────┘

- Shared security policy (all apps get union)
- App A can deserialize App B's classes
- Violates Principle of Least Privilege
- No Application-Level Security Model

3. PR-7941/GEODE-10515 Custom Filter - Hardcoded Security

Summary:

  • Hardcoded ALLOWED_CLASSES (Set) and ALLOWED_PATTERNS (regex)
  • Secure by default, but inflexible

Decision Matrix

Requirement Application-Level Security Model TCCL PR-7941/GEODE-10515
Multi-tenant isolation - Per-app policies - Shared policy - Per-app (hardcoded)
Principle of Least Privilege - Yes - No - Yes
Standard API - JEP 290 - Custom - Custom
Lines of Code 9 939
Configuration Flexibility - web.xml - Cluster-wide - Hardcoded
Operational Overhead Low High Medium
Secure by Default - Yes - No - Yes
Maintenance Burden Low Medium High
Industry Standard - Yes - No - No

Architectural Consistency

Geode's Existing Security Model

Geode already provides fine-grained authorization through SecurityManager:

public interface SecurityManager {
  boolean authorize(Object principal, ResourcePermission permission);
}

// Example permissions:
DATA:READ:RegionA           // Read any key in RegionA
DATA:READ:RegionA:key1      // Read only key1 in RegionA
DATA:WRITE:RegionB          // Write to RegionB

Current State:

  • Data Access: Per-region, per-key, per-user authorization
  • Deserialization: Cluster-wide policy (with TCCL)

With ObjectInputFilter:

  • Data Access: Per-region, per-key, per-user authorization
  • Deserialization: Application-level policy

Result: Architectural consistency - both use fine-grained, Application-Level Security Model.


Industry Standards

OWASP Deserialization Cheat Sheet

  • Hardening ObjectInputStream - Recommended approach using resolveClass() override
  • ObjectInputFilter (JEP 290) - Standard Java API for deserialization filtering
  • Custom implementations - Require careful design and comprehensive testing
  • SerialKiller library - Community-maintained safe deserialization wrapper

Security Frameworks

  • OWASP Top 10 A08:2021 - Software and Data Integrity Failures (includes insecure deserialization)
  • CWE-502 - Deserialization of Untrusted Data
  • Zero Trust Architecture - Application-level security boundaries align with per-application filtering model

Implementation Details

Files Changed (7 files, 1,502 insertions, 1 deletion)

Production Code (9 lines)

1. GemfireHttpSession.java

// Create filter from user configuration
String filterPattern = getServletContext()
    .getInitParameter("serializable-object-filter");
ObjectInputFilter filter = filterPattern != null 
    ? ObjectInputFilter.Config.createFilter(filterPattern)
    : null;

ObjectInputStream ois = new ClassLoaderObjectInputStream(
    new ByteArrayInputStream(baos.toByteArray()), loader, filter);

2. ClassLoaderObjectInputStream.java

public ClassLoaderObjectInputStream(InputStream in, ClassLoader loader,
                                   ObjectInputFilter filter) throws IOException {
  super(in);
  this.loader = loader;
  if (filter != null) {
    setObjectInputFilter(filter);  // JEP 290 API
  }
}

3. web.xml - Configuration Example

<context-param>
  <param-name>serializable-object-filter</param-name>
  <param-value>com.myapp.**;java.lang.**;java.util.**;!*</param-value>
</context-param>

Test Files (1,243 lines)

4. ClassLoaderObjectInputStreamTest.java

  • 5 new tests validating filter parameter handling

5. DeserializationSecurityTest.java

  • 9 comprehensive security tests
  • RCE prevention tests
  • DoS prevention tests
  • Resource limit tests

6. GadgetChainSecurityTest.java

  • 36 gadget chain blocking tests
  • Tests for all 26 dangerous classes
  • Tests for all 10 dangerous package patterns

Testing

Test Coverage Summary

52 total tests (all passing )
 ClassLoaderObjectInputStreamTest: 7 tests
   Filter parameter handling
   Null filter behavior
   Filter application verification

 DeserializationSecurityTest: 9 tests
   RCE prevention (4 tests)
   DoS prevention (4 tests)
   Resource limits (1 test)

 GadgetChainSecurityTest: 36 tests
    Commons Collections gadgets (7 tests)
    JNDI injection (3 tests)
    Remote class loading (4 tests)
    Scripting engines (3 tests)
    XSLT execution (2 tests)
    Spring exploits (3 tests)
    C3P0 attacks (2 tests)
    Package pattern blocking (10 tests)

Running Tests

./gradlew :geode-modules:test --tests "*ObjectInputStreamTest"
./gradlew :geode-modules:test --tests "*DeserializationSecurityTest"
./gradlew :geode-modules:test --tests "*GadgetChainSecurityTest"

Configuration Guide

Basic Configuration

Step 1: Add filter pattern to web.xml

<web-app>
  <context-param>
    <param-name>serializable-object-filter</param-name>
    <param-value>com.myapp.model.**;java.lang.**;!*</param-value>
  </context-param>
</web-app>

Step 2: Deploy WAR file (no cluster restart needed)

Pattern Syntax (JEP 290)

com.myapp.**            Allow all com.myapp classes
java.lang.String        Allow specific class
!com.dangerous.**       Explicitly reject package
!*                      Reject everything else (default deny)

Multi-Application Example

E-commerce Application

<param-value>
  com.shop.model.**;
  com.payment.**;
  java.lang.**;java.util.**;
  !*
</param-value>

Analytics Application

<param-value>
  com.analytics.**;
  com.ml.**;
  java.lang.**;java.util.**;
  !*
</param-value>

CMS Application

<param-value>
  com.cms.**;
  java.lang.**;java.util.**;
  !*
</param-value>

Result: Each application has isolated security policy


Checklist

  • Implementation complete (9 lines)
  • 52 tests passing
  • Security documentation complete
  • Configuration examples provided
  • Backwards compatible (filter optional)
  • Standard JEP 290 API
  • Zero cluster configuration changes
  • Aligns with Geode SecurityManager model

References


Recommendation

This PR implements Application-Level Security Model using Java's ObjectInputFilter API (JEP 290). It provides:

  • 9 lines vs 939 (PR-7941/GEODE-10515)
  • Application-level isolation vs cluster-wide shared policy
  • Standard JEP 290 API vs custom implementation
  • Flexible configuration vs hardcoded lists
  • Architectural consistency with Geode SecurityManager
  • Aligns with OWASP guidance on deserialization security

Recommended for most use cases - provides the best balance of security, simplicity, and operational flexibility.

For all changes, please confirm:

  • Is there a JIRA ticket associated with this PR? Is it referenced in the commit message?
  • Has your PR been rebased against the latest commit within the target branch (typically develop)?
  • Is your initial contribution a single, squashed commit?
  • Does gradlew build run cleanly?
  • Have you written or updated unit tests to verify your changes?
  • If adding new dependencies to the code, are these dependencies licensed in a way that is compatible for inclusion under ASF 2.0?

- Implement per-application deserialization filtering using standard JEP 290 API
- Add ObjectInputFilter parameter to ClassLoaderObjectInputStream constructor
- Update GemfireHttpSession to read filter configuration from ServletContext
- Add comprehensive security tests covering RCE and DoS prevention
- Add 52 tests validating gadget chain blocking and resource limits
- Add example configuration in session-testing-war web.xml

This provides application-level security isolation, allowing each web application
to define its own deserialization policy independent of cluster configuration.
@JinwooHwang
Copy link
Contributor Author

All checks have passed, @sboorlagadda

@JinwooHwang JinwooHwang changed the title Secure Session Deserialization with Application-Level Security Model using ObjectInputFilter (JEP 290) [GEODE-10535] Secure Session Deserialization with Application-Level Security Model using ObjectInputFilter (JEP 290) Dec 9, 2025
- Add comprehensive security guide for configuring deserialization protection
- Document JEP 290 ObjectInputFilter pattern syntax and examples
- Include best practices, troubleshooting, and migration guidance
- Add navigation link in HTTP Session Management chapter overview
Copy link
Member

@sboorlagadda sboorlagadda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think adding a log if users have not configured a filter should be good improvement and other nitpicks.

String filterPattern = getServletContext()
.getInitParameter("serializable-object-filter");
ObjectInputFilter filter = filterPattern != null
? ObjectInputFilter.Config.createFilter(filterPattern)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we concerned with any duplicate object creations? Should we guard them against races?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate your excellent point, @sboorlagadda. I've implemented filter caching using the double-checked locking pattern with volatile fields. The changes include:

  • Added private volatile ObjectInputFilter cachedFilter to cache the filter instance
  • Added private volatile boolean filterLogged to ensure one-time logging
  • Implemented getOrCreateFilter() method that creates and caches the filter on first use
  • The double-checked locking ensures thread-safety without synchronization overhead on subsequent calls

This eliminates both the performance overhead of recreating the filter on every deserialization and prevents race conditions in multi-threaded servlet environments.

throws IOException {
super(in);
this.loader = loader;
setObjectInputFilter(filter);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a null check?

 if (filter != null) {
    setObjectInputFilter(filter);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch! Added a null check.

- Implement filter caching using double-checked locking with volatile fields to eliminate race conditions and improve performance
- Add null check before setObjectInputFilter() for defensive programming
- Add INFO logging when filter is configured and WARN logging when not configured to improve security visibility

Addresses review comments by @sboorlagadda on PR apache#7966
@JinwooHwang
Copy link
Contributor Author

Thank you for your brilliant idea, @sboorlagadda. Implemented comprehensive logging in the getOrCreateFilter() method. I appreciate your thoughtful review.

@JinwooHwang
Copy link
Contributor Author

JinwooHwang commented Dec 11, 2025

Hi @sboorlagadda,
All checks have passed. When you have a moment, your approval on this PR, as well as on #7968, would be greatly appreciated. Please feel free to determine whether this should be included in the 2.0.0 release.
Thank you in advance for your time and consideration. I appreciate it.

Copy link
Member

@sboorlagadda sboorlagadda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved

@JinwooHwang
Copy link
Contributor Author

Thank you so much for your review and approval @sboorlagadda

@JinwooHwang JinwooHwang merged commit affba70 into apache:develop Dec 12, 2025
15 checks passed
JinwooHwang added a commit that referenced this pull request Dec 12, 2025
- Implement filter caching using double-checked locking with volatile fields to eliminate race conditions and improve performance
- Add null check before setObjectInputFilter() for defensive programming
- Add INFO logging when filter is configured and WARN logging when not configured to improve security visibility

Addresses review comments by @sboorlagadda on PR #7966
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants