Apply the style package allowlist to nested entries - #137
Merged
erseco merged 4 commits intoSep 26, 2026
Merged
Conversation
When config.xml sat at the archive root, verify_entries() skipped every entry
containing a slash before checking its extension, so a style ZIP could write
x/shell.php or x/.htaccess into uploads/exelearning-styles/{slug}/. Only
directory entries are skipped now; every file, at any depth, must have an
allowed extension. Directory entries in single-root-folder packages, which
were rejected as extensionless, are accepted too.
Also escape the entry name quoted in rejection messages: core prints settings
errors unescaped, so a crafted entry name ran as markup in wp-admin.
Contributor
Test in WordPress PlaygroundTest the plugin with the code from this branch:
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## hotfix/test-isolation #137 +/- ##
===========================================================
- Coverage 96.87% 96.87% -0.01%
Complexity 864 864
===========================================================
Files 39 39
Lines 4323 4322 -1
===========================================================
- Hits 4188 4187 -1
Misses 135 135
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
erseco
added this pull request to stack #144
September 26, 2026 07:38
Skipping directory entries first let a directory outside the package's root folder (evil/ next to acme/) pass verify_entries(), although it breaks the single-root rule that zip_mixed_roots enforces. extract_safely() dropped it anyway, but the check now runs in the right order.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #136.
Security fix: style package allowlist bypass
ExeLearning_Style_Package::verify_entries()skipped every entry that contained a/before checking its extension, wheneverconfig.xmlsat at the archive root. So a style ZIP could writex/shell.phporx/.htaccessintouploads/exelearning-styles/{slug}/x/. That directory has no deny-PHP guard, so the result was server-side code execution.Uploading a style package requires
manage_options. The fix matters most where that is not the same as full server access:DISALLOW_FILE_MODSorDISALLOW_FILE_EDIT.Change
/) are skipped. Every file, at any depth, must have an allowed extension. The single-root check still runs first, so a directory outside the root folder (evil/next toacme/) is rejected withzip_mixed_roots.esc_html()the entry name. Core prints settings errors unescaped, so a crafted entry name rendered as markup in wp-admin (a low-severity self-XSS).Behaviour changes
acme/,acme/img/) are now accepted. Before, those entries failed the extension check, and most ZIP tools store them.fonts/LICENSE) are now rejected. The bundled eXeLearning themes only use allowed extensions.Tests
x/shell.php,x/.htaccess,x/.user.ini,deep/er/a.phtmlandfonts/LICENSEare rejected;StylesServiceTest's icon fixture no longer includesicons/no-extension, which only installed because of the bypass.readme.txtin the same fixture still covers skipping non-image files.OK (952 tests).