A malicious iframe has been making its rounds due to a broken non-existent security check in the admin section of the Fancybox for WordPress plugin. Samples of affected sites indicate the vulnerability is being used to initiate a drive-by download targeting MSIE browsers (potentially targeting a recently-announced unpatched IE exploit?). The plugin exploit vector results from poor handling of unauthenticated requests to the plugin’s admin options page (taken from fancybox.php):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
function mfbfw_admin_options() { $settings = get_option( 'mfbfw' ); if ( isset($_GET['page']) && $_GET['page'] == 'fancybox-for-wordpress' ) { if ( isset($_REQUEST['action']) && 'update' == $_REQUEST['action'] ) { $settings = stripslashes_deep( $_POST['mfbfw'] ); $settings = array_map( 'convert_chars', $settings ); update_option( 'mfbfw', $settings ); wp_safe_redirect( add_query_arg('updated', 'true') ); die; } else if ( isset($_REQUEST['action']) && 'reset' == $_REQUEST['action'] ) { $defaults_array = mfbfw_defaults(); // Store defaults in an array update_option( 'mfbfw', $defaults_array ); // Write defaults to database wp_safe_redirect( add_query_arg('reset', 'true') ); die; } } register_setting( 'mfbfw-options', 'mfbfw' ); } add_action( 'admin_init', 'mfbfw_admin_options' ); |
This function is registered as part of the admin_init hook, and doesn’t provide for an sanitation of the $_POST['mfbfw']
value when the script is called with action=update
, nor does it provide any measure of access control, resulting in unauthenticated arbitrary writes to the mfbfw
wp_options value (these values are echoed out when a FancyBox object is built). The target of the exploit, according to Securi’s honeypot reports, was the extraCalls
portion of the settings object, designed to contain arbitrary FancyBox API calls:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
/** * Load FancyBox with the settings set */ function mfbfw_init() { $settings = get_option( 'mfbfw' ); $version = get_option( 'mfbfw_active_version' ); echo "\n<!-- Fancybox for WordPress v" . $version . ' -->'; ?> <script type="text/javascript"> /* SNIP: previously echoed FancyBox jQuery omitted for brevity. */ <?php if ( isset($settings['extraCallsEnable']) && $settings['extraCallsEnable'] ) { echo $settings['extraCalls']; echo "\n"; } ?> }) </script> <?php echo "<!-- END Fancybox for WordPress -->\n"; } add_action( 'wp_head', 'mfbfw_init' ); |
So the vulnerability is that unchecked write access is provided to a given DB row, the contents of which will be echoed into a browser in the context of a <script>
tag. The official plugin repo’s development logs indicated that the latest vulnerable version (3.0.2) was committed over two years ago. By all accounts the author was quick to release a fix once the exploit path was discovered, but that fix appears to break the functionality of a portion of the plugin’s options page. The newly patched function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
function mfbfw_admin_options() { $settings = get_option( 'mfbfw' ); if ( isset($_GET['page']) && $_GET['page'] == 'fancybox-for-wordpress' ) { if ( isset($_REQUEST['action']) && 'reset' == $_REQUEST['action'] && check_admin_referer( 'mfbfw-options-options' ) ) { $defaults_array = mfbfw_defaults(); // Store defaults in an array update_option( 'mfbfw', $defaults_array ); // Write defaults to database wp_safe_redirect( add_query_arg('reset', 'true') ); die; } } register_setting( 'mfbfw-options', 'mfbfw' ); } add_action( 'admin_init', 'mfbfw_admin_options' ); |
And a nonce has been added to the reset button in admin.php:
1 2 3 4 5 6 7 |
<form method="post" action=""> <div style="text-align:center;padding:0 0 1.5em;margin:-15px 0 5px;"> <?php wp_nonce_field( 'mfbfw-options-reset' ); ?> <input type="submit" name="mfbfw_update" id="reset" onClick="return confirmDefaults();" class="button-secondary" value="<?php esc_attr_e( 'Revert to defaults', 'mfbfw' ); ?>" /> <input type="hidden" name="action" value="reset" /> </div> </form> |
So the update_option
call has been removed from the admin options function, and a token attempt at access control has been established (hah, security puns). Note, however, that the attempt to verify authentication when a reset is requested is mishandled, because the check_admin_referer
call is attempting to validate a nonce field that has not been set (expecting mfbfw-options-options
vs being set with mfbfw-options-reset
); indeed, this does break reset functionality (check_admin_referer
will fail and display a “Are you sure you want to do this?” message). This doesn’t expose any additional security concerns, but it certainly makes me question the quality of the rest of the plugin, and the fact that this vulnerability went unnoticed and unpatched for two and a half years forces the question of how many installations were compromised by this exploit.
Update 1: A bit more digging around showed additional possible methods of exploit for the same vector. The plugin uses a settings page to provide a configuration interface for some FancyBox API settings, however, the output displayed from the mfbwf
object is not sanitized before being inserted into page elements. Using the method shown above, an attacker could have inserted elements resulting in XSS when the Fancybox for WordPress settings page was accessed. Administrators who had the vulnerable version of this plugin installed should consider resetting their user sessions and credentials.
Update 2: A new patch was released for the plugin that resolves the mis-matched nonce values. It does not, however, resolve the issue of unsanitized script data echoed to the admin panel.
“resulting in a trivial SQLi vector”? Are you sure?
update_option
is a safe function (read the WordPress Codex) and that was *not* the vulnerability.Thanks, I’ve updated the write-up to reflect the fact that the vulnerability did not result from unescaped DB values/SQLi, but rather unauthenticated access via the specific request params.
Handling such (reset) requests outside of the Settings API context in general is bad practice, but you’re right about the nonce field in this case.
A better way around it would be to not use a separate form for the reset action, but rather use a name on the reset button and check for that name when the saved settings are validated by the API, skipping the whole block that confusingly runs on admin_init altogether.
That said, your question about the quality of the rest of the plugin is fair. Given its popularity and current state it deserves a complete security review. Given it a quick glance, I’m fairly certain there’s at least one other issue, which luckily is not as severe as the one that has been patched.
I’d agree with your assessment of better solution to handle the issues noted. I too found another issue; for now I’ll hold off on further discussion to see if any further developments arise, but I would be interested to see any assessment you could provide.
Excuse my ignorance, but what is a zero day?
http://lmgtfy.com/?q=zero+day
wow… no need to be a dick about it