From f9289b36b61e34d98c049313e75569f7ded65e68 Mon Sep 17 00:00:00 2001
From: Valera V Harseko <vharseko@3a-systems.ru>
Date: Wed, 21 Oct 2026 08:53:14 +0000
Subject: [PATCH] GHSA-c5gp-3768-jc59 OAuth2 file token authorization lets an unauthenticated client select files outside the token directory
---
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/FileAccessTokenResolver.java | 46 ++++++++++++++++++++++++++++++++++++++++------
1 files changed, 40 insertions(+), 6 deletions(-)
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/FileAccessTokenResolver.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/FileAccessTokenResolver.java
index fc092f6..42163fb 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/FileAccessTokenResolver.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/FileAccessTokenResolver.java
@@ -12,6 +12,7 @@
* information: "Portions Copyright [year] [name of copyright owner]".
*
* Copyright 2016 ForgeRock AS.
+ * Portions Copyright 2026 3A Systems, LLC.
*/
package org.forgerock.opendj.rest2ldap.authz;
@@ -22,10 +23,12 @@
import static org.forgerock.util.promise.Promises.newExceptionPromise;
import static org.forgerock.util.promise.Promises.newResultPromise;
-import java.io.File;
-import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.InvalidPathException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import org.forgerock.http.oauth2.AccessTokenException;
import org.forgerock.http.oauth2.AccessTokenInfo;
@@ -36,19 +39,30 @@
import org.forgerock.services.context.Context;
import org.forgerock.util.promise.Promise;
-/** A file access token resolver which should only be used for test purpose.*/
+/**
+ * A file access token resolver which should only be used for test purpose.
+ * <p>
+ * The token is the name of a file directly inside the token folder. The token arrives verbatim from the
+ * {@code Authorization} header of an unauthenticated request, so a token which is not a plain file name (it
+ * contains a path separator, or it resolves to a file outside the folder) is refused before any file is opened.
+ */
final class FileAccessTokenResolver implements AccessTokenResolver {
- private final String folderPath;
+ private final Path folder;
FileAccessTokenResolver(final String folderPath) {
- this.folderPath = checkNotNull(folderPath);
+ this.folder = Paths.get(checkNotNull(folderPath)).toAbsolutePath().normalize();
}
@Override
public Promise<AccessTokenInfo, AccessTokenException> resolve(final Context context, final String token) {
+ final Path tokenFile = tokenFile(token);
+ if (tokenFile == null) {
+ return newExceptionPromise(newAccessTokenException(ERR_OAUTH2_FILE_INVALID_TOKEN_NAME.get(token)));
+ }
+
final JsonValue accessToken;
- try (final InputStream stream = new FileInputStream(new File(folderPath, token))) {
+ try (final InputStream stream = Files.newInputStream(tokenFile)) {
accessToken = new JsonValue(Json.readJsonLenient(stream));
} catch (final IOException e) {
return newExceptionPromise(newAccessTokenException(ERR_OAUTH2_FILE_NO_TOKEN.get(token) , e));
@@ -64,4 +78,24 @@
newAccessTokenException(ERR_OAUTH2_FILE_INVALID_JSON_TOKEN.get(token, e.getMessage()), e));
}
}
+
+ /**
+ * Returns the file named by the token inside the token folder, or {@code null} when the token is not a plain
+ * file name. Separators are refused whatever the platform treats as one, and the resolved path must still have
+ * the folder as its parent and the token as its name once normalized, so that {@code .}, {@code ..}, an empty
+ * token or an absolute path never select anything outside the folder.
+ */
+ private Path tokenFile(final String token) {
+ if (token.indexOf('/') >= 0 || token.indexOf('\\') >= 0) {
+ return null;
+ }
+ final Path file;
+ try {
+ file = folder.resolve(token).normalize();
+ } catch (final InvalidPathException e) {
+ return null;
+ }
+ final Path name = file.getFileName();
+ return folder.equals(file.getParent()) && name != null && token.equals(name.toString()) ? file : null;
+ }
}
--
Gitblit v1.10.0