High severity8.8NVD Advisory· Published Mar 31, 2025· Updated Apr 15, 2026
CVE-2025-31129
CVE-2025-31129
Description
Jooby is a web framework for Java and Kotlin. The pac4j io.jooby.internal.pac4j.SessionStoreImpl#get module deserializes untrusted data. This vulnerability is fixed in 2.17.0 (2.x) and 3.7.0 (3.x).
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
io.jooby:jooby-pac4jMaven | < 2.17.0 | 2.17.0 |
io.jooby:jooby-pac4jMaven | >= 3.0.0.M1, < 3.7.0 | 3.7.0 |
Patches
3b6655544cdbb5adecacc2ce83e13562cf36dsecurity: pac4j can cause deserialization of untrusted data
10 files changed · +755 −342
modules/jooby-bom/pom.xml+303 −302 modified@@ -1,5 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> -<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"> +<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> @@ -11,11 +12,306 @@ <groupId>io.jooby</groupId> <artifactId>jooby-bom</artifactId> - <version>3.6.2-SNAPSHOT</version> <packaging>pom</packaging> + <version>3.6.2-SNAPSHOT</version> <description>Jooby (Bill of Materials)</description> <url>https://jooby.io</url> + <!-- THIS FILE IS AUTO GENERATED. DON'T EDIT --> + <properties> + <spotless.check.skip>true</spotless.check.skip> + </properties> + + <dependencyManagement> + <dependencies> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-avaje-inject</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-avaje-jsonb</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-avaje-validator</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-awssdk-v1</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-awssdk-v2</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-caffeine</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-camel</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-cli</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-commons-email</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-conscrypt</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-db-scheduler</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-distribution</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-ebean</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-flyway</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-freemarker</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-graphiql</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-graphql</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-graphql-playground</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-gson</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-guice</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-handlebars</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-hibernate</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-hibernate-validator</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-hikari</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-jackson</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-jasypt</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-jdbi</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-jetty</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-jstachio</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-jte</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-jwt</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-kafka</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-kotlin</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-log4j</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-logback</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-maven-plugin</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-metrics</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-mutiny</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-netty</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-openapi</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-pac4j</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-pebble</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-quartz</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-reactor</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-redis</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-redoc</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-rocker</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-run</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-rxjava3</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-stork</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-swagger-ui</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-test</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-thymeleaf</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-undertow</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-whoops</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.jooby</groupId> + <artifactId>jooby-yasson</artifactId> + <version>${project.version}</version> + </dependency> + </dependencies> +</dependencyManagement> + <licenses> <license> <name>The Apache Software License, Version 2.0</name> @@ -36,311 +332,16 @@ </scm> <distributionManagement> - <repository> - <id>ossrh</id> - <name>Nexus Release Repository</name> - <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url> - </repository> <snapshotRepository> <id>ossrh</id> <name>Sonatype Nexus Snapshots</name> <url>https://oss.sonatype.org/content/repositories/snapshots/</url> </snapshotRepository> + <repository> + <id>ossrh</id> + <name>Nexus Release Repository</name> + <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url> + </repository> </distributionManagement> - <!-- THIS FILE IS AUTO GENERATED. DON'T EDIT --> - <properties> - <spotless.check.skip>true</spotless.check.skip> - </properties> - - <dependencyManagement> - <dependencies> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-avaje-inject</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-avaje-jsonb</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-avaje-validator</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-awssdk-v1</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-awssdk-v2</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-caffeine</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-camel</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-cli</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-commons-email</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-conscrypt</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-db-scheduler</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-distribution</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-ebean</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-flyway</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-freemarker</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-graphiql</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-graphql</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-graphql-playground</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-gson</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-guice</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-handlebars</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-hibernate</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-hibernate-validator</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-hikari</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-jackson</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-jasypt</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-jdbi</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-jetty</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-jstachio</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-jte</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-jwt</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-kafka</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-kotlin</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-log4j</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-logback</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-maven-plugin</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-metrics</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-mutiny</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-netty</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-openapi</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-pac4j</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-pebble</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-quartz</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-reactor</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-redis</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-redoc</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-rocker</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-run</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-rxjava3</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-stork</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-swagger-ui</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-test</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-thymeleaf</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-undertow</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-whoops</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-yasson</artifactId> - <version>${project.version}</version> - </dependency> - </dependencies> - </dependencyManagement> - </project>
modules/jooby-pac4j/src/main/java/io/jooby/internal/pac4j/Pac4jSession.java+126 −0 added@@ -0,0 +1,126 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package io.jooby.internal.pac4j; + +import java.time.Instant; +import java.util.Map; + +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import io.jooby.*; +import io.jooby.pac4j.Pac4jUntrustedDataFound; + +class Pac4jSession implements Session { + public static final String PAC4J = "p4j~"; + + public static final String BIN = "b64~"; + + private final Session session; + + public Pac4jSession(@NonNull Session session) { + this.session = session; + } + + @Nullable public String getId() { + return session.getId(); + } + + @NonNull public Value get(@NonNull String name) { + return session.get(name); + } + + @NonNull public Instant getLastAccessedTime() { + return session.getLastAccessedTime(); + } + + public void destroy() { + session.destroy(); + } + + @NonNull public Session setId(String id) { + session.setId(id); + return this; + } + + @NonNull public Value remove(@NonNull String name) { + return session.remove(name); + } + + public boolean isNew() { + return session.isNew(); + } + + @NonNull public Session setNew(boolean isNew) { + session.setNew(isNew); + return this; + } + + @NonNull public Session setLastAccessedTime(@NonNull Instant lastAccessedTime) { + session.setLastAccessedTime(lastAccessedTime); + return this; + } + + public boolean isModify() { + return session.isModify(); + } + + @NonNull public Session setCreationTime(@NonNull Instant creationTime) { + session.setCreationTime(creationTime); + return this; + } + + @NonNull public Session setModify(boolean modify) { + session.setModify(modify); + return this; + } + + public Session renewId() { + session.renewId(); + return this; + } + + @NonNull public Instant getCreationTime() { + return session.getCreationTime(); + } + + @NonNull public Map<String, String> toMap() { + return session.toMap(); + } + + public Session clear() { + session.clear(); + return this; + } + + public Session getSession() { + return session; + } + + public static Context create(Context ctx) { + return new ForwardingContext(ctx) { + @NonNull @Override + public Session session() { + return new Pac4jSession(super.session()); + } + + @Override + public Session sessionOrNull() { + Session session = super.sessionOrNull(); + return session == null ? null : new Pac4jSession(session); + } + }; + } + + @NonNull @Override + public Session put(@NonNull String name, @NonNull String value) { + if (value != null) { + if (value.startsWith(PAC4J) || value.startsWith(BIN)) { + throw new Pac4jUntrustedDataFound(name); + } + } + return session.put(name, value); + } +}
modules/jooby-pac4j/src/main/java/io/jooby/internal/pac4j/SessionStoreImpl.java+17 −38 modified@@ -13,13 +13,10 @@ import static io.jooby.StatusCode.SEE_OTHER_CODE; import static io.jooby.StatusCode.TEMPORARY_REDIRECT_CODE; import static io.jooby.StatusCode.UNAUTHORIZED_CODE; +import static io.jooby.internal.pac4j.Pac4jSession.BIN; +import static io.jooby.internal.pac4j.Pac4jSession.PAC4J; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.util.Base64; +import java.io.*; import java.util.Optional; import org.pac4j.core.context.WebContext; @@ -35,19 +32,15 @@ import org.pac4j.core.exception.http.UnauthorizedAction; import org.pac4j.core.exception.http.WithContentAction; import org.pac4j.core.exception.http.WithLocationAction; +import org.pac4j.core.util.serializer.Serializer; import io.jooby.Context; import io.jooby.Session; -import io.jooby.SneakyThrows; import io.jooby.Value; import io.jooby.pac4j.Pac4jContext; public class SessionStoreImpl implements org.pac4j.core.context.session.SessionStore { - private static final String PAC4J = "p4j~"; - - private static final String BIN = "b64~"; - private Session getSession(WebContext context) { return context(context).session(); } @@ -75,20 +68,17 @@ public Optional<String> getSessionId(WebContext context, boolean createSession) @Override public Optional<Object> get(WebContext context, String key) { - Optional sessionValue = - getSessionOrEmpty(context) - .map(session -> session.get(key)) - .map(SessionStoreImpl::strToObject) - .orElseGet(Optional::empty); - return sessionValue; + return getSessionOrEmpty(context) + .map(session -> session.get(key)) + .flatMap(value -> strToObject(context(context).require(Serializer.class), value)); } @Override public void set(WebContext context, String key, Object value) { - if (value == null || value.toString().length() == 0) { + if (value == null || value.toString().isEmpty()) { getSessionOrEmpty(context).ifPresent(session -> session.remove(key)); } else { - String encoded = objToStr(value); + String encoded = objToStr(context(context).require(Serializer.class), value); getSession(context).put(key, encoded); } } @@ -116,42 +106,31 @@ public Optional<SessionStore> buildFromTrackableSession( @Override public boolean renewSession(WebContext context) { - getSessionOrEmpty(context).ifPresent(session -> session.renewId()); - return true; + var session = getSessionOrEmpty(context); + session.ifPresent(Session::renewId); + return session.isPresent(); } - static Optional<Object> strToObject(final Value node) { + static Optional<Object> strToObject(Serializer serializer, Value node) { if (node.isMissing()) { return Optional.empty(); } String value = node.value(); if (value.startsWith(BIN)) { - try { - byte[] bytes = Base64.getDecoder().decode(value.substring(BIN.length())); - return Optional.of(new ObjectInputStream(new ByteArrayInputStream(bytes)).readObject()); - } catch (Exception x) { - throw SneakyThrows.propagate(x); - } + return Optional.of(serializer.deserializeFromString(value.substring(BIN.length()))); } else if (value.startsWith(PAC4J)) { return Optional.of(strToAction(value.substring(PAC4J.length()))); } return Optional.of(value); } - static String objToStr(final Object value) { + static String objToStr(Serializer serializer, Object value) { if (value instanceof CharSequence || value instanceof Number || value instanceof Boolean) { return value.toString(); } else if (value instanceof HttpAction) { return actionToStr((HttpAction) value); - } - try { - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - ObjectOutputStream stream = new ObjectOutputStream(bytes); - stream.writeObject(value); - stream.flush(); - return BIN + Base64.getEncoder().encodeToString(bytes.toByteArray()); - } catch (IOException x) { - throw SneakyThrows.propagate(x); + } else { + return BIN + serializer.serializeToString(value); } }
modules/jooby-pac4j/src/main/java/io/jooby/internal/pac4j/UntrustedSessionDataDetector.java+23 −0 added@@ -0,0 +1,23 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package io.jooby.internal.pac4j; + +import edu.umd.cs.findbugs.annotations.NonNull; +import io.jooby.Route; +import io.jooby.Session; + +public class UntrustedSessionDataDetector implements Route.Filter { + @Override + @NonNull public Route.Handler apply(@NonNull Route.Handler next) { + return ctx -> { + Session session = ctx.sessionOrNull(); + if (session instanceof Pac4jSession) { + return session; + } + return session == null ? next.apply(ctx) : next.apply(Pac4jSession.create(ctx)); + }; + } +}
modules/jooby-pac4j/src/main/java/io/jooby/pac4j/Pac4jModule.java+6 −1 modified@@ -31,6 +31,7 @@ import org.pac4j.core.http.adapter.HttpActionAdapter; import org.pac4j.core.http.url.UrlResolver; import org.pac4j.core.profile.factory.ProfileManagerFactory; +import org.pac4j.core.util.serializer.Serializer; import org.pac4j.http.client.indirect.FormClient; import org.pac4j.http.credentials.authenticator.test.SimpleTestUsernamePasswordAuthenticator; @@ -345,7 +346,10 @@ public Pac4jModule client( @Override public void install(@NonNull Jooby app) throws Exception { - app.getServices().putIfAbsent(Pac4jOptions.class, options); + var services = app.getServices(); + services.putIfAbsent(Pac4jOptions.class, options); + // Set defaults: + services.putIfAbsent(Serializer.class, options.getSerializer()); var clients = ofNullable(options.getClients()) @@ -488,6 +492,7 @@ public void install(@NonNull Jooby app) throws Exception { if (securityLogic == null) { options.setSecurityLogic(newSecurityLogic(excludes)); } + app.use(new UntrustedSessionDataDetector()); /** For each client to a specific path, add a security handler. */ for (var entry : allClients.entrySet()) {
modules/jooby-pac4j/src/main/java/io/jooby/pac4j/Pac4jOptions.java+27 −1 modified@@ -8,6 +8,8 @@ import java.util.Optional; import org.pac4j.core.config.Config; +import org.pac4j.core.util.serializer.JavaSerializer; +import org.pac4j.core.util.serializer.Serializer; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -52,6 +54,8 @@ public class Pac4jOptions extends Config { private boolean forceLogoutRoutes = false; + private Serializer serializer = new JavaSerializer(); + private Pac4jOptions(Config config) { setClients(config.getClients()); Optional.ofNullable(config.getAuthorizers()).ifPresent(this::setAuthorizers); @@ -353,8 +357,30 @@ public boolean isForceLogoutRoutes() { * * @param logoutUrlPattern It’s the logout URL pattern that the url parameter must match. It is an * optional parameter and only relative URLs are allowed by default. + * @return This instance. */ - public void setLogoutUrlPattern(String logoutUrlPattern) { + public @NonNull Pac4jOptions setLogoutUrlPattern(String logoutUrlPattern) { this.logoutUrlPattern = logoutUrlPattern; + return this; + } + + /** + * Used for save complex object into session while using indirect clients. + * + * @return Serializer, defaults to {@link JavaSerializer}. + */ + public @NonNull Serializer getSerializer() { + return serializer; + } + + /** + * Set serializer for saving complex object into session while using indirect clients. + * + * @param serializer Serializer. + * @return This instance. + */ + public @NonNull Pac4jOptions setSerializer(@NonNull Serializer serializer) { + this.serializer = serializer; + return this; } }
modules/jooby-pac4j/src/main/java/io/jooby/pac4j/Pac4jUntrustedDataFound.java+20 −0 added@@ -0,0 +1,20 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package io.jooby.pac4j; + +import io.jooby.StatusCode; +import io.jooby.exception.StatusCodeException; + +/** + * Occurs when sensitive encoded data is set outside pac4j internals. + * + * @since 2.16.6 + */ +public class Pac4jUntrustedDataFound extends StatusCodeException { + public Pac4jUntrustedDataFound(String message) { + super(StatusCode.FORBIDDEN, message); + } +}
modules/jooby-pac4j/src/main/java/module-info.java+1 −0 modified@@ -13,4 +13,5 @@ requires org.slf4j; requires pac4j.core; requires pac4j.http; + requires jsr305; }
mypatch.patch+107 −0 added@@ -0,0 +1,107 @@ +package io.jooby.internal.pac4j; + +import io.jooby.Session; +import io.jooby.SneakyThrows; +import io.jooby.Value; +import io.jooby.pac4j.Pac4jContext; +import org.pac4j.core.context.session.SessionStore; + +import org.pac4j.core.exception.http.UnauthorizedAction; +import org.pac4j.core.exception.http.WithContentAction; +import org.pac4j.core.exception.http.WithLocationAction; +import org.pac4j.core.util.JavaSerializationHelper; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Base64; +import java.io.*; +import java.util.Optional; + +import static io.jooby.StatusCode.BAD_REQUEST_CODE; + +public class SessionStoreImpl + implements org.pac4j.core.context.session.SessionStore<Pac4jContext> { + + private static final String PAC4J = "p4j~"; + + private static final String BIN = "b64~"; + + private Session getSession(Pac4jContext context) { + return context.getContext().session(); + return session(context.getContext().session()); + } + + private Session session(Session session) { + if (session instanceof Pac4jSession) { + return ((Pac4jSession) session).getSession(); + } + return session; + } + + private Optional<Session> getSessionOrEmpty(Pac4jContext context) { + return Optional.ofNullable(context.getContext().sessionOrNull()); + return Optional.ofNullable(session(context.getContext().sessionOrNull())); + } + + @Override public String getOrCreateSessionId(Pac4jContext context) { + + } + + @Override public boolean renewSession(Pac4jContext context) { + //getSessionOrEmpty(context).ifPresent(session -> session.renewId()); + getSessionOrEmpty(context).ifPresent(Session::renewId); + return true; + } + + + return Optional.empty(); + } + String value = node.value(); + if (value.startsWith(BIN)) { + try { + byte[] bytes = Base64.getDecoder().decode(value.substring(BIN.length())); + return Optional.of(new ObjectInputStream(new ByteArrayInputStream(bytes)).readObject()); + } catch (Exception x) { + throw SneakyThrows.propagate(x); + } + } else if (value.startsWith(PAC4J)) { + return Optional.of(strToAction(value.substring(PAC4J.length()))); + if (value.startsWith(Pac4jSession.BIN)) { + JavaSerializationHelper helper = new JavaSerializationHelper(); + return Optional.of(helper.deserializeFromBase64(value.substring(Pac4jSession.BIN.length()))); + } else if (value.startsWith(Pac4jSession.PAC4J)) { + return Optional.of(strToAction(value.substring(Pac4jSession.PAC4J.length()))); + } + return Optional.of(value); + } + + return value.toString(); + } else if (value instanceof HttpAction) { + return actionToStr((HttpAction) value); + } + try { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + ObjectOutputStream stream = new ObjectOutputStream(bytes); + stream.writeObject(value); + stream.flush(); + return BIN + Base64.getEncoder().encodeToString(bytes.toByteArray()); + } catch (IOException x) { + throw SneakyThrows.propagate(x); + } else if (value instanceof Serializable) { + JavaSerializationHelper helper = new JavaSerializationHelper(); + return Pac4jSession.BIN + helper.serializeToBase64((Serializable) value); + } else { + throw new UnsupportedOperationException("Unsupported type: " + value.getClass().getName()); + } + } + + private static String actionToStr(HttpAction action) { + StringBuilder buffer = new StringBuilder(); + buffer.append(PAC4J).append(action.getCode()); + buffer.append(Pac4jSession.PAC4J).append(action.getCode()); + if (action instanceof WithContentAction) { + buffer.append(":").append(((WithContentAction) action).getContent()); + } else if (action instanceof WithLocationAction) {
tests/src/test/java/io/jooby/i3633/Issue3633.java+125 −0 added@@ -0,0 +1,125 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package io.jooby.i3633; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.pac4j.core.util.serializer.JavaSerializer; +import org.pac4j.http.client.direct.HeaderClient; +import org.pac4j.http.client.indirect.FormClient; +import org.pac4j.http.credentials.authenticator.test.SimpleTestTokenAuthenticator; +import org.pac4j.http.credentials.authenticator.test.SimpleTestUsernamePasswordAuthenticator; + +import io.jooby.MediaType; +import io.jooby.junit.ServerTest; +import io.jooby.junit.ServerTestRunner; +import io.jooby.pac4j.Pac4jModule; +import okhttp3.FormBody; +import okhttp3.Response; + +public class Issue3633 { + private static final String WELCOME = + "<!DOCTYPE html>\n" + + "<html>\n" + + "<head>\n" + + " <title>Welcome Page</title>\n" + + "</head>\n" + + "<body>\n" + + "<h3>Welcome: {0}</h3>\n" + + "<h4><a href=\"/logout\">Logout</a></h4>\n" + + "</body>\n" + + "</html>\n"; + + @ServerTest + public void pac4jShouldNotAllowToSetUntrustedDataOnIndirectClients(ServerTestRunner runner) { + JavaSerializer serializer = new JavaSerializer(); + String externalToken = "b64~" + serializer.serializeToString("hello cwm"); + runner + .define( + app -> { + app.install( + new Pac4jModule() + .client( + conf -> + new FormClient( + "/login", new SimpleTestUsernamePasswordAuthenticator()))); + + app.get( + "/", + ctx -> { + String token = ctx.query("token").value(); + ctx.session().put("token", token); + Object user = ctx.getUser(); + return ctx.setResponseType(MediaType.html).send(String.format(WELCOME, user)); + }); + }) + .dontFollowRedirects() + .ready( + http -> { + http.post( + "/callback?client_name=FormClient", + new FormBody.Builder().add("username", "test").add("password", "test").build(), + rsp -> { + String sid = sid(rsp); + http.header("Cookie", sid); + http.get( + "/?token=" + externalToken, + rsp2 -> { + assertEquals(403, rsp2.code()); + }); + + // success + http.header("Cookie", sid); + http.get( + "/?token=" + "123", + rsp2 -> { + assertEquals(200, rsp2.code()); + }); + }); + }); + } + + @ServerTest + public void pac4jShouldNotCreateSessionOnDirectClient(ServerTestRunner runner) { + JavaSerializer serializer = new JavaSerializer(); + String externalToken = "b64~" + serializer.serializeToString("hello cwm"); + runner + .define( + app -> { + app.install( + new Pac4jModule() + .client( + conf -> new HeaderClient("token", new SimpleTestTokenAuthenticator()))); + + app.get( + "/", + ctx -> { + String token = ctx.header("token").value(); + assertTrue(ctx.sessionOrNull() == null); + // force create session, should be OK due pac4j does nothing on direct client + // (no session is required) + ctx.session().put("token", token); + Object user = ctx.getUser(); + return ctx.setResponseType(MediaType.html).send(String.format(WELCOME, user)); + }); + }) + .dontFollowRedirects() + .ready( + http -> { + http.header("token", externalToken); + http.get( + "/", + rsp2 -> { + assertEquals(200, rsp2.code()); + }); + }); + } + + private String sid(Response response) { + return response.header("Set-Cookie").split(";")[0]; + } +}
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-7c5v-895v-w4q5ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-31129ghsaADVISORY
- github.com/jooby-project/jooby/blob/v2.x/modules/jooby-pac4j/src/main/java/io/jooby/internal/pac4j/SessionStoreImpl.javanvdWEB
- github.com/jooby-project/jooby/blob/v3.6.1/modules/jooby-pac4j/src/main/java/io/jooby/internal/pac4j/SessionStoreImpl.javanvdWEB
- github.com/jooby-project/jooby/commit/3e13562cf36d7407813eae464e0f4b598de15692nvdWEB
- github.com/jooby-project/jooby/security/advisories/GHSA-7c5v-895v-w4q5nvdWEB
News mentions
0No linked articles in our index yet.