PluginProbe
Tracking Code Manager / 2.3.0
Tracking Code Manager v2.3.0
2.8.0 2.7.0 trunk 1.11.8 1.11.9 1.12.0 1.12.1 1.12.2 1.12.3 1.4 1.5 2.0.0 2.0.1 2.0.13 2.0.14 2.0.15 2.0.16 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.1.0 2.2.0 All 29 releases
← All changes | includes/classes/utils/Options.php +18 -60 trunk2.3.0 View file →
@@ -3,13 +3,10 @@
3 3 exit;
4 4 }
5 5
6 6 class TCMP_Options {
7 + public function __construct() {
7 8
8 - private $request_data;
9 -
10 - public function __construct() {
11 - $this->request_data = array();
12 9 }
13 10
14 11 //Cache
15 12 private function getCacheName( $array ) {
@@ -97,42 +94,24 @@
97 94 $key = $this->get_key( $key );
98 95 $wp_session[ $key ] = $value;
99 96 }
100 97
101 - // Request-scoped state.
102 - //
103 - // These values are the plugin's own per-request scratch space: the post being
104 - // rendered, the ids of snippets already written, the pending admin notices.
105 - // Every one of them is produced by this plugin during the request and read
106 - // back by it later in the same request. None is ever legitimately supplied by
107 - // the client.
108 - //
109 - // getRequest() used to fall back to $_POST['TCM_' . $key] whenever a key was
110 - // absent from the in-memory store, which turned all of that internal state
111 - // into an unauthenticated input surface. That is CVE-2026-15180: a POST of
112 - // TCM_ErrorMessages[0] landed in the admin notice box on the next page render.
113 - // The fallback was already vestigial — nothing in the plugin has written to
114 - // $_POST['TCM_*'] since 2.5.0 (RDU-1630) — so it is removed outright rather
115 - // than gated behind a nonce, which would have kept an attacker-influenced
116 - // path alive for no caller's benefit.
117 - //
118 - // Consequence for removeRequest(): unsetting the POST field is no longer a
119 - // way to clear a key, so it now clears the in-memory entry. That also fixes
120 - // the clean-up it was always meant to perform — writeMessages( clean: true )
121 - // previously only ever removed a POST field that the plugin itself never set,
122 - // so an internally queued notice survived its own render and could be emitted
123 - // a second time by the next writeMessages() call in the same request
124 - // (includes/admin/editor.php does exactly that).
98 + //$_REQUEST
99 + //However WP enforces its own logic - during load process wp_magic_quotes() processes variables to emulate magic quotes setting and enforces $_REQUEST to contain combination of $_GET and $_POST, no matter what PHP configuration says.
125 100 private function removeRequest( $key ) {
126 - unset( $this->request_data[ $key ] );
101 + $key = $this->get_key( $key );
102 + if ( isset( $_POST[ $key ] ) ) {
103 + unset( $_POST[ $key ] );
104 + }
127 105 }
128 106 private function getRequest( $key, $default = false ) {
107 + $key = $this->get_key( $key );
129 108 $result = $default;
130 - if ( isset( $this->request_data[ $key ] ) ) {
131 - if ( is_object( $this->request_data[ $key ] ) ) {
132 - $result = clone $this->request_data[ $key ];
109 + if ( isset( $_POST[ $key ] ) ) {
110 + if ( is_object( $_POST[ $key ] ) ) {
111 + $result = clone $_POST[ $key ];
133 112 } else {
134 - $result = $this->request_data[ $key ];
113 + $result = $_POST[ $key ];
135 114 }
136 115 $result = $this->recursive_wp_kses( $result );
137 116 }
138 117 return $result;
@@ -137,25 +116,8 @@
137 116 }
138 117 return $result;
139 118 }
140 119
141 - // Sanitize a request-scoped value tree.
142 - //
143 - // The permissive whitelist in tcmp_free_wp_kses_tags_attrs.php exists for
144 - // exactly one kind of value: a snippet body. Those are admin-authored
145 - // tracking snippets and legitimately contain <script>, <iframe> and onload —
146 - // that is the plugin's purpose. Applying the same whitelist to every other
147 - // string, as this method used to, extended "may contain executable markup" to
148 - // values that are never tracking code: notice text, names, ids.
149 - //
150 - // So the 'code' field keeps the permissive pass (still opt-out-able via Skip
151 - // Code Sanitization, since a snippet may need markup the whitelist omits) and
152 - // everything else gets wp_kses_post(), which keeps the formatting notices
153 - // legitimately embed — <a href>, <strong>, <span style> — while dropping
154 - // script/iframe/style and every on* handler.
155 - //
156 - // This mirrors the split TCMP_Utils::sanitize_post_or_get() already applies on
157 - // the save path (F-06): permissive for 'code', plain text for the rest.
158 120 public function recursive_wp_kses( $array ) {
159 121 global $tcmp_allowed_html_tags;
160 122 foreach ( $array as $key => &$value ) {
161 123 if ( is_array( $value ) ) {
@@ -164,9 +126,9 @@
164 126 if ( ! $this->getSkipCodeSanitization() ) {
165 127 $value = wp_kses( $value, $tcmp_allowed_html_tags );
166 128 }
167 129 } elseif ( is_string( $value ) ) {
168 - $value = wp_kses_post( $value );
130 + $value = wp_kses( $value, $tcmp_allowed_html_tags );
169 131 } else {
170 132 // do nothing ... could be a video or graphics object
171 133 }
172 134 }
@@ -173,9 +135,10 @@
173 135 return $array;
174 136 }
175 137
176 138 private function setRequest( $key, $value ) {
177 - $this->request_data[ $key ] = $value;
139 + $key = $this->get_key( $key );
140 + $_POST[ $key ] = $value;
178 141 }
179 142
180 143 public function isPluginFirstInstall() {
181 144 return $this->getOption( 'PluginFirstInstall', false );
@@ -388,19 +351,14 @@
388 351 private function writeGenericMessages( $type, $clean = true ) {
389 352 $type = sanitize_text_field( $type );
390 353 $result = false;
391 354 $array = $this->getRequest( $type . 'Messages', array() );
355 + // These messages are built by the plugin and have been already sanitized.
356 + // Trying to sanitize them again will break plugin functionality since some of the messages contain html.
392 357 if ( is_array( $array ) && count( $array ) > 0 ) {
393 358 $result = true;
394 - // Escape at the sink so safety no longer depends on every caller
395 - // pre-escaping its message arguments (see F-04). wp_kses_post keeps
396 - // the intentional markup (links, styled spans) some notices embed
397 - // while neutralising anything dangerous. It now wraps the wpautop()
398 - // result rather than each message, so the escaping is applied to
399 - // everything that is actually echoed.
400 - $html = wpautop( implode( "\n", $array ) );
401 359 ?>
402 - <div class="tcmp-box-<?php echo esc_attr( strtolower( $type ) ); ?>"><?php echo wp_kses_post( $html ); ?></div>
360 + <div class="tcmp-box-<?php echo strtolower( $type ); ?>"><?php echo wpautop( implode( "\n", $array ) ); ?></div>
403 361 <?php
404 362 }
405 363 if ( $clean ) {
406 364 $this->removeRequest( $type . 'Messages' );