Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion inc/admin-pages/class-base-customer-facing-admin-page.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,18 @@ public function init(): void {

add_action('updated_user_meta', [$this, 'save_settings'], 10, 4);

/*
* This form customizes the network-admin menu title/position/icon of
* the customer-facing pages and persists a network-level setting, so it
* must require network-admin rights. The previous 'exist' capability
* allowed any logged-in user to submit it.
*/
wu_register_form(
"edit_admin_page_$this->id",
[
'render' => [$this, 'render_edit_page'],
'handler' => [$this, 'handle_edit_page'],
'capability' => 'exist',
'capability' => 'manage_network',
]
);

Expand Down
4 changes: 4 additions & 0 deletions inc/admin-pages/class-system-info-admin-page.php
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@
'wp_post_revisions' => [
'tooltip' => '',
'title' => 'WP Options Transients',
'value' => defined('WP_POST_REVISIONS') ? WP_POST_REVISIONS ?: __('Disabled', 'ultimate-multisite') : __('Not set', 'ultimate-multisite'),

Check warning on line 489 in inc/admin-pages/class-system-info-admin-page.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Found usage of constant WP_POST_REVISIONS. Use wp_revisions_to_keep() instead.
],
'disable_wp_cron' => [
'tooltip' => '',
Expand Down Expand Up @@ -561,6 +561,10 @@
*/
public function generate_text_file_system_info(): void {

if ( ! current_user_can('manage_network')) {
wp_die(esc_html__('You do not have permission to access this resource.', 'ultimate-multisite'), 403);
}

$file_name = sprintf("$this->id-%s.txt", gmdate('Y-m-d'));

header('Content-Description: File Transfer');
Expand Down
31 changes: 26 additions & 5 deletions inc/admin-pages/class-view-logs-admin-page.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
*/
public function init(): void {

add_action('wp_ajax_wu_handle_view_logs', [$this, 'handle_view_logs']);

Check warning on line 83 in inc/admin-pages/class-view-logs-admin-page.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Action callback returns array but should not return anything.
}

/**
Expand Down Expand Up @@ -141,15 +141,21 @@
*/
public function handle_view_logs() {

if ( ! current_user_can('manage_network')) {
wp_die(esc_html__('You do not have permission to access this resource.', 'ultimate-multisite'), 403);
}

$logs_folder = Logger::get_logs_folder();

$logs_list = list_files(
Logger::get_logs_folder(),
$logs_folder,
2,
[
'index.html',
]
);

$logs_list = array_combine(array_values($logs_list), array_map(fn($file) => str_replace(Logger::get_logs_folder(), '', (string) $file), $logs_list));
$logs_list = array_combine(array_values($logs_list), array_map(fn($file) => str_replace($logs_folder, '', (string) $file), $logs_list));

if (empty($logs_list)) {
$logs_list[''] = __('No log files found', 'ultimate-multisite');
Expand All @@ -161,9 +167,24 @@

$contents = '';

// Security check
if ($file && ! stristr((string) $file, Logger::get_logs_folder())) {
wp_die(esc_html__('You can see files that are not Ultimate Multisite\'s logs', 'ultimate-multisite'));
/*
* Security check: confine the requested file to the logs folder.
*
* realpath() resolves any '..' traversal so a crafted path cannot
* escape the logs directory (the previous substring check accepted
* any path that merely *contained* the logs folder, e.g.
* "<logs>/../../../wp-config.php"). The resolved path must also be a
* real file located under the resolved logs folder.
*/
if ($file) {
$real_file = realpath((string) $file);
$real_folder = realpath($logs_folder);

if (false === $real_file || false === $real_folder || ! str_starts_with($real_file, trailingslashit($real_folder))) {
wp_die(esc_html__('You can only view Ultimate Multisite log files.', 'ultimate-multisite'), 403);
}

$file = $real_file;
}

if ( ! $file && ! empty($logs_list)) {
Expand All @@ -174,7 +195,7 @@

$default_content = wu_request('return_ascii', 'yes') === 'yes' ? wu_get_template_contents('events/ascii-badge') : __('No log entries found.', 'ultimate-multisite');

$contents = $file && file_exists($file) ? file_get_contents($file) : $default_content;

Check warning on line 198 in inc/admin-pages/class-view-logs-admin-page.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

file_get_contents() is discouraged. Use wp_remote_get() for remote URLs instead.

$response = [
'file' => $file,
Expand Down
13 changes: 13 additions & 0 deletions inc/class-ajax.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,19 @@
*/
public function search_models(): void {

/*

Check warning on line 89 in inc/class-ajax.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Empty line not required before block comment
* The selectize search endpoint returns network-wide objects
* (customers, memberships, payments and — for the 'user' model —
* WordPress user logins and email addresses). It is only ever wired
* to network-admin forms, so restrict it to network administrators
* to prevent any logged-in user from enumerating that data.
*/
if ( ! current_user_can('manage_network')) {
wp_send_json([]);

return;
}

/**
* Fires before the processing of the search request.
*
Expand Down Expand Up @@ -330,12 +343,12 @@
$section['fields'] = array_map(
function ($item) use ($section, $section_slug) {

$item['section'] = $section_slug;

Check warning on line 346 in inc/class-ajax.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Line indented incorrectly; expected at least 5 tabs, found 4

// Normalise to string so array_filter never strips it and

Check warning on line 348 in inc/class-ajax.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Line indented incorrectly; expected at least 5 tabs, found 4
// the JS template always receives a defined scalar value.

Check warning on line 349 in inc/class-ajax.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Line indented incorrectly; expected at least 5 tabs, found 4
$raw_title = wu_get_isset($section, 'title', '');

Check warning on line 350 in inc/class-ajax.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Equals sign not aligned with surrounding assignments; expected 13 spaces but found 14 spaces

Check warning on line 350 in inc/class-ajax.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Line indented incorrectly; expected at least 5 tabs, found 4
$item['section_title'] = is_scalar($raw_title) ? (string) $raw_title : '';

Check warning on line 351 in inc/class-ajax.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Equals sign not aligned with surrounding assignments; expected 1 space but found 2 spaces

Check warning on line 351 in inc/class-ajax.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Line indented incorrectly; expected at least 5 tabs, found 4

$item['url'] = wu_network_admin_url(
'wp-ultimo-settings',
Expand Down
21 changes: 20 additions & 1 deletion inc/class-dashboard-widgets.php
Original file line number Diff line number Diff line change
Expand Up @@ -320,10 +320,16 @@ public function output_widget_summary(): void {
*/
public function process_ajax_fetch_rss(): void {

if ( ! current_user_can('manage_network')) {
wp_die('', '', ['response' => 403]);
}

$default_url = 'https://community.wpultimo.com/topics/feed';

$atts = wp_parse_args(
$_GET, // phpcs:ignore WordPress.Security.NonceVerification.Recommended
[
'url' => 'https://community.wpultimo.com/topics/feed',
'url' => $default_url,
'title' => __('Forum Discussions', 'ultimate-multisite'),
'items' => 3,
'show_summary' => 1,
Expand All @@ -332,6 +338,15 @@ public function process_ajax_fetch_rss(): void {
]
);

/*
* Never let the request control the outbound URL. This widget only
* renders the plugin's own community feed; honouring a request-supplied
* URL would turn the endpoint into a server-side request forgery (SSRF)
* probe against internal hosts. Site owners can still override the feed
* server-side via the filter below.
*/
$atts['url'] = apply_filters('wu_dashboard_rss_feed_url', $default_url);

wp_widget_rss_output($atts);

exit;
Expand Down Expand Up @@ -377,6 +392,10 @@ public function process_ajax_fetch_events(): void {
*/
public function handle_table_csv(): void {

if ( ! current_user_can('manage_network')) {
wp_die('', '', ['response' => 403]);
}

$date_range = wu_request('date_range');
$headers = json_decode(stripslashes((string) wu_request('headers')));
$data = json_decode(stripslashes((string) wu_request('data')));
Expand Down
8 changes: 8 additions & 0 deletions inc/managers/class-domain-manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -1226,6 +1226,10 @@ public static function dns_get_record($domain) {
*/
public function get_dns_records(): void {

if ( ! current_user_can('manage_network')) {
wp_send_json_error(new \WP_Error('unauthorized', __('You do not have permission to perform this action.', 'ultimate-multisite')));
}

$domain = wu_request('domain');

if ( ! $domain) {
Expand Down Expand Up @@ -1491,6 +1495,10 @@ public function maybe_auto_promote_primary_domain($old_stage, $new_stage, $domai
*/
public function test_integration() {

if ( ! current_user_can('manage_network')) {
wp_send_json_error(new \WP_Error('unauthorized', __('You do not have permission to perform this action.', 'ultimate-multisite')));
}

$integration_id = wu_request('integration', 'none');

// Try the new Integration Registry first
Expand Down
4 changes: 4 additions & 0 deletions inc/managers/class-site-manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,10 @@ public function async_get_site_screenshot($site_id) {
*/
public function get_site_screenshot(): void {

if ( ! current_user_can('manage_network')) {
wp_send_json_error(new \WP_Error('unauthorized', __('You do not have permission to perform this action.', 'ultimate-multisite')));
}

$site_id = wu_request('site_id');

$site = wu_get_site($site_id);
Expand Down
15 changes: 14 additions & 1 deletion inc/site-templates/class-template-placeholders.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ public function placeholder_replacer($content): string {
*/
public function serve_placeholders_via_ajax(): void {

if ( ! current_user_can('manage_network')) {
wp_send_json_error(new \WP_Error('unauthorized', __('You do not have permission to perform this action.', 'ultimate-multisite')));
}

wp_send_json_success($this->placeholders_as_saved);
}

Expand All @@ -157,7 +161,16 @@ public function serve_placeholders_via_ajax(): void {
*/
public function save_placeholders(): void {

if ( ! check_ajax_referer('wu_edit_placeholders_editing')) {
if ( ! current_user_can('manage_network')) {
wp_send_json(
[
'code' => 'not-enough-permissions',
'message' => __('You don\'t have permission to alter placeholders.', 'ultimate-multisite'),
]
);
}

if ( ! check_ajax_referer('wu_edit_placeholders_editing', false, false)) {
wp_send_json(
[
'code' => 'not-enough-permissions',
Expand Down
Loading