Persistent Cross-Site Scripting (XSS) Vulnerability in XO Security
When it comes to trying to improve the security surrounding WordPress one of the big impediments is the security industry. One of the things we see them doing is providing misleading and sometimes outright false information to the public about security. One outright falsehood that has been widely spread is that there are lots of brute force attacks against WordPress admin passwords, when based on security companies own evidence are not happening at all. What is really happening are dictionary attacks, which involve an attacker try to login using common passwords. That type of attack is easily protected against by using a strong password, something that WordPress does a good job of helping you do. What might explain why security companies are saying something that isn’t true here is so that they can use the false claim to promote their plugins and services, like we have found Wordfence doing. The problem with this is that every plugin on the website introduces the possibility of a vulnerability, including security plugins.
Take the plugin XO Security that we recently ran across, which is promoted as providing “enhanced login security.” It provides a number of features and by default it will log login attempts. That involves storing and outputting user input data, which needs to be properly handled, but in that wasn’t happening, which was allowing for persistent cross-site scripting (XSS).
When a failed login attempts occurs the plugin’s function failed_login(), in the file /main.php, is run. If the failed login attempt doesn’t involve a username that already exists on the website, the password was logged without being sanitized in version 1.5.2:
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 | function failed_login( $user_name ) { global $wpdb; $login_time = current_time( 'mysql' ); $ip_address = $this->get_ipaddress(); $lang = $this->get_language(); $user_agent = $this->get_user_agent(); $password = filter_input ( INPUT_POST, 'pwd' ); if ( get_user_by( 'login', $user_name ) ) { $password = null; } $wpdb->insert( $this->loginlog_table, array( 'success' => false, 'login_time' => $login_time, 'ip_address' => $ip_address, 'lang' => $lang, 'user_agent' => $user_agent, 'user_name' => $user_name, 'failed_password' =>F% $password ) ); |
When the data was output on the page /wp-admin/users.php?page=xo-security-login-log in 1.5.2 there was no escaping of the password value (in the file /admin.php):
159 160 161 162 163 164 165 | $datas = $wpdb->get_results( $wpdb->prepare( "SELECT `id`,`success`,`login_time` as `logintime`,`ip_address` as `ipaddress`,`lang`,`user_agent` as `useragent`,`user_name` as `loginname`,`failed_password` as `failedpassword`" . "FROM {$loginlog_table}{$where} ORDER BY `{$orderby}` {$order} LIMIT %d, %d;", $start, $per_page ), 'ARRAY_A' ); $this->items = $datas; $this->set_pagination_args( array( 'total_items' => $total_items, 'per_page' => $per_page, 'total_pages' => ceil( $total_items / $per_page ) ) ); |
After we notified the developer of the vulnerability they released version 1.5.3, which fixed it by running the password value through esc_html() when saving it and when outputting it.
Proof of Concept
The following proof of concept will cause any available cookies to be shown in an alert box when visiting the page /wp-admin/users.php?page=xo-security-login-log.
Attempt to login into WordPress with a username that doesn’t exist on the website and the following as the password:
<script>alert(document.cookie);</script>
Timeline
- February 6, 2017 – Developer Notified
- February 7, 2017 – Version 1.5.3 released, which fixes vulnerability.