Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/groundhogg/admin/contacts/contacts-page.php
+++ b/groundhogg/admin/contacts/contacts-page.php
@@ -78,7 +78,6 @@
add_action( 'wp_ajax_groundhogg_contact_upload_file', [ $this, 'ajax_upload_file' ] );
add_action( 'wp_ajax_groundhogg_edit_contact', [ $this, 'ajax_edit_contact' ] );
add_action( 'wp_ajax_groundhogg_contact_table_row', [ $this, 'ajax_contact_table_row' ] );
- add_action( 'wp_ajax_groundhogg_get_contacts_table', [ $this, 'ajax_get_table' ] );
}
/**
@@ -762,33 +761,6 @@
return $this->process_status_change();
}
-
- public function ajax_get_table() {
-
-// if ( ! current_user_can( 'view_contacts' ) ){
-// return;
-// }
-
- ob_start();
-
- $contacts_table = new TablesContacts_Table();
-
- ?>
- <form method="post" id="contacts-table-form">
- <?php
- $contacts_table->prepare_items();
- $contacts_table->display();
- ?>
- </form>
- <?php
-
- $table = ob_get_clean();
-
- wp_send_json_success( [
- 'html' => $table
- ] );
- }
-
/**
* Save the contact during inline edit
*/
--- a/groundhogg/db/query/where.php
+++ b/groundhogg/db/query/where.php
@@ -473,11 +473,6 @@
*/
public function notIn( $column, $values ) {
- if ( is_string( $values ) && str_starts_with( $values, 'SELECT' ) ) {
- _doing_it_wrong( __METHOD__, 'Use the GroundhoggQuery class for sub queries', '4.5' );
- return $this;
- }
-
$column = $this->sanitize_column( $column );
if ( is_a( $values, Query::class ) ) {
@@ -486,6 +481,11 @@
return $this;
}
+ if ( is_string( $values ) && str_starts_with( $values, 'SELECT' ) ) {
+ _doing_it_wrong( __METHOD__, 'Use the GroundhoggQuery class for sub queries', '4.5' );
+ return $this;
+ }
+
$values = array_values( ensure_array( $values ) );
if ( count( $values ) === 1 ) {
--- a/groundhogg/groundhogg.php
+++ b/groundhogg/groundhogg.php
@@ -3,7 +3,7 @@
* Plugin Name: Groundhogg
* Plugin URI: https://www.groundhogg.io/?utm_source=wp-plugins&utm_campaign=plugin-uri&utm_medium=wp-dash
* Description: CRM and marketing automation for WordPress
- * Version: 4.5.4
+ * Version: 4.5.5
* Author: Groundhogg Inc.
* Author URI: https://www.groundhogg.io/?utm_source=wp-plugins&utm_campaign=author-uri&utm_medium=wp-dash
* Text Domain: groundhogg
@@ -24,7 +24,7 @@
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
-define( 'GROUNDHOGG_VERSION', '4.5.4' );
+define( 'GROUNDHOGG_VERSION', '4.5.5' );
define( 'GROUNDHOGG_PREVIOUS_STABLE_VERSION', '4.5.3' );
define( 'GROUNDHOGG__FILE__', __FILE__ );
--- a/groundhogg/includes/classes/email.php
+++ b/groundhogg/includes/classes/email.php
@@ -417,6 +417,14 @@
*/
public function get_open_tracking_src() {
+ if ( $this->_use_legacy_tracking_links ){
+ return managed_page_url( sprintf(
+ "o/%s/%s",
+ dechex( $this->get_contact()->get_id() ),
+ dechex( $this->get_event()->get_id( true ) )
+ ) );
+ }
+
$payload = implode( '|', [
'o',
dechex( $this->get_contact()->get_id() ),
@@ -1268,7 +1276,14 @@
* @param $contact Contact|int
*/
public function set_contact( $contact ) {
- $this->contact = get_contactdata( $contact );
+
+ $contact = get_contactdata( $contact );
+
+ if ( ! is_a_contact( $contact ) ) {
+ throw new InvalidContactException( 'Invalid contact provided.' );
+ }
+
+ $this->contact = $contact;
}
/**
@@ -1284,6 +1299,10 @@
$event = get_queued_event_by_id( $event );
}
+ if ( ! $event || ! $event->exists() ) {
+ throw new InvalidEventException( 'Invalid event provided.' );
+ }
+
$this->event = $event;
}
--- a/groundhogg/includes/exceptions.php
+++ b/groundhogg/includes/exceptions.php
@@ -10,3 +10,5 @@
class NoItemsException extends Exception {}
class NoContactsException extends Exception {}
class InvalidFiltersException extends Exception {}
+class InvalidContactException extends Exception {}
+class InvalidEventException extends Exception {}
--- a/groundhogg/includes/legacy-contact-query.php
+++ b/groundhogg/includes/legacy-contact-query.php
@@ -1010,11 +1010,11 @@
}
if ( $this->query_vars['after'] ) {
- $where['after'] = "$this->table_name.$this->date_key >= '{$this->query_vars['after']}'";
+ $where['after'] = $wpdb->prepare( "{$this->table_name}.{$this->date_key} >= %s", $this->query_vars['after'] );
}
if ( $this->query_vars['before'] ) {
- $where['before'] = "$this->table_name.$this->date_key <= '{$this->query_vars['before']}'";
+ $where['before'] = $wpdb->prepare( "{$this->table_name}.{$this->date_key} <= %s", $this->query_vars['before'] );
}
if ( ! empty( $this->meta_query_clauses['where'] ) ) {
--- a/groundhogg/includes/shortcodes.php
+++ b/groundhogg/includes/shortcodes.php
@@ -98,7 +98,11 @@
return '';
}
- $email->set_contact( get_contactdata() );
+ try {
+ $email->set_contact( get_contactdata() );
+ } catch ( InvalidContactException $e ) {
+ // never mind
+ }
if ( ! defined( 'GROUNDHOGG_IS_BROWSER_VIEW' ) ) {
define( 'GROUNDHOGG_IS_BROWSER_VIEW', true );
--- a/groundhogg/includes/tracking.php
+++ b/groundhogg/includes/tracking.php
@@ -304,6 +304,20 @@
public static $use_safe_redirect = false;
/**
+ * Show when a link is invalid
+ *
+ * @return void
+ */
+ public function invalid_link_screen() {
+ wp_die( 'This link is currently unavailable.', 'Invalid Link', [
+ 'response' => 404,
+ // translators: %s is the blog name
+ 'link_text' => sprintf( esc_html__( 'Return to %s', 'groundhogg' ), esc_html( get_bloginfo( 'name', 'display' ) ) ),
+ 'link_url' => esc_url( home_url() )
+ ] );
+ }
+
+ /**
* Do a tracking redirect during the template_redirect hook
*/
public function template_redirect() {
@@ -321,71 +335,99 @@
$tracking_action = get_query_var( 'tracking_action' );
$tracking_payload = get_query_var( 'tracking_payload' );
- if ( ! empty( $tracking_payload ) ){
+ try {
- $payload_parts = explode( '.', $tracking_payload );
- $encoded_payload = $payload_parts[0];
- $encoded_signature = $payload_parts[1];
-
- $id_payload = base64url_decode( $encoded_payload );
- $signature = base64url_decode( $encoded_signature );
- $link_parts = explode( '|', $id_payload ); // url | contact | event
- $event_id = absint( hexdec( array_pop( $link_parts ) ) );
- $contact_id = absint( hexdec( array_pop( $link_parts ) ) );
- // in case the url contains `|` we re-implode and start from the back
- $target_url = esc_url_raw( sanitize_text_field( implode( '|', $link_parts ) ) );
+ if ( ! empty( $tracking_payload ) ){
- // signature check failed,
- if ( ! check_signature( $id_payload, $signature, 16 ) ){
+ $payload_parts = explode( '.', $tracking_payload );
+ $encoded_payload = $payload_parts[0];
+ $encoded_signature = $payload_parts[1];
+
+ $id_payload = base64url_decode( $encoded_payload );
+ $signature = base64url_decode( $encoded_signature );
+ $link_parts = explode( '|', $id_payload ); // url | contact | event
+ $event_id = absint( hexdec( array_pop( $link_parts ) ) );
+ $contact_id = absint( hexdec( array_pop( $link_parts ) ) );
+ // in case the url contains `|` we re-implode and start from the back
+ $target_url = esc_url_raw( sanitize_text_field( implode( '|', $link_parts ) ) );
+
+ // signature check failed,
+ if ( ! check_signature( $id_payload, $signature, 16 ) ){
+
+ // todo check against the email log?
+
+ // let's check the payload against the generated content itself
+ // in the event the signature changes, we can test against the actual content
+ $event = get_event_by_queued_id( $event_id );
+
+ if ( ! $event || ! $event->exists() ){
+ $this->invalid_link_screen();
+ return;
+ }
+
+ $email = $event->get_email();
+ $email->set_contact( $contact_id );
+ $email->set_event( $event );
+ $generated_content = $email->build();
+
+ if ( ! str_contains( $generated_content, $encoded_payload ) ){
+ $this->invalid_link_screen();
+ return;
+ }
+
+ }
+
+ set_query_var( 'target_url', $target_url );
+ set_query_var( 'contact_id', $contact_id );
+ set_query_var( 'event_id', $event_id );
+
+ }
+ else if ( $tracking_action === 'click' ) { // legacy tracking link
// todo check against the email log?
- // let's check the payload against the generated content itself
- // in the event the signature changes, we can test against the actual content
+ // if we have a legacy tracking link, let's check against the content
+ $contact_id = absint( get_query_var( 'contact_id' ) );
+ $event_id = absint( get_query_var( 'event_id' ) );
+ $target_url = get_query_var( 'target_url' );
+
$event = get_event_by_queued_id( $event_id );
+
+ if ( ! $event || ! $event->exists() ){
+ $this->invalid_link_screen();
+ return;
+ }
+
$email = $event->get_email();
$email->set_contact( $contact_id );
$email->set_event( $event );
+ // make sure using the legacy tracking link format
+ $email->use_legacy_tracking_links();
+
$generated_content = $email->build();
- if ( ! str_contains( $generated_content, $encoded_payload ) ){
- wp_die( 'Invalid link.' );
+ // rebuild our tracking link url from the provided data
+ $legacy_tracking_url = sprintf( 'c/%s/%s/%s',
+ dechex( $contact_id ),
+ dechex( $event_id ),
+ base64url_encode( $target_url )
+ );
+
+ if ( ! str_contains( $generated_content, $legacy_tracking_url ) ){
+ $this->invalid_link_screen();
+ return;
}
-
}
-
- set_query_var( 'target_url', $target_url );
- set_query_var( 'contact_id', $contact_id );
- set_query_var( 'event_id', $event_id );
-
- } else {
-
- // todo check against the email log?
-
- // if we have a legacy tracking link, let's check against the content
- $contact_id = absint( get_query_var( 'contact_id' ) );
- $event_id = absint( get_query_var( 'event_id' ) );
- $target_url = get_query_var( 'target_url' );
-
- $event = get_event_by_queued_id( $event_id );
- $email = $event->get_email();
- $email->set_contact( $contact_id );
- $email->set_event( $event );
- // make sure using the legacy tracking link format
- $email->use_legacy_tracking_links();
-
- $generated_content = $email->build();
-
- // rebuild our tracking link url from the provided data
- $legacy_tracking_url = sprintf( 'c/%s/%s/%s',
- dechex( $contact_id ),
- dechex( $event_id ),
- base64url_encode( $target_url )
- );
-
- if ( ! str_contains( $generated_content, $legacy_tracking_url ) ){
- wp_die( 'Invalid link.' );
+ else if ( $tracking_action === 'open' ) { // legacy open
+ $contact_id = absint( get_query_var( 'contact_id' ) );
+ $event_id = absint( get_query_var( 'event_id' ) );
}
+ else {
+ return;
+ }
+
+ } catch ( Throwable $e ){
+ $this->invalid_link_screen();
}
$contact = get_contactdata( $contact_id );
--- a/groundhogg/templates/emails/email.php
+++ b/groundhogg/templates/emails/email.php
@@ -17,8 +17,16 @@
wp_die( 'Invalid email.' );
}
-$email->set_contact( get_contactdata() );
-$email->set_event( Plugin::$instance->tracking->get_current_event() );
+try {
+ $email->set_contact( get_contactdata() );
+} catch ( InvalidContactException $e ) {
+
+}
+try {
+ $email->set_event( Plugin::$instance->tracking->get_current_event() );
+} catch ( InvalidEventException $e ) {
+
+}
status_header( 200 );
header( 'Content-Type: text/html; charset=utf-8' );