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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions