From 58cbd6b56dc833ecec1fb4ff6348e5b97f3b44be Mon Sep 17 00:00:00 2001 From: RhiobeT Date: Sun, 12 Apr 2026 19:23:58 +0200 Subject: [PATCH] Remove unplanned path traversal feature --- .../sh/rhiobet/lalafin/api/FilePrivateAPI.java | 4 ++++ .../lalafin/api/internal/RoleAccessService.java | 14 ++++++++++++++ .../java/sh/rhiobet/lalafin/file/FileResource.java | 4 ++++ .../sh/rhiobet/lalafin/file/ViewerResource.java | 4 ++++ 4 files changed, 26 insertions(+) diff --git a/src/main/java/sh/rhiobet/lalafin/api/FilePrivateAPI.java b/src/main/java/sh/rhiobet/lalafin/api/FilePrivateAPI.java index 13ae4b4..9bfb94a 100644 --- a/src/main/java/sh/rhiobet/lalafin/api/FilePrivateAPI.java +++ b/src/main/java/sh/rhiobet/lalafin/api/FilePrivateAPI.java @@ -52,6 +52,10 @@ public class FilePrivateAPI { @Path("/{names: .+}") @Produces(MediaType.APPLICATION_JSON) public Response getFileInfo(List names) { + if (roleAccessService.hasEncodedPathSeparator(names)) { + return Response.status(Response.Status.BAD_REQUEST).build(); + } + if (!roleAccessService.checkRouteAccess(securityIdentity.getRoles(), names) || !adventAccessService.checkEventAccess(names)) { return Response.status(Response.Status.FORBIDDEN).build(); diff --git a/src/main/java/sh/rhiobet/lalafin/api/internal/RoleAccessService.java b/src/main/java/sh/rhiobet/lalafin/api/internal/RoleAccessService.java index 3ec220b..bd2f361 100644 --- a/src/main/java/sh/rhiobet/lalafin/api/internal/RoleAccessService.java +++ b/src/main/java/sh/rhiobet/lalafin/api/internal/RoleAccessService.java @@ -1,5 +1,7 @@ package sh.rhiobet.lalafin.api.internal; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -15,6 +17,18 @@ public class RoleAccessService { @Inject FileApiConfiguration fileApiConfiguration; + public boolean hasEncodedPathSeparator(final List names) { + return names.stream().anyMatch(s -> { + String current = s.getPath(); + while (true) { + String decoded = URLDecoder.decode(current, StandardCharsets.UTF_8); + if (decoded.equals(current)) break; + current = decoded; + } + return current.contains("/") || current.contains("\0") || current.equals("..") || current.equals("."); + }); + } + public boolean checkRouteAccess(final Set userRoles, final List names) { List matchingRoutes = new ArrayList<>(); for (Route route : fileApiConfiguration.routes()) { diff --git a/src/main/java/sh/rhiobet/lalafin/file/FileResource.java b/src/main/java/sh/rhiobet/lalafin/file/FileResource.java index 92ce714..f2818c9 100644 --- a/src/main/java/sh/rhiobet/lalafin/file/FileResource.java +++ b/src/main/java/sh/rhiobet/lalafin/file/FileResource.java @@ -59,6 +59,10 @@ public class FileResource { @GET @Path("/{names: .+}") public Response serve(List names, @HeaderParam("Range") String range) { + if (roleAccessService.hasEncodedPathSeparator(names)) { + return Response.status(Response.Status.BAD_REQUEST).build(); + } + if (!roleAccessService.checkRouteAccess(securityIdentity.getRoles(), names) || !adventAccessService.checkEventAccess(names)) { return Response.status(Response.Status.FORBIDDEN).build(); diff --git a/src/main/java/sh/rhiobet/lalafin/file/ViewerResource.java b/src/main/java/sh/rhiobet/lalafin/file/ViewerResource.java index cd12d99..c005042 100644 --- a/src/main/java/sh/rhiobet/lalafin/file/ViewerResource.java +++ b/src/main/java/sh/rhiobet/lalafin/file/ViewerResource.java @@ -45,6 +45,10 @@ public class ViewerResource { @GET @Path("/{names: .+}/{page}") public Response view(List names, int page) { + if (roleAccessService.hasEncodedPathSeparator(names)) { + return Response.status(Response.Status.BAD_REQUEST).build(); + } + if (!roleAccessService.checkRouteAccess(securityIdentity.getRoles(), names) || !adventAccessService.checkEventAccess(names)) { return Response.status(Response.Status.FORBIDDEN).build();