Skip to content
Merged
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
4 changes: 4 additions & 0 deletions changes.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### 1.3.5

* Security hardening of the admin notice dismissal AJAX handler: require a valid nonce, require the `manage_options` capability, and restrict writable option keys to the `dismiss_*_notice` pattern.

### 1.3.4

* Fix notice about translations loaded too soon
Expand Down
30 changes: 24 additions & 6 deletions lib/classes/class-bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -595,25 +595,43 @@ public function define_license_manager() {
}

public function ud_bootstrap_dismiss_notice() {
check_ajax_referer( 'ud_bootstrap_dismiss_notice', 'nonce' );

$response = array(
'success' => '0',
'error' => __( 'There was an error in request.', $this->domain ),
);
$error = false;

if( empty( $_POST['key'] ) ||
//** Dismissing a notice writes global state, so it requires the same
//** capability as managing the product itself. */
if( ! current_user_can( 'manage_options' ) ) {
$response['error'] = __( 'You are not allowed to do this action.', $this->domain );
$error = true;
}

//** Only the notice-dismissal options this instance actually reads back
//** (dismiss_<slug>_<version>_notice) may be written - never an arbitrary key. */
$option_key = isset( $_POST['key'] ) ? sanitize_key( $_POST['key'] ) : '';

if( ! $error && ( strpos( $option_key, 'dismiss_' ) !== 0 || substr( $option_key, -7 ) !== '_notice' ) ) {
$response['error'] = __( 'Invalid key', $this->domain );
$error = true;
}
Comment on lines +617 to +620

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟥 Broad pattern permits unrelated option writes

Any dismiss_*_notice name passes validation without matching the current product. A compromised administrator can overwrite unrelated options with that shape.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


if( ! $error && (
empty( $_POST['slug'] ) ||
empty( $_POST['type'] ) ||
empty( $_POST['version'] )
) {
) ) {
$response['error'] = __( 'Invalid values', $this->domain );
$error = true;
}

if ( ! $error && update_option( ( $_POST['key'] ), array(
'slug' => $_POST['slug'],
'type' => $_POST['type'],
'version' => $_POST['version'],
if ( ! $error && update_option( $option_key, array(
'slug' => sanitize_key( $_POST['slug'] ),
'type' => sanitize_key( $_POST['type'] ),
'version' => sanitize_text_field( $_POST['version'] ),
) ) ) {
$response['success'] = '1';
}
Expand Down
Loading