-
Notifications
You must be signed in to change notification settings - Fork 825
WooCommerce Analytics: Improve get_current_url()
and URL sanitization
#44679
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
01fc346
56e6f5a
c39a8f5
4853b2e
8dc316f
07d2cf1
3dd77b8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
Significance: patch | ||
Type: fixed | ||
Comment: enhance sanitize logic, no change log need since not released yet |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -375,22 +375,18 @@ private function maybe_record_engagement( $properties = array() ) { | |
return; | ||
} | ||
|
||
$event_js = $this->process_event_properties( 'woocommerceanalytics_session_engagement', $properties ); | ||
$add_engagement_to_cookie_js = " | ||
const cookies = document.cookie.split('; ').reduce((acc, cookie) => { | ||
const [name, value] = cookie.split('='); | ||
acc[name] = value; | ||
return acc; | ||
}, {}); | ||
|
||
if (cookies.woocommerceanalytics_session) { | ||
let sessionData = JSON.parse(decodeURIComponent(cookies.woocommerceanalytics_session)); | ||
sessionData.is_engaged = true; | ||
document.cookie = `woocommerceanalytics_session=\${JSON.stringify(sessionData)}; expires=\${sessionData.expires}; path=/; secure; samesite=strict`; | ||
} | ||
"; | ||
|
||
wc_enqueue_js( $add_engagement_to_cookie_js ); | ||
$event_js = $this->process_event_properties( 'woocommerceanalytics_session_engagement', $properties ); | ||
|
||
$session_data = $this->get_session_cookie(); | ||
if ( empty( $session_data ) ) { | ||
return; | ||
} | ||
$session_data['is_engaged'] = true; | ||
$session_data['landing_page'] = rawurlencode( $session_data['landing_page'] ); | ||
$encoded_session_data = wp_json_encode( $session_data ); | ||
$cookie_js = "document.cookie = 'woocommerceanalytics_session={$encoded_session_data}; expires={$session_data['expires']}; path=/; secure; samesite=strict';"; | ||
wc_enqueue_js( $cookie_js ); | ||
|
||
wc_enqueue_js( "_wca.push({$event_js});" ); | ||
$this->engaged_session = true; | ||
} | ||
|
@@ -408,16 +404,16 @@ public function maybe_start_session() { | |
$this->is_new_session = true; | ||
|
||
$session_expiration = $this->get_session_expiration_time(); | ||
$event_js = $this->process_event_properties( 'woocommerceanalytics_session_started' ); | ||
$cookie_js = " | ||
const sessionData = JSON.stringify({ | ||
session_id: '$this->session_id', | ||
landing_page: encodeURIComponent('$this->landing_page'), | ||
expires: '$session_expiration', | ||
}); | ||
document.cookie = `woocommerceanalytics_session=\${sessionData}; expires=$session_expiration; path=/; secure; samesite=strict`; | ||
"; | ||
$session_data = array( | ||
'session_id' => $this->session_id, | ||
'landing_page' => rawurlencode( $this->landing_page ), | ||
'expires' => $session_expiration, | ||
); | ||
$encoded_data = wp_json_encode( $session_data ); | ||
$cookie_js = "document.cookie = 'woocommerceanalytics_session={$encoded_data}; expires={$session_expiration}; path=/; secure; samesite=strict';"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I considered using PHP to set the cookie, but I realized that it might not be the best approach for our use case since we may want to delay setting the cookie until getting user consent for tracking in the future implementation. Additionally, PHP cookie can only be set before any output is sent to the browser. When I tried to set the cookie using PHP, I encountered an error message saying “Cannot send session cookie - headers already sent.”. |
||
wc_enqueue_js( $cookie_js ); // save the session cookie for further events in the session | ||
|
||
$event_js = $this->process_event_properties( 'woocommerceanalytics_session_started' ); | ||
wc_enqueue_js( "_wca.push({$event_js});" ); // trigger session started event | ||
} | ||
} | ||
|
@@ -819,7 +815,15 @@ public function get_landing_page() { | |
* @return array | ||
*/ | ||
public function get_session_cookie() { | ||
return json_decode( sanitize_text_field( wp_unslash( $_COOKIE['woocommerceanalytics_session'] ?? '' ) ), true ) ?? array(); | ||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON is decoded and validated below. We don't need to sanitize the cookie value because we're not outputting it but decoding it as JSON. Sanitization might break the JSON. | ||
$raw_cookie = isset( $_COOKIE['woocommerceanalytics_session'] ) ? wp_unslash( $_COOKIE['woocommerceanalytics_session'] ) : ''; | ||
|
||
if ( ! $raw_cookie ) { | ||
return array(); | ||
} | ||
|
||
$decoded = json_decode( $raw_cookie, true ); | ||
return is_array( $decoded ) ? $decoded : array(); | ||
} | ||
|
||
/** | ||
|
@@ -828,7 +832,7 @@ public function get_session_cookie() { | |
* @return string | ||
*/ | ||
public function get_current_url() { | ||
return sanitize_url( wp_unslash( ( empty( $_SERVER['HTTPS'] ) ? 'http' : 'https' ) . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]" ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidatedNotSanitized -- actually escaped with sanitize_url. | ||
return home_url( add_query_arg( null, null ) ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I replaced usage of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The echo sanitize_url( wp_unslash( ( "https://example.com/?test='+window.alert('XSS')+'' ) );
https://example.com/?test='+window.alert('XSS')+ // nothing is escaped |
||
} | ||
|
||
/** | ||
|
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If
landing_page
contains a malicious URL/?test='+window.alert('XSS')+'
, it would get interpolated directly into the JavaScript:Now, we encode the entire object via PHP to avoid any potential attacks.