fix[faustwp]: (#2310, #2311) use hash_equals for secret key comparison, clean up failed blockset extraction#2312
Conversation
…et key comparison, clean up failed blockset extraction Replace timing-vulnerable === comparisons with hash_equals() for the shared secret key in rest_authorize_permission_callback(), wpac_authorize_permission_callback(), and filter_introspection(). The codebase already uses hash_equals() for HMAC validation in auth/functions.php — these three spots were missed. Also sanitize the $_SERVER['HTTP_X_FAUST_SECRET'] superglobal with wp_unslash() and sanitize_text_field() per WordPress coding standards before passing to hash_equals(). Additionally, delete the uploaded blockset file from the target directory when unzip_uploaded_file() fails, preventing orphaned files from accumulating on disk after failed uploads. Closes wpengine#2310 Closes wpengine#2311
|
Currently, we do not support the creation of preview deployments based on changes coming from forked repositories. |
🦋 Changeset detectedLatest commit: 2294d33 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Fran-A-Dev
left a comment
There was a problem hiding this comment.
The REST calls look ok. The GraphQL path might need one other guard. $_SERVER['HTTP_X_FAUST_SECRET'] is sanitized before comparison, but it is still accessed directly without an isset() check. If that header is missing, this could still trigger an undefined index notice. @moonmeister @latenighthackathon do we default to an empty string before calling hash_equals() so the comparison fails cleanly when the header is absent?
|
Good catch to flag this - the existing if ( ! isset( $_SERVER['HTTP_X_FAUST_SECRET'] ) ) {
return $value;
}This should return early before we ever reach the |
Description
This PR addresses two defense-in-depth issues in the FaustWP WordPress plugin:
1. Timing-vulnerable secret key comparisons (#2310)
The shared secret key (
x-faustwp-secretheader) is compared using PHP's===operator in three permission callbacks.===short-circuits on the first byte mismatch, making it theoretically vulnerable to timing side-channel attacks (CWE-208).Changes:
plugins/faustwp/includes/rest/callbacks.php—rest_authorize_permission_callback()(line 422) andwpac_authorize_permission_callback()(line 447):=== $header_key→hash_equals( $secret_key, $header_key )plugins/faustwp/includes/graphql/callbacks.php—filter_introspection()(line 70):!== $_SERVER[...]→! hash_equals()with properwp_unslash()+sanitize_text_field()per WordPress coding standardsThe codebase already uses
hash_equals()for HMAC validation inauth/functions.php:200— these three spots were inconsistent.2. Failed blockset extraction leaves file on disk (#2311)
process_and_replace_blocks()inplugins/faustwp/includes/blocks/functions.phpmoves an uploaded file to the target directory, then attempts extraction. Ifunzip_uploaded_file()fails, the moved file is left at a predictable path underwp-content/uploads/faustwp/blocks/without cleanup.Fix: Call
$wp_filesystem->delete( $target_file )before returning theWP_Error.Related Issues
Closes #2310
Closes #2311
Testing
Confirm the issue (before the fix)
1. Verify
===is used for secret key comparison:2. Verify
!==is used in the GraphQL introspection filter:3. Verify no cleanup on failed unzip:
Confirm the fix
4. Verify all three comparisons now use
hash_equals():5. Verify
$_SERVERis properly sanitized beforehash_equals():6. Verify the cleanup delete is in the correct function (
process_and_replace_blocks, notunzip_uploaded_file):7. No
===remaining for secret key comparison:Run the test suites
phpcs — confirms changes follow WordPress coding standards:
JS tests — confirms no regressions in frontend packages:
WordPress PHPUnit — confirms plugin functionality is intact: