Skip to content

IRL - Missed Detection: AJAX handler without nonce (wp_ajax_nopriv) #106

@mrtwebdesign

Description

@mrtwebdesign

Summary

WPCC reported "wp_ajax handlers without nonce validation" as PASSED when the codebase contains an unprotected public AJAX endpoint.

Scanned: Universal Child Theme 2024 (~12K LOC)
WPCC Version: 2.0.14


🔴 Missed Detection

Location: functions.php lines 657-696

// ajax search
function fetch_search_data() {

    $response = new stdClass();
    $keyword = $_POST['keyword'];  // ❌ No nonce check anywhere in function

    // products
    $prod_args = array(
        'post_type' => 'product',
        'post_status' => 'publish',
        's' => $keyword,
        'posts_per_page' => 3,
    );

    $prod_search = new WP_Query($prod_args);
    // ... rest of function ...
    echo json_encode($response);
    die();
}
add_action('wp_ajax_fetch_search_data', 'fetch_search_data');
add_action('wp_ajax_nopriv_fetch_search_data', 'fetch_search_data');  // ← PUBLIC endpoint

What WPCC reported:

{"name": "wp_ajax handlers without nonce validation", "status": "passed", "findings_count": 0}

Why This Matters

  1. CSRF Vulnerability - No wp_verify_nonce() or check_ajax_referer()
  2. Public Endpoint - wp_ajax_nopriv_ makes this accessible to unauthenticated users
  3. Unsanitized Input - $_POST['keyword'] flows directly into WP_Query (separate issue)

Possible Detection Pattern Issue

The current pattern may not match when:

  • Function definition and add_action are separated by many lines
  • Function uses die() instead of wp_die() or wp_send_json()
  • The add_action hooks are on consecutive lines (695-696)

Suggested pattern to catch this:

# Find functions registered to wp_ajax that lack nonce checks
# 1. Find add_action('wp_ajax_...')
# 2. Extract function name
# 3. Search function body for wp_verify_nonce|check_ajax_referer
# 4. Flag if missing

Suggested Test Fixture

// fixtures/ajax-no-nonce-nopriv.php
// Expected: FAIL - AJAX handler without nonce validation

function bad_public_ajax_handler() {
    $data = sanitize_text_field($_POST['data']);
    echo json_encode(['result' => $data]);
    die();
}
add_action('wp_ajax_bad_handler', 'bad_public_ajax_handler');
add_action('wp_ajax_nopriv_bad_handler', 'bad_public_ajax_handler');

Additional Consideration

wp_ajax_nopriv_ handlers should perhaps have elevated severity since they're publicly accessible without authentication. A nonce-less wp_ajax_ handler (logged-in only) is concerning, but wp_ajax_nopriv_ without nonce is more critical.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions