PluginProbe
GiveWP – Donation Plugin and Fundraising Platform / 4.16.9
GiveWP – Donation Plugin and Fundraising Platform v4.16.9
4.16.9 4.16.8.1 4.16.8 4.16.7.2 4.16.7.1 4.16.7 4.16.6.1 4.16.6 4.16.5.1 4.16.5 4.16.4 4.16.3 4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 2.3.1 2.3.2 2.30.0 All 255 releases
← All changes | src/Helpers/Utils.php +178 -16 2.30.04.16.9 View file →
@@ -29,8 +29,9 @@
29 29
30 30 /**
31 31 * This function will change request url with other url.
32 32 *
33 + * @since 4.2.0 Replace URL anchor with request_anchor argument
33 34 * @since 2.7.0
34 35 *
35 36 * @param string $location Requested URL.
36 37 * @param string $url URL.
@@ -40,35 +41,42 @@
40 41 * @return string
41 42 */
42 43 public static function switchRequestedURL($location, $url, $addArgs = [], $removeArgs = [])
43 44 {
45 + $urlAnchor = '';
46 +
47 + if (strpos($url, '#') !== false) {
48 + [$url, $urlAnchor] = explode('#', $url, 2);
49 + }
50 +
44 51 $queryString = [];
45 52
46 - if ($index = strpos($location, '?')) {
47 - $queryString = wp_parse_args(substr($location, strpos($location, '?') + 1));
53 + if (($index = strpos($location, '?')) !== false) {
54 + $queryString = wp_parse_args(substr($location, $index + 1));
48 55 }
49 56
50 - if ($index = strpos($url, '?')) {
51 - $queryString = array_merge($queryString, wp_parse_args(substr($url, strpos($url, '?') + 1)));
57 + if (($index = strpos($url, '?')) !== false) {
58 + $queryString = array_merge(
59 + $queryString,
60 + wp_parse_args(substr($url, $index + 1))
61 + );
62 + $url = substr($url, 0, $index);
52 63 }
53 64
54 - $url = add_query_arg(
55 - $queryString,
56 - $url
57 - );
65 + $url = add_query_arg($queryString, $url);
58 66
59 - if ($removeArgs) {
60 - foreach ($removeArgs as $name) {
61 - $url = add_query_arg([$name => false], $url);
62 - }
67 + foreach ((array) $removeArgs as $name) {
68 + $url = add_query_arg([$name => false], $url);
63 69 }
64 70
65 - if ($addArgs) {
66 - foreach ($addArgs as $name => $value) {
67 - $url = add_query_arg([$name => $value], $url);
68 - }
71 + foreach ((array) $addArgs as $name => $value) {
72 + $url = add_query_arg([$name => $value], $url);
69 73 }
70 74
75 + if (!empty($urlAnchor)) {
76 + $url = add_query_arg('request_anchor', $urlAnchor, $url);
77 + }
78 +
71 79 return esc_url_raw($url);
72 80 }
73 81
74 82 /**
@@ -109,6 +117,160 @@
109 117 include_once ABSPATH . 'wp-admin/includes/plugin.php';
110 118 }
111 119
112 120 return is_plugin_active($plugin);
121 + }
122 +
123 + /**
124 + * @since 3.17.2
125 + */
126 + public static function removeBackslashes($data)
127 + {
128 + /**
129 + * The stripslashes_deep() method removes only the first backslash occurrence from
130 + * a given string, so we are using the ltrim() method to make sure we are removing
131 + * all other occurrences. We need to remove these backslashes from the beginner of
132 + * the input because attackers can use them to bypass the is_serialized() check.
133 + */
134 + $data = stripslashes_deep($data);
135 + $data = is_string($data) ? ltrim($data, '\\') : $data;
136 +
137 + return $data;
138 + }
139 +
140 + /**
141 + * Decode strings recursively to prevent double (or more) encoded strings
142 + *
143 + * @since 3.19.4
144 + */
145 + public static function recursiveUrlDecode(string $data): string
146 + {
147 + $decoded = urldecode($data);
148 +
149 + return $decoded === $data ? $data : self::recursiveUrlDecode($decoded);
150 + }
151 +
152 + /**
153 + * The regular expression attempts to capture the basic structure of all data types that can be serialized by PHP.
154 + *
155 + * @since 3.19.4 Decode the string and remove any character not allowed in a serialized string
156 + * @since 3.19.3 Support all types of serialized data instead of only objects and arrays
157 + * @since 3.17.2
158 + */
159 + public static function containsSerializedDataRegex($data): bool
160 + {
161 + if ( ! is_string($data)) {
162 + return false;
163 + }
164 +
165 + $data = self::recursiveUrlDecode($data);
166 +
167 + /**
168 + * This regular expression removes any special character that is not:
169 + * a Letter (a-zA-Z), number (0-9), or any of the characters {}, :, ;, ", ', ., [, ], (, ), ,
170 + */
171 + $data = preg_replace('/[^a-zA-Z0-9:{};"\'.\[\](),]/', '', $data);
172 +
173 + $pattern = '/
174 + (a:\d+:\{.*}) | # Matches arrays (e.g: a:2:{i:0;s:5:"hello";i:1;i:42;})
175 + (O:\d+:"[^"]+":\{.*}) | # Matches objects (e.g: O:8:"stdClass":1:{s:4:"name";s:5:"James";})
176 + (s:\d+:"[^"]*";) | # Matches strings (e.g: s:5:"hello";)
177 + (i:\d+;) | # Matches integers (e.g: i:42;)
178 + (b:[01];) | # Matches booleans (e.g: b:1; or b:0;)
179 + (d:\d+(\.\d+)?;) | # Matches floats (e.g: d:3.14;)
180 + (N;) # Matches NULL (e.g: N;)
181 + /x';
182 +
183 + return preg_match($pattern, $data) === 1;
184 + }
185 +
186 + /**
187 + * @since 3.17.2
188 + */
189 + public static function isSerialized($data): bool
190 + {
191 + $data = self::removeBackslashes($data);
192 +
193 + if (is_serialized($data) || self::containsSerializedDataRegex($data)) {
194 + return true;
195 + }
196 +
197 + return false;
198 + }
199 +
200 + /**
201 + * @since 4.16.7.2 Returns false instead of the raw serialized string when an object is detected
202 + * @since 4.16.6 Returns $data when $unserializedData constins __PHP_Incomplete_Class
203 + * @since 3.17.2
204 + */
205 + public static function safeUnserialize($data)
206 + {
207 + $data = self::removeBackslashes($data);
208 +
209 + /**
210 + * We are setting the allowed_classes to false as a default to
211 + * prevent the injection of objects that can run unwished code.
212 + *
213 + * From PHP docs:
214 + * allowed_classes - Either an array of class names which should be accepted, false to accept no classes, or
215 + * true to accept all classes. If this option is defined and unserialize() encounters an object of a class
216 + * that isn't to be accepted, then the object will be instantiated as __PHP_Incomplete_Class instead. Omitting
217 + * this option is the same as defining it as true: PHP will attempt to instantiate objects of any class.
218 + */
219 + $unserializedData = @unserialize(trim($data), ['allowed_classes' => false]);
220 +
221 + if (self::containsPhpIncompleteClass($unserializedData)) {
222 + return false;
223 + }
224 +
225 + /*
226 + * In case the passed string is not unserializeable, false is returned.
227 + *
228 + * @see https://www.php.net/manual/en/function.unserialize.php
229 + */
230 +
231 + return ! $unserializedData && ! self::containsSerializedDataRegex($data) ? $data : $unserializedData;
232 + }
233 +
234 + /**
235 + * Recursively checks if the given data contains any __PHP_Incomplete_Class instance,
236 + * which is what unserialize() produces for classes not present in allowed_classes.
237 + *
238 + * @since 4.16.6
239 + *
240 + * @param mixed $data Data to check, can be any type.
241 + *
242 + * @return bool True if a __PHP_Incomplete_Class instance is found at any nesting level.
243 + */
244 + public static function containsPhpIncompleteClass($data): bool
245 + {
246 + if ($data instanceof \__PHP_Incomplete_Class) {
247 + return true;
248 + }
249 +
250 + if (is_array($data) || is_object($data)) {
251 + foreach ((array)$data as $value) {
252 + if (self::containsPhpIncompleteClass($value)) {
253 + return true;
254 + }
255 + }
256 + }
257 +
258 + return false;
259 + }
260 +
261 + /**
262 + * Avoid insecure usage of `unserialize` when the data could be submitted by the user.
263 + *
264 + * @since 3.16.1
265 + *
266 + * @param string $data Data that might be unserialized.
267 + *
268 + * @return mixed Unserialized data can be any type.
269 + */
270 + public static function maybeSafeUnserialize($data)
271 + {
272 + return self::isSerialized($data)
273 + ? self::safeUnserialize($data)
274 + : $data;
113 275 }
114 276 }