All-in-One Event Calendar stored XSS and SQL injection

All-in One Event Calendar by Time.ly is a WordPress plugin with 100,000+ active installs according to statistics provided by WordPress. Two critical bugs were found in the plugin by Klikki in March 2016. Both are exploitable by unauthenticated attackers. The problems were fixed in version 2.4.0 released on May 17.

Details

Any unexpected error condition within the plugin code causes it to be automatically disabled. A notification is created to inform the site administrators about the problem. The error report contains a stack trace and URL of the problematic HTTP request. This notification is shown on top of the WordPress Dashboard whenever an administrator logs on the system.

The contents of this error notification aren’t adequately sanitized. An attacker may intentionally create an error condition and include HTML, including <script> tags, in the URL. The HTML code would be then rendered on top of the Dashboard.

An attacker exploiting this bug can perform HTTP requests on behalf of administrators logging on the system. Under default configuration this leads to server-side compromise.

The following diff between versions 2.3.12 and 2.4.0 shows the bug and fix implemented by Time.ly:

                $trace     = nl2br( $exception->getTraceAsString() );
                $ident     = sha1( $trace );
                if ( ! empty( $trace ) ) {
-                       $request_uri  = $_SERVER['REQUEST_URI'];
+                       $request_uri  = strip_tags($_SERVER['REQUEST_URI']);
+                       // Limit URL to 100 characters
+                       $request_uri = substr($request_uri, 0, 100);
                        $button_label = __( 'Toggle error details', AI1EC_PLUGIN_NAME );
                        $title        = __( 'Error Details:', AI1EC_PLUGIN_NAME );
                        $backtrace    = <<<JAVASCRIPT

One way to produce an exploitable error condition is in conjunction with the SQL injection bug.

SQL injection

When calendar events are retrieved in XML format, a parameter events_per_page is not properly sanitized. Even though a prepared SQL query is used, this variable is concatenated in the statement template instead of using a “?” placeholder.

The following diff shows the bug and fix:

                // Even if there ARE more than 5 times the limit results - we shall not
                // try to fetch and display these, as it would crash system
+               $limit = preg_replace('/\D/', '', $limit);
                $upper_boundary = $limit;
                if (
                        $settings->get( 'agenda_include_entire_last_day' ) &&

Any results or errors from the SQL query aren’t shown, so blind SQL injection techniques have to be used to retrieve information from the database.

Vendor response

Time.ly was contacted on March 28 and the patch was released on May 17, 2016.

Credits

The vulnerability was discovered and researched by Jouko Pynnönen of Klikki Oy, Finland.

Updated May 18: this is a blind SQL injection regardless of error displaying/logging settings.