Headline
CVE-2023-2714: help-page.php in groundhogg/tags/2.7.9.8/admin/help – WordPress Plugin Repository
The Groundhogg plugin for WordPress is vulnerable to unauthorized modification of data due to a missing capability check on the ‘check_license’ functions in versions up to, and including, 2.7.9.8. This makes it possible for authenticated attackers, with subscriber-level permissions and above, to change the license key and support license key, but it can only be changed to a valid license key.
1<?php23namespace Groundhogg\Admin\Help;45use Groundhogg\Admin\Tabbed_Admin_Page;6use Groundhogg\Contact;7use Groundhogg\License_Manager;8use Groundhogg\Plugin;9use function Groundhogg\admin_page_url;10use function Groundhogg\create_contact_from_user;11use function Groundhogg\email_kses;12use function Groundhogg\get_db;13use function Groundhogg\get_post_var;14use function Groundhogg\html;15use function Groundhogg\is_event_queue_processing;16use function Groundhogg\is_option_enabled;17use function Groundhogg\is_pro_features_active;18use function Groundhogg\managed_page_url;19use function Groundhogg\permissions_key_url;20use function Groundhogg\remote_post_json;21use function Groundhogg\utils;2223class Help_Page extends Tabbed_Admin_Page {2425 /**26 * Add Ajax actions…27 */28 protected function add_ajax_actions() {29 add_action( 'wp_ajax_groundhogg_doc_search’, [ $this, ‘get_docs_ajax’ ] );30 add_action( 'wp_ajax_groundhogg_fix_missing_tables’, [ $this, ‘fix_missing_tables’ ] );31 add_action( 'wp_ajax_groundhogg_enable_safe_mode’, [ $this, ‘enable_safe_mode’ ] );32 add_action( 'wp_ajax_groundhogg_disable_safe_mode’, [ $this, ‘disable_safe_mode’ ] );33 add_action( 'wp_ajax_groundhogg_submit_support_ticket’, [ $this, ‘submit_ticket’ ] );34 add_action( 'wp_ajax_groundhogg_resave_permalinks’, [ $this, ‘resave_permalinks’ ] );35 add_action( 'wp_ajax_groundhogg_check_support_license’, [ $this, ‘check_license’ ] );36 }3738 /**39 * Checks the provided license to see if it’s valid40 */41 public function check_license() {4243 $license = sanitize_text_field( get_post_var( ‘license’ ) );4445 $result = License_Manager::activate_license_quietly( $license, 12344 );4647 if ( is_wp_error( $result ) ) {48 wp_send_json_error( $result );49 }5051 update_option( 'gh_support_license’, $license );5253 wp_send_json_success();54 }555657 public function resave_permalinks() {58 if ( ! current_user_can( ‘manage_options’ ) ) {59 return;60 }6162 flush_rewrite_rules();6364 wp_send_json_success();65 }6667 public function enable_safe_mode() {68 if ( ! current_user_can( ‘deactivate_plugins’ ) ) {69 return;70 }7172 if ( groundhogg_enable_safe_mode() ) {73 wp_send_json_success();74 }7576 wp_send_json_error();77 }7879 public function disable_safe_mode() {80 if ( ! current_user_can( ‘activate_plugins’ ) ) {81 return;82 }8384 groundhogg_disable_safe_mode();8586 wp_send_json_success();87 }8889 /**90 * Fetch documents that match search from helpscout91 *92 * @return void93 */94 public function get_docs_ajax() {9596 if ( ! current_user_can( ‘view_contacts’ ) ) {97 return;98 }99100 $query = sanitize_text_field( get_post_var( ‘query’ ) );101 $json = remote_post_json( ‘https://help.groundhogg.io/search/typeahead?query=’ . $query, false, ‘GET’ );102 wp_send_json( $json );103 }104105 /**106 * Try to install/repair missing tables107 *108 * @return void109 */110 public function fix_missing_tables() {111112 if ( ! current_user_can( ‘manage_options’ ) ) {113 return;114 }115116 global $wpdb;117118 $dbs = Plugin::instance()->dbs->get_dbs();119 $missing_tables = [];120 $db_errors = [];121 $db_results = [];122123 foreach ( $dbs as $db ) {124 // Try to create the table125 if ( ! $db->installed() ) {126 $db_results[] = $db->create_table();127128 // If still not installed129 if ( ! $db->installed() ) {130 $missing_tables[] = $db->get_table_name();131132 if ( ! empty( $wpdb->last_error ) ) {133 $db_errors[] = $wpdb->last_error;134 }135 }136 }137 }138139 wp_send_json_success( [140 ‘missing_tables’ => $missing_tables,141 ‘db_errors’ => $db_errors,142 ‘db_results’ => $db_results,143 ] );144145 }146147 const SUPPORT_ENDPOINT = 'https://www.groundhogg.io/wp-json/gh/v3/support-3/’;148 const SUPPORT_EMAIL = '[email protected]’;149 const HELP_EMAIL = '[email protected]’;150 const SUPPORT_LOGIN = 'groundhogg’;151152 /**153 * Generate an auto expiring login link for support instead of sending the password.154 *155 * @param $contact Contact156 *157 * @return string158 */159 public function generate_auto_login_link_for_support( $contact ) {160161 $link_url = managed_page_url( ‘auto-login’ );162 $redirect_to = admin_url();163164 $link_url = permissions_key_url( $link_url, $contact, 'auto_login’, 7 * DAY_IN_SECONDS, false );165166 if ( $redirect_to && is_string( $redirect_to ) ) {167 $link_url = add_query_arg( [168 ‘cid’ => $contact->get_id(),169 ‘redirect_to’ => urlencode( $redirect_to ),170 ], $link_url );171 }172173 return $link_url;174 }175176 /**177 * Create a support user178 *179 * @return false|\WP_User180 */181 public function create_support_user() {182183 $user_login = get_option( 'gh_support_user_login’, self::SUPPORT_LOGIN );184185 $user = get_userdatabylogin( $user_login );186187 // No user exists, create one188 if ( ! $user ) {189190 $user_id = wp_create_user( $user_login, wp_generate_password(), self::SUPPORT_EMAIL );191192 if ( is_wp_error( $user_id ) ) {193 wp_send_json_error( $user_id );194 }195196 $user = get_userdata( $user_id );197198 } // User exists, but does not belong to us199 else if ( ! in_array( $user->user_email, [ self::SUPPORT_EMAIL, self::HELP_EMAIL ] ) ) {200 // Set a unique login201 update_option( 'gh_support_user_login’, uniqid( self::SUPPORT_LOGIN . ‘_’ ) );202203 return $this->create_support_user();204 }205206 // Set locale to en_US207 update_user_meta( $user->ID, 'locale’, ‘en_US’ );208209 $user->set_role( ‘administrator’ );210211 return $user;212213 }214215 /**216 * Submit the ticket217 *218 * @return void219 */220 public function submit_ticket() {221222 $args = [223 ‘name’ => sanitize_text_field( get_post_var( ‘name’ ) ),224 ‘email’ => sanitize_email( get_post_var( ‘email’ ) ),225 ‘license’ => get_option( ‘gh_support_license’ ),226 ‘host’ => sanitize_text_field( get_post_var( ‘host’ ) ),227 ‘mood’ => sanitize_text_field( get_post_var( ‘mood’ ) ),228 ‘gh_experience’ => sanitize_text_field( get_post_var( ‘gh_experience’ ) ),229 ‘wp_experience’ => sanitize_text_field( get_post_var( ‘wp_experience’ ) ),230 ‘subject’ => base64_encode( sanitize_text_field( get_post_var( ‘subject’ ) ) ),231 ‘message’ => base64_encode( email_kses( get_post_var( ‘message’ ) ) ),232 ‘system’ => base64_encode( groundhogg_tools_sysinfo_get() ),233 ‘authorized’ => filter_var( get_post_var( ‘authorization’ ), FILTER_VALIDATE_BOOLEAN ) ? ‘Yes’ : 'No’,234 ‘admin_access’ => filter_var( get_post_var( ‘admin_access’ ), FILTER_VALIDATE_BOOLEAN ) ? ‘Yes’ : 'No’,235 ‘safe_mode’ => filter_var( get_post_var( ‘safe_mode’ ), FILTER_VALIDATE_BOOLEAN ),236 ‘login_url’ => wp_login_url(),237 ];238239 // Save these for later240 update_option( 'gh_ticket_defaults’, [241 ‘gh_experience’ => $args[‘gh_experience’],242 ‘wp_experience’ => $args[‘wp_experience’],243 ‘host’ => $args[‘host’],244 ] );245246 if ( $args[‘admin_access’] === ‘Yes’ ) {247248 $user = $this->create_support_user();249250 $contact = create_contact_from_user( $user );251 $contact->unsubscribe();252253 $args[‘login_url’] = $this->generate_auto_login_link_for_support( $contact );254 }255256 $response = remote_post_json( self::SUPPORT_ENDPOINT, $args );257258 if ( is_wp_error( $response ) ) {259 wp_send_json_error( $response );260 }261262 wp_send_json_success();263 }264265 /**266 * Now title actions267 *268 * @return array|array[]269 */270 protected function get_title_actions() {271 return [];272 }273274 /**275 * Adds additional actions.276 *277 * @return mixed278 */279 protected function add_additional_actions() {280 if ( ! is_pro_features_active() ) {281 add_action( 'admin_print_styles’, function () {282 ?>283 <style>284 .nav-tab-wrapper a[href="?page=gh_help&tab=support"] {285 color: #DB741A;286 }287288 .nav-tab-wrapper a[href="?page=gh_help&tab=support"] .dashicons {289 margin-right: 4px;290 }291 </style>292 <?php293 } );294 }295 }296297 /**298 * Get the page slug299 *300 * @return string301 */302 public function get_slug() {303 return 'gh_help’;304 }305306 /**307 * Get the menu name308 *309 * @return string310 */311 public function get_name() {312 return __( ‘Help’ );313 }314315 /**316 * The required minimum capability required to load the page317 *318 * @return string319 */320 public function get_cap() {321 return 'edit_contacts’;322 }323324 public function get_priority() {325 return 2;326 }327328 /**329 * Get the item type for this page330 *331 * @return mixed332 */333 public function get_item_type() {334 // TODO: Implement get_item_type() method.335 }336337 /**338 * Enqueue any scripts339 */340 public function scripts() {341 wp_enqueue_style( ‘groundhogg-admin’ );342 wp_enqueue_style( ‘groundhogg-admin-help’ );343344 if ( $this->get_current_tab() === ‘troubleshooting’ ) {345346 $master_license = get_option( ‘gh_master_license’ );347 if ( ! empty( $master_license ) ) {348 update_option( 'gh_support_license’, $master_license );349 }350351 wp_enqueue_media();352 wp_enqueue_editor();353354 $ip_info = utils()->location->ip_info();355356 if ( $ip_info ) {357 $user_tz = $ip_info[‘time_zone’];358 if ( ! $user_tz ) {359 $user_tz = 'UTC’;360 }361 } else {362 $user_tz = 'UTC’;363 }364365 $user_tz = new \DateTimeZone( $user_tz );366 $wp_tz = wp_timezone();367 $user_date = new \DateTime( 'now’, $user_tz );368 $wp_date = new \DateTime( 'now’, $wp_tz );369370 $dbs = Plugin::instance()->dbs->get_dbs();371 $missing_tables = [];372373 foreach ( $dbs as $db ) {374 if ( ! $db->installed() ) {375 $missing_tables[] = $db->get_table_name();376 }377 }378379 if ( ! function_exists( ‘get_plugins’ ) ) {380 include_once ABSPATH . 'wp-admin/includes/plugin.php’;381 }382383 $active_plugins = get_plugins();384385 $status = [386 ‘ticket_defaults’ => get_option( 'gh_ticket_defaults’, [] ),387 ‘active_plugins’ => $active_plugins,388 ‘cron_is_working’ => is_event_queue_processing(),389 ‘recent_failed_events’ => get_db( ‘events’ )->query( [390 ‘select’ => 'COUNT(ID) as count, error_code, error_message’,391 ‘status’ => 'failed’,392 ‘after’ => time() - ( 30 * DAY_IN_SECONDS ),393 ‘groupby’ => 'error_code’394 ] ),395 ‘required_updates’ => get_plugin_updates(),396 ‘missing_db_tables’ => $missing_tables,397 ‘helper_installed’ => defined( ‘GROUNDHOGG_HELPER_VERSION’ ),398 ‘smtp’ => [399 ‘wordpress’ => \Groundhogg_Email_Services::get_wordpress_service(),400 ‘transactional’ => \Groundhogg_Email_Services::get_transactional_service(),401 ‘marketing’ => \Groundhogg_Email_Services::get_marketing_service(),402 ‘any_service_installed’ => defined( ‘MAILHAWK_VERSION’ ) ||403 defined( ‘GROUNDHOGG_SMTP_VERSION’ ) ||404 defined( ‘GROUNDHOGG_SENDGRID_VERSION’ ) ||405 defined( ‘GROUNDHOGG_ELASTIC_EMAIL_VERSION’ ) ||406 defined( ‘GROUNDHOGG_AWS_VERSION’ )407 ],408 ‘timezone’ => [409 ‘site’ => $wp_tz->getName(),410 ‘user’ => $user_tz->getName(),411 ‘matches’ => $user_date->getOffset() === $wp_date->getOffset(),412 ],413 ‘safe_mode_enabled’ => is_option_enabled( ‘gh_safe_mode_enabled’ ),414 ‘php’ => [415 ‘recommended’ => '7.4’,416 ‘is_recommended’ => version_compare( PHP_VERSION, '7.4’, ‘>=’ )417 ],418 ];419420 wp_enqueue_script( ‘groundhogg-troubleshooter’ );421 wp_enqueue_style( ‘groundhogg-admin-guided-setup’ );422 wp_localize_script( 'groundhogg-troubleshooter’, 'GroundhoggTroubleshooter’, $status );423424 }425 }426427 /**428 * Add any help items429 *430 * @return mixed431 */432 public function help() {433 // TODO: Implement help() method.434 }435436 /**437 * array of [ 'name’, ‘slug’ ]438 *439 * @return array[]440 */441 protected function get_tabs() {442 $tabs = [443 [444 ‘name’ => __( 'Troubleshooting’, ‘groundhogg’ ),445 ‘slug’ => 'troubleshooting’446 ],447 [448 ‘name’ => __( 'Basic Help’, ‘groundhogg’ ),449 ‘slug’ => 'docs’450 ],451 ];452453 return $tabs;454 }455456 public function docs_view() {457458 $topics = [459 [460 ‘title’ => __( 'New to Groundhogg?’, ‘groundhogg’ ),461 ‘description’ => __( 'If you are new to Groundhogg, try browsing our getting started articles to learn what you need to know!’, ‘groundhogg’ ),462 ‘button_text’ => __( 'I need help getting started!’, ‘groundhogg’ ),463 ‘button_link’ => 'https://help.groundhogg.io/collection/1-getting-started’464 ],465 [466 ‘title’ => __( 'Building something?’, ‘groundhogg’ ),467 ‘description’ => __( 'Are you building something custom with Groundhogg? Take a look at our developer oriented articles.’, ‘groundhogg’ ),468 ‘button_text’ => __( 'I need help with development!’, ‘groundhogg’ ),469 ‘button_link’ => 'https://help.groundhogg.io/collection/141-developers’470 ],471 [472 ‘title’ => __( 'Have a question?’, ‘groundhogg’ ),473 ‘description’ => __( 'Someone else may have already asked your question. Check out our FAQs to see if there is an answer for you.’, ‘groundhogg’ ),474 ‘button_text’ => __( 'I have a question!’, ‘groundhogg’ ),475 ‘button_link’ => 'https://help.groundhogg.io/collection/6-faqs’476 ],477 [478 ‘title’ => __( 'Installing an extension?’, ‘groundhogg’ ),479 ‘description’ => __( 'We have detailed setup guides for all of our premium extensions. Find the one you need!’, ‘groundhogg’ ),480 ‘button_text’ => __( 'I need help with an extension!’, ‘groundhogg’ ),481 ‘button_link’ => 'https://help.groundhogg.io/collection/24-extensions’482 ],483 [484 ‘title’ => __( 'Didn\’t find what you need?’, ‘groundhogg’ ),485 ‘description’ => __( 'If you didn\’t find what you were looking for then you can join our support group and ask the community!’, ‘groundhogg’ ),486 ‘button_text’ => __( 'Join the community!’, ‘groundhogg’ ),487 ‘button_link’ => 'https://www.groundhogg.io/fb/’488 ],489 [490 ‘title’ => __( 'Having a technical issue?’, ‘groundhogg’ ),491 ‘description’ => __( 'Use the troublshooter to diagnose potential issues on your site.’, ‘groundhogg’ ),492 ‘button_text’ => __( 'Start the troubleshooter!’, ‘groundhogg’ ),493 ‘button_link’ => admin_page_url( 'gh_help’, [ ‘tab’ => ‘troubleshooting’ ], ‘issues-found’ )494 ],495 [496 ‘title’ => __( 'Need technical help?’, ‘groundhogg’ ),497 ‘description’ => __( 'If you require technical assistance then the best option is to open a support ticket with our advanced support team.’, ‘groundhogg’ ),498 ‘button_text’ => __( 'Open a ticket!’, ‘groundhogg’ ),499 ‘button_link’ => admin_page_url( 'gh_help’, [ ‘tab’ => ‘troubleshooting’ ], ‘ticket’ )500 ],501 ]502503 ?>504 <p></p>505 <div id="docs" class="post-box-grid">506 <?php foreach ( $topics as $topic ): ?>507 <div class="gh-panel">508 <div class="gh-panel-header">509 <h2><?php echo $topic[‘title’] ?></h2>510 </div>511 <div class="inside">512 <p><?php echo $topic[‘description’] ?></p>513 <?php echo html()->e( 'a’, [514 ‘class’ => 'gh-button secondary’,515 ‘href’ => $topic[‘button_link’]516 ], $topic[‘button_text’] ) ?>517 </div>518 </div>519 <?php endforeach; ?>520 </div>521 <?php522 }523524 /**525 * Shows the main div for the troubleshooter526 *527 * @return void528 */529 public function troubleshooting_view() {530 ?>531 <div id="troubleshooter"></div><?php532 }533534 /**535 * Output the basic view.536 *537 * @return mixed538 */539 public function view() {540 ?>541 <div id="troubleshooter"></div><?php542 }543}