WordPress.org

Make WordPress Core

Opened 5 months ago

Closed 2 months ago

Last modified 2 months ago

#42948 closed defect (bug) (fixed)

Backbone client sending empty string in X-WP-Nonce header by default in some cases

Reported by: FPCSJames Owned by: adamsilverstein
Milestone: 4.9.5 Priority: normal
Severity: normal Version: 4.9.1
Component: REST API Keywords:
Focuses: Cc:

Description

Environment

I run several instances of "headless" WP 4.9.1, all with the same configuration and set of plugins. All are hosted in a fairly standard shared environment, running CloudLinux + cPanel; LiteSpeed and LSPHP are in use with PHP version 7.0.x (latest stable). These instances feed Backbone client deployments on other domains, which are loading the versions of jQuery, Underscore, Backbone, and the Backbone client (wp-api.js) as included in WP. No authentication is being performed as the script only fetches public posts.

Issue/Steps

One of these deployments started to fail to load single posts (collections continued to work fine). I set Header add Access-Control-Allow-Headers "x-wp-nonce" in .htaccess (which was never previously necessary, as my script does not send a nonce); this produced a 403 with error code rest_cookie_invalid_nonce. I eventually discovered that this was because an empty string was being sent in the X-WP-Nonce header.

Possible source of issue

This function appears at line 828 of wp-api.js as of 4.9.1. When the server sends a X-WP-Nonce header with a response, the base model will compare it with the existing value (which is either undefined or null) and if it doesn't match, set it. This is causing the empty string to be stored, and sent on subsequent requests.

Workaround

To work around this, I changed my code from:

data.model.on("sync", function() {
   data.view = new Views.SingleView({
      model: data.model
   });
   data.view.render()
});

to

data.model.endpointModel.set('nonce', null);
data.model.on("sync", function() {
   data.view = new Views.SingleView({
      model: data.model
   });
   data.view.render()
   data.model.endpointModel.set('nonce', null);
});

thereby setting the nonce to null before and after each sync. It appears that, in wp-api.js, the line:

if ( _.isFunction( model.nonce ) && ! _.isUndefined( model.nonce() ) && ! _.isNull( model.nonce() ) ) {

should be modified to check if model.nonce() returns an empty string by adding && ! _.isBlank( model.nonce() ) or chaning _.isNull to _.isBlank.

Attachments (1)

42948.diff (643 bytes) - added by adamsilverstein 3 months ago.

Download all attachments as: .zip

Change History (13)

This ticket was mentioned in Slack in #core-js by fpcsjames. View the logs.


5 months ago

#2 @FPCSJames
5 months ago

Oops - bit of a mistype on my last sentence. "chaning" should read "changing", and I meant to cite _.isEmpty. It should be "or changing _.isNull to _.isEmpty".

#3 @adamsilverstein
5 months ago

  • Owner set to adamsilverstein
  • Status changed from new to assigned

#4 @adamsilverstein
5 months ago

Thanks for the bug report @FPCSJames - I'll try to work on this soon - I'm also curious to discover why a blank X-WP-Nonce header is being sent in the first place.

#5 @adamsilverstein
3 months ago

  • Milestone changed from Awaiting Review to 4.9.5

In 42948.diff:

  • Don't set the returned nonce if it is blank.

I looked thru the code the creates and returns the nonce and it should never be blank - https://core.trac.wordpress.org/browser/branches/4.9/src/wp-includes/rest-api.php#L788 - unless you have a plugin that is overwriting the pluggable wp_create_nonce function.

@FPCSJames can you give my patch a test and see if it resolves the issue you are seeing?

#6 @FPCSJames
3 months ago

I do not have any plugins overriding wp_create_nonce.

I tested your patch, but unfortunately, it did not work. My original suggestion, checking whether the nonce is empty before options.beforeSend is set, does work.

I believe I found the real issue. Keep in mind that in my use case, I'm embedding wp-api.js and my code outside of a WP deployment, so wpApiSettings is statically generated. Right now, that's only defining the root and versionString parameters.

In wp.api.init(), the line:

attributes.nonce = args.nonce || wpApiSettings.nonce || ''; checks to see if init() is called directly with a nonce argument (it's not - undefined), if it's defined in wpApiSettings (it's not - undefined) and defaulting to an empty string otherwise. Therefore, from what I can tell, either that line needs to change to default to null instead of an empty string, or my change of isNull to isEmpty in sync() needs to be applied.

Last edited 3 months ago by FPCSJames (previous) (diff)

#7 follow-up: @ocean90
3 months ago

Related: #43265, #43266

#43266 uses isEmpty() and should cover this I think.

Last edited 3 months ago by ocean90 (previous) (diff)

#8 in reply to: ↑ 7 @FPCSJames
3 months ago

Replying to ocean90:

Related: #43265, #43265

#43265 uses isEmpty() and should cover this I think.

It appears both patches attached to #43265 use isNull() and isUndefined(), not isEmpty(), unless I'm missing something.

#9 follow-up: @ocean90
3 months ago

Sorry, posted the same ticket number twice… The second one is #43266.

#10 in reply to: ↑ 9 @FPCSJames
3 months ago

Replying to ocean90:

Sorry, posted the same ticket number twice… The second one is #43266.

In that case, I agree - #43266 would do it.

#11 @ocean90
2 months ago

  • Resolution set to fixed
  • Status changed from assigned to closed

In 42852:

REST API JavaScript Client: Support an empty string for nonce to disable sending the X-WP-Nonce header.

Passing a nonce argument with an empty string to wp.api.init() now does no longer fall back to wpApiSettings.nonce. This makes it possible to stop sending nonce headers, for example to a read-only endpoint on another site in a multisite install.

Props adamsilverstein, FPCSJames, ocean90, swissspidy.
Fixes #42948, #43266.

#12 @ocean90
2 months ago

In 42854:

REST API JavaScript Client: Support an empty string for nonce to disable sending the X-WP-Nonce header.

Passing a nonce argument with an empty string to wp.api.init() now does no longer fall back to wpApiSettings.nonce. This makes it possible to stop sending nonce headers, for example to a read-only endpoint on another site in a multisite install.

Merge of [42852] to the 4.9 branch.

Props adamsilverstein, FPCSJames, ocean90, swissspidy.
See #42948, #43266.

Note: See TracTickets for help on using tickets.