WishlistCast Plugin Security

A client of mine recently had some dramas with HostGator making an unannounced modification to her server’s mod_security which broke her site – specifically, it blocked Nanacast from talking to WishlistCast – so her new members that bought her online program were not getting added to her Wishlist membership. You can imagine the implications to her business. This is the exact issue I describe in more detail here, which is the most common cause of “Wishlistcast not working” emails and skype messages I receive. When speaking to Hostgator support, they provided my client with this information:

(11:03:30pm)HG Support.:I would highly recommend you contact that plugins developer. Any developer worth thier salt knows sending anything via GET is highly insecure.
(11:03:44pm)HG Support.:This is especially true when sending a security code, like was the case here.
(11:03:50pm)client:which developer
(11:03:57pm)HG Support.:The one for wishlist
(11:03:57pm)client:wishlist
(11:04:27pm)HG Support.:Sending a jumble of letters like that is seen by nearly any security software as a spam request, cause frankly it looks like one
(11:04:39pm)client:oh ok
(11:04:56pm)HG Support.:Combine this with WordPress’s javascript concatenation, and you have a spammy request URI and jumbled javascript being sent via an ajax call to another file.

Anyway, ignoring the complete FUD about javascript concatenation and Ajax requests (which have nothing to do with anything here), and the dig at my potential lack of developer salt-worthiness (really? I’m crushed!), I thought it would be constructive to transparently explain the design and security implications of using the Wishlistcast plugin. I am open to any constructive feedback and suggestions for improvement.

Background / Overview

