WordPress 高風險項目:SQL Injection 防範指南

為什麼 WordPress 容易成為 SQL Injection 攻擊目標?

WordPress 表單預設透過 $_POST 取得使用者輸入,若未進行妥善過濾與驗證,極易遭受 SQL Injection 攻擊。以下為建議的防範措施:


一、輸入資料過濾

WordPress 提供許多 資料清洗(Sanitization) 函式,用以過濾使用者輸入:

$name  = sanitize_text_field($_POST['name']);
$email = sanitize_email($_POST['email']);
$url   = esc_url_raw($_POST['url']);

二、安全執行 SQL 查詢

請避免直接拼接 SQL 字串,應使用 $wpdb->prepare() 處理參數:

global $wpdb;

$name  = sanitize_text_field($_POST['name']);
$email = sanitize_email($_POST['email']);

$sql = $wpdb->prepare(
    "INSERT INTO {$wpdb->prefix}mytable (name, email) VALUES (%s, %s)",
    $name,
    $email
);
$wpdb->query($sql);
  • %s:代表字串
  • %d:代表整數
  • %f:代表浮點數

參考:wpdb::prepare() 官方文件


三、使用 WordPress 內建 API 處理資料

若資料與文章、使用者、評論等實體有關,建議直接使用內建函式:

  • wp_insert_post():新增文章
  • wp_update_post():更新文章
  • wp_insert_comment():新增評論

這些函式會自動處理資料過濾與轉義。


四、表單與參數驗證實例

1. 使用 parse_request 驗證 GET 參數

add_action('parse_request', 'validate_get_params');

function validate_get_params($wp) {
    if ('GET' !== $_SERVER['REQUEST_METHOD']) return;

    $pattern = '/^case\srandomblob\(\d+\)\swhen\snot\snull\sthen\s\d\selse\s\d\send/i';
    $rule = '/case\srandomblob/i';

    foreach ($_GET as $key => $value) {
        $value = sanitize_text_field($value);

        if (($key === 'id' || $key === 'p') && !is_numeric($value)) {
            $wp->query_vars['error'] = 404;
            return;
        }

        if (preg_match($pattern, $value) || preg_match($rule, $value)) {
            $wp->query_vars['error'] = 404;
            return;
        }
    }
}

2. Elementor 表單欄位驗證

add_action('elementor_pro/forms/validation', 'validate_elementor_form', 10, 2);

function validate_elementor_form($record, $ajax_handler) {
    $fields = $record->get('fields');
    $regex = '/^[0-9a-zA-Z]+$/';
    $pattern = '/^case\srandomblob\(\d+\)\swhen\snot\snull\sthen\s\d\selse\s\d\send/i';
    $rule = '/case\srandomblob/i';

    $form_fields = ['post_id', 'form_id', 'queried_id', '_wpnonce'];

    foreach ($fields as $key => $value) {
        $value = sanitize_text_field($value);

        if (in_array($key, $form_fields) && !preg_match($regex, $value)) {
            $ajax_handler->add_error('非法格式');
            return;
        }

        if (preg_match($pattern, $value) || preg_match($rule, $value)) {
            $ajax_handler->add_error('偵測到 SQL Injection 攻擊');
            return;
        }
    }
}

3. Elementor AJAX 提交驗證

add_action('elementor_pro/forms/new_record', 'check_elementor_ajax', 10, 2);

function check_elementor_ajax($record, $handler) {
    $fields = $record->get('fields');
    $regex = '/^[0-9a-zA-Z]+$/';
    $pattern = '/^case\srandomblob\(\d+\)\swhen\snot\snull\sthen\s\d\selse\s\d\send/i';
    $rule = '/case\srandomblob/i';

    $form_fields = ['post_id', 'form_id', 'queried_id', '_wpnonce'];

    foreach ($fields as $key => $value) {
        $value = sanitize_text_field($value);

        if (in_array($key, $form_fields) && !preg_match($regex, $value)) {
            $handler->set_success(false);
        }

        if (preg_match($pattern, $value) || preg_match($rule, $value)) {
            $handler->set_success(false);
        }
    }
}

五、常見鉤子與資料處理實作

sanitize_post:過濾文章資料

add_filter('sanitize_post', 'filter_contact_form');

function filter_contact_form($data) {
    if ($data['post_type'] === 'contact-us') {
        $data['post_title'] = sanitize_text_field($data['post_title']);
    }
    return $data;
}

comment_form_default_fields:自訂評論欄位

add_filter('comment_form_default_fields', 'custom_comment_fields');

function custom_comment_fields($fields) {
    $fields['author'] = '<input id="author" name="author" type="text" />';
    $fields['email'] = '<input id="email" name="email" type="email" />';
    $fields['url'] = '<input id="url" name="url" type="url" />';
    return $fields;
}

woocommerce_checkout_fields:自訂 WooCommerce 結帳欄位

add_filter('woocommerce_checkout_fields', 'custom_checkout_fields');

function custom_checkout_fields($fields) {
    $fields['billing']['billing_first_name']['label'] = '名字';
    $fields['billing']['my_custom_field'] = [
        'label' => '自定欄位',
        'required' => false,
        'class' => ['form-row-wide'],
        'clear' => true
    ];
    return $fields;
}

六、結論

防止 SQL Injection 攻擊的關鍵在於「永遠不要相信來自使用者的輸入」,並善用 WordPress 內建的過濾與轉義函式、資料庫查詢工具(如 $wpdb->prepare())與 API 函式,搭配嚴謹的驗證流程,才能降低網站遭入侵的風險。