Skip to content

Commit c9f5d0b

Browse files
committed
WPCasa 1.4.2
1 parent aec793c commit c9f5d0b

File tree

6 files changed

+234
-111
lines changed

6 files changed

+234
-111
lines changed

README.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Tags: listings, property, real-estate, rental, realtor
66
Requires at least: 6.2
77
Requires PHP: 7.2
88
Tested up to: 6.8
9-
Stable tag: 1.4.1
9+
Stable tag: 1.4.2
1010
License: GPLv2 or later
1111
License URI: https://www.gnu.org/licenses/gpl-2.0.html
1212

@@ -231,6 +231,12 @@ Andrea Manzato
231231
[Simon Rimkus](https://github.com/simonrimkus)
232232

233233
== Changelog ==
234+
= 1.4.2 =
235+
* FIX: Vulnerable to cross site scripting (XSS) with shortcodes 'wpsight_listings_map' reported by Muhammad Yudha - DJ on Patchstack - Thank you for that!
236+
* FIX: Vulnerable to API code injection reported by mikemyers from Wordfence - Thank you for that!
237+
* FIX: Deprecated message "Creation of dynamic property"
238+
* FIX: "Trying to access array offset on false" on settings page
239+
234240
= 1.4.1 =
235241
* FIX: The license page may show an error under certain circumstances
236242

includes/admin/class-wpsight-admin-page-about.php

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,35 @@ public function output() : void {
110110
</style>
111111

112112
<ul class="tabs" data-tabgroup="first-tab-group">
113-
<li class="tab"><a href="#version-1-4-1" class="active">v1.4.1</a></li>
113+
<li class="tab"><a href="#version-1-4-2" class="active">v1.4.2</a></li>
114+
<li class="tab"><a href="#version-1-4-1">v1.4.1</a></li>
114115
<li class="tab"><a href="#version-1-4-0">v1.4.0</a></li>
115116
<li class="tab"><a href="#version-1-3-1">v1.3.1</a></li>
116-
<li class="tab"><a href="#version-1-3-0" >v1.3.0</a></li>
117117
<li><a href="https://wordpress.org/plugins/wpcasa/#developers" target="_blank"><?php echo esc_html__( 'More', 'wpcasa' ); ?></a></li>
118118
</ul>
119119

120120
<section id="first-tab-group" class="tabgroup">
121+
<div id="version-1-4-2">
122+
<p>Version: 1.4.2</p>
123+
<table>
124+
<tr>
125+
<td><span class="changelog-entry-fix">Hotfix</span></td>
126+
<td>Vulnerable to cross site scripting (XSS) with shortcodes 'wpsight_listings_map' reported by Muhammad Yudha - DJ at Patchstack</td>
127+
</tr>
128+
<tr>
129+
<td><span class="changelog-entry-fix">Hotfix</span></td>
130+
<td>Vulnerable to API code injection reported by mikemyers from Wordfence</td>
131+
</tr>
132+
<tr>
133+
<td><span class="changelog-entry-fix">Fix</span></td>
134+
<td>Deprecated message "Creation of dynamic property"</td>
135+
</tr>
136+
<tr>
137+
<td><span class="changelog-entry-fix">Fix</span></td>
138+
<td>"Trying to access array offset on false" on settings page</td>
139+
</tr>
140+
</table>
141+
</div>
121142
<div id="version-1-4-1">
122143
<p>Version: 1.4.1</p>
123144
<table>
@@ -186,6 +207,29 @@ public function output() : void {
186207
</table>
187208
</div>
188209

210+
</section>
211+
212+
<script type="text/javascript">
213+
jQuery(document).ready(function($) {
214+
$('.tabgroup > div').hide();
215+
$('.tabgroup > div:first-of-type').show();
216+
$('.tabs .tab a').click(function(e){
217+
e.preventDefault();
218+
var $this = $(this),
219+
tabgroup = '#'+$this.parents('.tabs').data('tabgroup'),
220+
others = $this.closest('.tab').siblings().children('a'),
221+
target = $this.attr('href');
222+
others.removeClass('active');
223+
$this.addClass('active');
224+
$(tabgroup).children('div').hide();
225+
$(target).show();
226+
})
227+
});
228+
</script>
229+
230+
</div>
231+
232+
<?php /*?>
189233
<div id="version-1-3-0">
190234
<p>Version: 1.3.0</p>
191235
<table>
@@ -224,29 +268,6 @@ public function output() : void {
224268
</table>
225269
</div>
226270
227-
</section>
228-
229-
<script type="text/javascript">
230-
jQuery(document).ready(function($) {
231-
$('.tabgroup > div').hide();
232-
$('.tabgroup > div:first-of-type').show();
233-
$('.tabs .tab a').click(function(e){
234-
e.preventDefault();
235-
var $this = $(this),
236-
tabgroup = '#'+$this.parents('.tabs').data('tabgroup'),
237-
others = $this.closest('.tab').siblings().children('a'),
238-
target = $this.attr('href');
239-
others.removeClass('active');
240-
$this.addClass('active');
241-
$(tabgroup).children('div').hide();
242-
$(target).show();
243-
})
244-
});
245-
</script>
246-
247-
</div>
248-
249-
<?php /*?>
250271
<div id="version-1-2-13">
251272
<p>Version: 1.2.13</p>
252273
<table>

includes/admin/views/option-measurement.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,18 @@
1616
$value = wpsight_get_option( $option['id'] );
1717
if( !isset( $value ) && isset( $option['default'] ) ) $value = $option['default'];
1818
$measurement = $value;
19-
19+
if( ! isset( $measurement['label'] ) ) {
20+
$measurement['label'] = '';
21+
}
22+
if( ! isset( $measurement['unit'] ) ) {
23+
$measurement['label'] = '';
24+
$measurement['unit'] = '';
25+
}
2026
$placeholder = isset( $option['placeholder'] ) ? 'placeholder="' . $option['placeholder'] . '"' : '';
2127
?>
2228

2329
<div class="wpsight-settings-field wpsight-settings-field-text">
24-
<input id="setting-<?php echo esc_attr( $option_css ); ?>_label" class="regular-text" type="text" name="<?php echo esc_attr( $option_id ) . '[label]'; ?>" value="<?php echo esc_attr( $measurement['label'] ); ?>" <?php echo esc_attr( implode( ' ', $attributes ) ); ?> <?php echo esc_attr( $placeholder ); ?> />
30+
<input id="setting-<?php echo esc_attr( $option_css ); ?>_label" class="regular-text" type="text" name="<?php echo esc_attr( $option_id ) . '[label]'; ?>" value="<?php echo esc_attr($measurement['label'] ); ?>" <?php echo esc_attr(implode(' ', $attributes ) ); ?> <?php echo esc_attr($placeholder ); ?> />
2531
</div>
2632

2733
<div class="wpsight-settings-field wpsight-settings-field-radio">
@@ -30,7 +36,6 @@
3036

3137
foreach ( wpsight_measurements() as $key => $unit ) {
3238
$id = $option_css .'-'. $key;
33-
3439
?>
3540

3641
<input id="setting-<?php echo esc_attr( $id ); ?>" name="<?php echo esc_attr( $option_id ); ?>[unit]" type="radio" value="<?php echo esc_attr( $key ); ?>" <?php echo esc_html( implode( ' ', $attributes ) ); ?> <?php checked( esc_attr( $measurement['unit'] ), esc_attr( $key ) ); ?> />

includes/class-wpsight-api.php

Lines changed: 98 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,134 @@
11
<?php
2+
/**
3+
* Secure WPSight API Handler.
4+
*
5+
* Hardened version of the original WPSight_API class.
6+
*
7+
* @package WPCasa
8+
* @since 1.0.0
9+
* @updated 1.4.2
10+
*/
11+
212
// Exit if accessed directly
313
if ( ! defined( 'ABSPATH' ) ) exit;
414

5-
/**
6-
* WPSight_API class
7-
*/
815
class WPSight_API {
916

1017
/**
11-
* Constructor
18+
* Constructor.
1219
*/
1320
public function __construct() {
14-
add_filter( 'query_vars', array( $this, 'add_query_vars'), 0 );
15-
add_action( 'parse_request', array( $this, 'api_requests'), 0 );
21+
// Ensure query var is registered early.
22+
add_filter( 'query_vars', array( $this, 'add_query_vars' ), 0 );
23+
24+
// Register API request handler.
25+
add_action( 'parse_request', array( $this, 'api_requests' ), 0 );
1626
}
1727

1828
/**
19-
* add_query_vars()
20-
*
21-
* @access public
29+
* Register custom query vars.
2230
*
23-
* @since 1.0.0
31+
* @param array $vars List of public query vars.
32+
* @return array
2433
*/
2534
public function add_query_vars( $vars ) {
2635
$vars[] = 'wpsight-api';
2736
return $vars;
2837
}
2938

3039
/**
31-
* add_endpoint()
32-
*
33-
* @access public
34-
*
35-
* @since 1.0.0
36-
*/
37-
public function add_endpoint() {
38-
add_rewrite_endpoint( 'wpsight-api', EP_ALL );
39-
}
40-
41-
/**
42-
* api_requests()
43-
*
44-
* @access public
40+
* Securely handle API requests.
4541
*
46-
* @since 1.0.0
42+
* @return void
4743
*/
4844
public function api_requests() {
4945
global $wp;
5046

51-
if ( ! empty( $_GET['wpsight-api'] ) )
52-
$wp->query_vars['wc-api'] = sanitize_text_field( $_GET['wpsight-api'] );
47+
// 1) Preferred getter.
48+
$raw = get_query_var( 'wpsight-api' );
5349

54-
if ( ! empty( $wp->query_vars['wc-api'] ) ) {
55-
// Buffer, we won't want any output here
56-
ob_start();
50+
// 2) Fallback: directly from $wp->query_vars.
51+
if ( empty( $raw ) && isset( $wp->query_vars['wpsight-api'] ) ) {
52+
$raw = $wp->query_vars['wpsight-api'];
53+
}
5754

58-
// Get API trigger
59-
$api = strtolower( esc_attr( $wp->query_vars['wpsight-api'] ) );
55+
// 3) Last resort: direct $_GET.
56+
if ( empty( $raw ) && ! empty( $_GET['wpsight-api'] ) ) {
57+
$raw = sanitize_text_field( wp_unslash( $_GET['wpsight-api'] ) );
58+
}
6059

61-
// Load class if exists
62-
if ( class_exists( $api ) )
63-
$api_class = new $api();
60+
// No request found → exit early.
61+
if ( empty( $raw ) ) {
62+
return;
63+
}
6464

65-
// Trigger actions
66-
do_action( 'wpsight_api_' . $api );
65+
// Sanitize to a valid key.
66+
$api = sanitize_key( $raw );
6767

68-
// Done, clear buffer and exit
68+
if ( empty( $api ) ) {
69+
return;
70+
}
71+
72+
/**
73+
* Build allow-list of allowed API endpoints.
74+
*
75+
* IMPORTANT: Replace or extend this list in your theme or plugin
76+
* using the 'wpsight_api_allowed_endpoints' filter.
77+
*
78+
* Example:
79+
*
80+
* add_filter( 'wpsight_api_allowed_endpoints', function( $allowed ) {
81+
* $allowed['ping'] = array( 'class' => null );
82+
* return $allowed;
83+
* } );
84+
*/
85+
$allowed = apply_filters(
86+
'wpsight_api_allowed_endpoints',
87+
array()
88+
);
89+
90+
// If API is not allowed, block access.
91+
if ( ! isset( $allowed[ $api ] ) ) {
92+
wp_die(
93+
sprintf(
94+
/* translators: %s: API endpoint slug */
95+
esc_html__( 'Endpoint "%s" not allowed.', 'wpcasa' ),
96+
$api
97+
),
98+
esc_html__( 'Forbidden', 'wpcasa' ),
99+
array( 'response' => 403 )
100+
);
101+
}
102+
103+
// Start output buffering.
104+
ob_start();
105+
106+
// Optional: safe class instantiation if explicitly allowed.
107+
if ( ! empty( $allowed[ $api ]['class'] ) && class_exists( $allowed[ $api ]['class'] ) ) {
108+
new $allowed[ $api ]['class']();
109+
}
110+
111+
/**
112+
* Trigger API action hook.
113+
*
114+
* Example usage:
115+
* add_action( 'wpsight_api_ping', function() {
116+
* echo 'pong';
117+
* } );
118+
*/
119+
do_action( 'wpsight_api_' . $api );
120+
121+
// In development mode, allow buffer output for easier testing.
122+
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
123+
ob_end_flush();
124+
} else {
69125
ob_end_clean();
70-
die('1');
71126
}
127+
128+
// Maintain old behaviour for backward compatibility.
129+
die( '1' );
72130
}
73131
}
74132

133+
// Instantiate the class.
75134
new WPSight_API();

0 commit comments

Comments
 (0)