The WishlistCast plugin adds an api end-point URL to your Wishlist site (http://www.yoursites.com/wp-content/plugins/wishlistcast/wishlistcast_api.php), allowing Nanacast (or technically another program) to add new users to your Wishlist membership.
When a new user is added to your Nanacast membership, Nanacast makes an Outgoing API call to the WishlistCast plugin API requesting it to “add a new user”. The Outgoing API call is a HTTP request – and the information WishlistCast needs to authorise and fulfill the request is sent as a combination of GET and POST parameters.
In order to authenticate the request, WishlistCast uses a “security_code” parameter, which is unique to your wishlist installation. You can think of this as an “API key”.
Because Nanacast can be configured to send multiple (up to 5) Outgoing API requests, and each of these can go to potentially different wishlist installations, the security_code parameter is sent as a GET parameter (so it can hold a different value for each Outgoing API Request).
In English what this means is – if you have multiple Wishlist membership sites, and you want to add someone to single Nanacast membership and have this add them to all of your wishlist sites, Nanacast needs to talk to each site individually. To do this within Nanacast’s design, you must pass the security_code as a GET parameter so it is unique to each outgoing call.

Security Implications of Using GET Parameters

The main security concerns around GET parameters, is that – because they form part of the URL – they are cached in a user’s browser, and are also open to being “packet sniffed” if sent over a WiFi network (e.g. browsing at your local coffee shop). They are also commonly recorded in your server’s history logs – where they are available if someone has access to (or hacks) your computer server.
However – in our case – it is not users who are browsing to the wishlistcast_api.php URL – so there are no concerns with browser history or WiFi packet sniffing.
The HTTP request is being sent from Nanacast directly to WishlistCast. It is a computer-to-computer call, with no humans or browsers in between.
So – the only real issue is that the URL with the security_code parameter might be visible in your server log. Of course, if someone has access to your server log, there are far easier ways for them to do damage than trying to figure out how to hack some obscure wishlistcast_api…

How WishlistCast Handles the Nanacast Request

Here is the code from the top of the WishlistCast plugin:
$f = array_merge( $_POST, $_GET );

// v1.4.6 introduce transaction logging with replay capability (for use by support)
$replaying = false;
if ( array_key_exists( 'replay', $f ) || !empty( $f['replay'] ) ) {
 // Note: The transaction replay functionality is not included in the public release
 if ( file_exists( dirname( __FILE__ ) . '/replay/replay_txns.php' ) ) {
 include_once(dirname( __FILE__ ) . '/replay/replay_txns.php');
 }
}

if ( !array_key_exists( 'mode', $f ) || $f['mode'] == '' ) {
 return; // Not executing as a result of being called from Nanacast - probably plugin activation
}
As you can see – the very first line of code merges the GET and POST parameters into a single array of parameters (called “$f”) – with priority given to the GET parameters in case of duplicate parameter names.
Then there is a block of code that checks if this is a transaction replay. The code to support “replay” functionality  is only available in my personal copy of the plugin and is used for support purposes – so even if someone sent a “replay” parameter, the file_exists check would fail.
Finally, there is a “sanity check” test to see if the “mode” parameter is present. This early check essentially tests to see if this call most likely originated from Nanacast, which ALWAYS sends a “mode” parameter. If this “mode” parameter is not present, the code returns (ends) immediately. You can see this for yourself if you visit the wishlistcast_api.php URL in your browser – you will just get a blank screen.
After this, there is some code to hook WishlistCast to WordPress (so we can talk to Wishlist) as well as set up logging. Note that adding Wordpress happens AFTER we have already grabbed our GET and POST parameters into $f. WordPress is not processing our parameters or doing any kind of javascript concatenation or Ajax mumbo-jumbo.
Then, there is this code to authenticate the request:
if ( isset( $f['verify_field'] ) && isset( $f['verify_value'] ) && ($f[$f['verify_field']] != $f['verify_value']) ) {
 logmsgandClose( 'Request ignored. Verification failed: ' . $f['verify_field'] . ' value is [' . $f[$f['verify_field']] . ']. Verification value is [' . $f['verify_value'] . ']' );
 return;
}

// Check secret key matches Wishlist Generic secret key from Wishlist Integration panel
if ( class_exists( 'WLMAPI' ) ) {
 $wishlist_security_code = WLMAPI::GetOption( 'genericsecret' );
 if ( $wishlist_security_code != '' && $wishlist_security_code != $f['security_code'] ) {
 logmsg( "Incoming Secret Key '" . $f['security_code'] . "' does not match the Secret Key in WishList Generic Integration settings", PEAR_LOG_NOTICE );
 die( "Incoming Secret Key '" . $f['security_code'] . "' does not match the Secret Key in WishList Generic Integration settings" );
 }
 unset( $wishlist_security_code );
}
The first block is an undocumented feature that allows you to add a verification field and value to the request – which allows you to control whether a particular request is handled or not. The specific use case for this is a client I had who wanted to manually verify payments before adding users to a membership. So I created a hidden “is_paid” field inside their nanacast membership with a default value of “N”, and on receipt of payment, he would edit the users nanacast membership and change the field to a “Y”. Ugly but effective. The verify field name was set to is_paid and the verify value was set to “Y”.
The second block compares the security_code parameter with the Wishlist Generic Secret code from your Wishlist installation.
You will notice that (if logging is enabled) I do log the (incorrect) security code that was passed in, however I do NOT log the expected value. So someone browsing the wishlistcast log file would not be able to use this to uncover the real security code.

TL; DR – Short Version

The wishlistcast_api code is designed with a set of sensible security checks at the start of the code to ensure that only valid, authenticated requests are processed. All of the code in the wishlistcast plugin follows best coding security practises to the best of my knowledge.

What About Using HTTPS?

You could install a SSL certificate on your site and send the request using an encrypted HTTPS call. Because the call is secured at the network level, this would make the call unreadable to an attacker who is able to monitor the network traffic between nanacast and your wishlist server. However, the URL (with parameters) would probably still appear unencrypted in your server logs, so this would result in NO improvement in security.

What Do You Think?

I am open to comments, constructive feedback and suggestions for improvement. Please comment below!

Leave a Reply

Your email address will not be published. Required fields are marked *