Persistent Cross-Site Scripting (XSS) Vulnerability in Total Security
We were recently doing some basic security checks over WordPress security plugins and identified a possible issue in the plugin Total Security. While the issue we first were looking into turn out to not be exploitable, we noticed a couple of other security vulnerabilities in the plugin. The first one is a persistent cross-site scripting (XSS) vulnerability that is in the 404 log feature, which is disabled by default, due to lack of proper handling of user input data.
While it seems pretty bad that a security plugin has security vulnerabilities of its own, what is more incredible is the response from the developer. It took them 5 days to get back to us and at that point it doesn’t even look like they have really looked over the information we provided them, since they were asking what the solution to the vulnerabilities despite much of that being provided in a link we had included in original message. Yesterday, 17 days later, they released a new version of the plugin, 3.3.8, which didn’t fix either of the vulnerabilities. Oddly the version available before that was 3.4, so they move backed versions as well.
When a 404 request, a request for a page that doesn’t exist, is made that is logged by the plugin using the function fdx_logevent(), located in the file /modules/class-p4.php. You can see that in version 3.4 the value is only sanitized to be used in an SQL statement, esc_sql:
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | function fdx_logevent() { $settings = Total_Security::fdx_get_settings(); global $wpdb; // define( 'DONOTCACHEPAGE', true ); // WP Super Cache and W3 Total Cache recognise this //ignor boots if ($settings['p4_check_2'] && !empty( $_SERVER['HTTP_USER_AGENT'] ) && preg_match( '/(bot|spider)/', $_SERVER['HTTP_USER_AGENT'] ) ) return; //ignor whithow http refer if ($settings['p4_check_3'] && empty($_SERVER['HTTP_REFERER'] ) ) return; $host = esc_sql( $this->fdxgetIp() ); $url = esc_sql( $_SERVER['REQUEST_URI'] ); $referrer = esc_sql( $this->getRefe() ); //log to database $wpdb->insert( $wpdb->base_prefix . 'total_security_log', array( 'timestamp' => current_time( 'timestamp' ), 'host' => $host, 'url' => $url, 'referrer' => $referrer ) ); } |
The value for the request’s referrer comes from the function getRefe(), which doesn’t do any sanitization
44 45 46 47 48 49 50 51 | function getRefe() { if ( isset( $_SERVER['HTTP_REFERER'] ) ) { $theRefe = $_SERVER['HTTP_REFERER']; } else { $theRefe = ''; } return $theRefe; } |
When that is output on the page /wp-admin/admin.php?page=total-security-error-404-log, it is not escaped.
It is brought in here:
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 | $data = $wpdb->get_results( "SELECT id, referrer, timestamp, host, url FROM `" . $wpdb->base_prefix . "total_security_log` $search ORDER BY timestamp DESC;", ARRAY_A ); usort ( $data, array( &$this, 'sortrows' ) ); $per_page = 50; //50 items per page $current_page = $this->get_pagenum(); $total_items = count( $data ); $data = array_slice( $data,( ( $current_page - 1 ) * $per_page ), $per_page ); $rows = array(); $count = 0; //Loop through results and take data we need foreach ( $data as $item => $attr ) { $rows[$count]['timestamp'] = $attr['timestamp']; $rows[$count]['id'] = $attr['id']; $rows[$count]['host'] = $attr['host']; $rows[$count]['uri'] = $attr['url']; $rows[$count]['referrer'] = $attr['referrer']; $count++; } $this->items = $rows; $this->set_pagination_args( array( 'total_items' => $total_items, 'per_page' => $per_page, 'total_pages' => ceil( $total_items/$per_page ) ) ); |
And then output here:
189 | $r = '<div class="crop" id="grenn"><a href="' . $item['referrer'] . '" target="_blank" title="' . $item['referrer'] . '">' . $item['referrer'] . '</a></div>'; |
Proof of Concept
With the 404 log feature enabled in the plugin, request a page that does not exist with your web browser’s referrer set to “<script>alert(document.cookie);</script>”.
Afterwards, when visiting the page /wp-admin/admin.php?page=total-security-error-404-log any available cookies to be shown in alert box on pages from the plugin.
Timeline
- 6/30/2016 – Developer notified.
- 7/5/2016 – Developer notified.
- 7/18/2016 – WordPress.org Plugin Directory notified.
- 7/19/2016 – Removed from WordPress.org Plugin Directory.
- 8/10/2016 – Version 3.4.1 released, which fixes vulnerability.