Skip to content

prevent zip-slip in DeploymentEngine.addAsWebResources#1227

Merged
robertlazarski merged 1 commit into
apache:masterfrom
rootvector2:addaswebresources-zip-slip
Jul 1, 2026
Merged

prevent zip-slip in DeploymentEngine.addAsWebResources#1227
robertlazarski merged 1 commit into
apache:masterfrom
rootvector2:addaswebresources-zip-slip

Conversation

@rootvector2

Copy link
Copy Markdown
Contributor

found while auditing the aar deployment path: addAsWebResources writes each WWW/ archive entry through new File(out, entryName) without containment, so a crafted aar entry like WWW/../../evil.jsp escapes the per-service web resource directory and writes to an arbitrary path; the fix canonicalizes each target and skips entries that resolve outside out.

Signed-off-by: rootvector2 <dxbnaveed.k@gmail.com>
@cortlepp

Copy link
Copy Markdown
Member

Thanks for the contribution! The change looks good to me from a first glance, it does not seem like this could break any legitimate usecase.

@robertlazarski wdyt?

@rootvector2

Copy link
Copy Markdown
Contributor Author

right, the containment check only rejects entries whose canonical path escapes out, so any legitimate WWW/ entry still resolves under the per-service dir and writes exactly as before. only crafted ../ entries get skipped.

@robertlazarski robertlazarski merged commit c2f5d68 into apache:master Jul 1, 2026
5 checks passed
@robertlazarski

Copy link
Copy Markdown
Contributor

Thanks for this @rootvector2, and thanks @cortlepp for taking a look! 🙌

I went through the containment logic carefully and it's correct — nicely
done. A couple of details I want to call out because they're easy to get
wrong and you got them right:

  • The outPath + File.separator guard on the prefix check. A plain
    startsWith(outPath) would let a sibling directory like .../svc-evil sneak
    past a target of .../svc, and this closes that gap.
  • The targetPath.equals(outPath) clause. The archive's root WWW/ entry
    strips down to an empty name, so it resolves to out itself — without the
    equals check, that legitimate entry (the one that creates the per-service
    dir) would get skipped.

Using getCanonicalPath() instead of getAbsolutePath() is also the right
call: it collapses .. and resolves symlinks before the comparison, and
absolute-path entries get re-rooted by new File(parent, child) anyway. I
confirmed the test genuinely drives the vulnerable path via reflection and
that a WWW/../../ entry resolves outside the target and gets skipped as
expected. It also has the ASF license header and follows the module's JUnit
convention. 👍

I also checked the other zip-extraction spots — the one in
deployment/util/Utils.java routes entry names through
TempFileManager.createTempFile() with generated names, so it isn't exposed
to the same traversal. No companion fix needed; the scope here is spot on.

LGTM — merging. Thanks again for the contribution!

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.

3 participants