Skip to content

bug: reject path traversal in uploaded asset file names and asset writes - #103

Open
devin-ai-integration[bot] wants to merge 1 commit into
develop-7.0.xfrom
devin/1787182905-asset-upload-path-traversal
Open

devin-ai-integration[bot] wants to merge 1 commit into
develop-7.0.xfrom
devin/1787182905-asset-upload-path-traversal

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Aug 19, 2026

Copy link
Copy Markdown

Summary

Admin asset uploads used the client supplied multipart filename verbatim: StaticAssetServiceImpl.createStaticAssetFromFile only replaced the configured invalid chars (+,*,%), so /, \ and .. survived into buildAssetURLStaticAsset.fullUrl → the FILESYSTEM branch of StaticAssetStorageServiceImpl.createStaticAssetStorage, which normalized workArea + fullUrl and wrote it with a raw FileOutputStream before any containment check. An authenticated admin could upload filename="../../../../opt/app/webapps/ROOT/x.jsp" and write arbitrary bytes anywhere the server process can reach (RCE via a dropped webshell).

The url is now assembled from sanitized segments only, and the filesystem writer independently refuses to write outside the work area.

StaticAssetServiceImpl

  • stripPathInformation — strips control characters, reduces to FilenameUtils.getName, rejects bare ./..; applied to the upload filename in normalizeFileExtension, createStaticAssetFromFile and createStaticAsset.
  • sanitizePathSegment on entityType/entityId, sanitizeAssetPath on the optional multi-segment fileName property (drops empty/. segments, rejects ..).
  • validateAssetURL — final assertion that the assembled url is an already-normalized absolute path.

StaticAssetStorageServiceImpl

  • validateFileSystemAssetUrl(fullUrl) — rejects any url that is not already normalized (covers ..\ too, via separatorsToUnix).
  • getDestinationFile(workArea, fullUrl) — replaces the ad-hoc new File(FilenameUtils.normalize(...)); rejects .. segments and requires destFile.getCanonicalPath() to start with the work-area canonical path + separator, so symlinked directories inside the work area cannot be used to escape either. Legit urls keep their directory structure (the read path hashes the same url).

common.properties

  • disabled.file.extensions now also denies active-content/executable extensions (jsp, jspx, html, svg, php, asp, js, jar, class, swf, …) instead of only pdf. allowed.file.extensions remains available as an allow-list override.

Verification

Maven Central and the Broadleaf nexus are blocked in this environment, so the module cannot be built here. The new methods were extracted verbatim into standalone harnesses and executed against the real commons-io / commons-lang3 jars:

  • url building/sanitization: 28 payload checks pass (traversal filenames, Windows separators, control chars, protocol-prefixed fileName, entity segment traversal).
  • storage sink: 13 checks pass — legit urls resolve inside the work area; /product/../../../../opt/tomcat/webapps/ROOT/shell.jsp, \..\..\shell.jsp, /link/shell.jsp (symlink), empty and / urls are all rejected.

JUnit tests added in StaticAssetServiceImplTest and the new StaticAssetStorageServiceImplTest cover the same cases.

Session: https://app.devin.ai/sessions/dac94c434ef9456fa6da0de2e359f39a

Devin-Org: engineering


Devin Review

Status Commit
⚪ Not started

Run Devin Review

Open in Devin Review (Staging)

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.

0 participants