fix(products): Add comprehensive data sanitization

Products module had NO sanitization - fixed to match Orders/Coupons/Customers

Issue:
 No sanitization in create_product
 No sanitization in update_product
 Direct assignment of raw user input
 Potential XSS and injection vulnerabilities
 Inconsistent with other modules

Changes Made:

1. Created Sanitization Helpers (Lines 23-65):
 sanitize_text() - Text fields (name, SKU)
 sanitize_textarea() - Descriptions (allows newlines)
 sanitize_number() - Prices, dimensions (removes non-numeric)
 sanitize_slug() - URL slugs (uses sanitize_title)

2. Fixed create_product() (Lines 278-317):
 Name → sanitize_text()
 Slug → sanitize_slug()
 Status → sanitize_key()
 Description → sanitize_textarea()
 Short description → sanitize_textarea()
 SKU → sanitize_text()
 Regular price → sanitize_number()
 Sale price → sanitize_number()
 Weight → sanitize_number()
 Length → sanitize_number()
 Width → sanitize_number()
 Height → sanitize_number()

3. Fixed update_product() (Lines 377-398):
 Same sanitization as create
 All text fields sanitized
 All numeric fields sanitized
 Status fields use sanitize_key()

Sanitization Logic:

Text Fields:
- sanitize_text_field() + trim()
- Prevents XSS attacks
- Example: '<script>alert(1)</script>' → ''

Textarea Fields:
- sanitize_textarea_field() + trim()
- Allows newlines for descriptions
- Prevents XSS but keeps formatting

Numbers:
- Remove non-numeric except . and -
- Example: 'abc123.45' → '123.45'
- Example: '10,000' → '10000'

Slugs:
- sanitize_title()
- Creates URL-safe slugs
- Example: 'Product Name!' → 'product-name'

Module Audit Results:

 Orders: FIXED (comprehensive sanitization)
 Coupons: GOOD (already has sanitization)
 Customers: GOOD (already has sanitization)
 Products: FIXED (added comprehensive sanitization)

All modules now have consistent, secure data handling!
This commit is contained in:
dwindown
2025-11-21 00:11:29 +07:00
parent 8b939a0903
commit 2e993b2f96

View File

@@ -20,6 +20,50 @@ use WC_Product_Variation;
class ProductsController { class ProductsController {
/**
* Sanitize text field
*/
private static function sanitize_text($value) {
if (!isset($value) || $value === '') {
return '';
}
$sanitized = sanitize_text_field($value);
return trim($sanitized);
}
/**
* Sanitize textarea (allows newlines)
*/
private static function sanitize_textarea($value) {
if (!isset($value) || $value === '') {
return '';
}
$sanitized = sanitize_textarea_field($value);
return trim($sanitized);
}
/**
* Sanitize numeric value
*/
private static function sanitize_number($value) {
if (!isset($value) || $value === '') {
return '';
}
// Remove non-numeric except decimal point and minus
$sanitized = preg_replace('/[^0-9.-]/', '', $value);
return $sanitized !== '' ? $sanitized : '';
}
/**
* Sanitize slug
*/
private static function sanitize_slug($value) {
if (!isset($value) || $value === '') {
return '';
}
return sanitize_title($value);
}
/** /**
* Register REST API routes * Register REST API routes
*/ */
@@ -231,24 +275,24 @@ class ProductsController {
$product = new WC_Product_Simple(); $product = new WC_Product_Simple();
} }
// Set basic data // Set basic data - sanitize all inputs
$product->set_name($data['name']); $product->set_name(self::sanitize_text($data['name']));
if (!empty($data['slug'])) { if (!empty($data['slug'])) {
$product->set_slug($data['slug']); $product->set_slug(self::sanitize_slug($data['slug']));
} }
$product->set_status($data['status'] ?? 'publish'); $product->set_status(sanitize_key($data['status'] ?? 'publish'));
$product->set_description($data['description'] ?? ''); $product->set_description(self::sanitize_textarea($data['description'] ?? ''));
$product->set_short_description($data['short_description'] ?? ''); $product->set_short_description(self::sanitize_textarea($data['short_description'] ?? ''));
if (!empty($data['sku'])) { if (!empty($data['sku'])) {
$product->set_sku($data['sku']); $product->set_sku(self::sanitize_text($data['sku']));
} }
if (!empty($data['regular_price'])) { if (!empty($data['regular_price'])) {
$product->set_regular_price($data['regular_price']); $product->set_regular_price(self::sanitize_number($data['regular_price']));
} }
if (!empty($data['sale_price'])) { if (!empty($data['sale_price'])) {
$product->set_sale_price($data['sale_price']); $product->set_sale_price(self::sanitize_number($data['sale_price']));
} }
$product->set_manage_stock($data['manage_stock'] ?? false); $product->set_manage_stock($data['manage_stock'] ?? false);
@@ -258,18 +302,18 @@ class ProductsController {
} }
$product->set_stock_status($data['stock_status'] ?? 'instock'); $product->set_stock_status($data['stock_status'] ?? 'instock');
// Optional fields // Optional fields - sanitize dimensions
if (!empty($data['weight'])) { if (!empty($data['weight'])) {
$product->set_weight($data['weight']); $product->set_weight(self::sanitize_number($data['weight']));
} }
if (!empty($data['length'])) { if (!empty($data['length'])) {
$product->set_length($data['length']); $product->set_length(self::sanitize_number($data['length']));
} }
if (!empty($data['width'])) { if (!empty($data['width'])) {
$product->set_width($data['width']); $product->set_width(self::sanitize_number($data['width']));
} }
if (!empty($data['height'])) { if (!empty($data['height'])) {
$product->set_height($data['height']); $product->set_height(self::sanitize_number($data['height']));
} }
// Virtual and downloadable // Virtual and downloadable
@@ -330,15 +374,15 @@ class ProductsController {
return new WP_Error('product_not_found', __('Product not found', 'woonoow'), ['status' => 404]); return new WP_Error('product_not_found', __('Product not found', 'woonoow'), ['status' => 404]);
} }
// Update basic data // Update basic data - sanitize all inputs
if (isset($data['name'])) $product->set_name($data['name']); if (isset($data['name'])) $product->set_name(self::sanitize_text($data['name']));
if (isset($data['slug'])) $product->set_slug($data['slug']); if (isset($data['slug'])) $product->set_slug(self::sanitize_slug($data['slug']));
if (isset($data['status'])) $product->set_status($data['status']); if (isset($data['status'])) $product->set_status(sanitize_key($data['status']));
if (isset($data['description'])) $product->set_description($data['description']); if (isset($data['description'])) $product->set_description(self::sanitize_textarea($data['description']));
if (isset($data['short_description'])) $product->set_short_description($data['short_description']); if (isset($data['short_description'])) $product->set_short_description(self::sanitize_textarea($data['short_description']));
if (isset($data['sku'])) $product->set_sku($data['sku']); if (isset($data['sku'])) $product->set_sku(self::sanitize_text($data['sku']));
if (isset($data['regular_price'])) $product->set_regular_price($data['regular_price']); if (isset($data['regular_price'])) $product->set_regular_price(self::sanitize_number($data['regular_price']));
if (isset($data['sale_price'])) $product->set_sale_price($data['sale_price']); if (isset($data['sale_price'])) $product->set_sale_price(self::sanitize_number($data['sale_price']));
if (isset($data['manage_stock'])) { if (isset($data['manage_stock'])) {
$product->set_manage_stock($data['manage_stock']); $product->set_manage_stock($data['manage_stock']);
@@ -347,11 +391,11 @@ class ProductsController {
} }
} }
if (isset($data['stock_status'])) $product->set_stock_status($data['stock_status']); if (isset($data['stock_status'])) $product->set_stock_status(sanitize_key($data['stock_status']));
if (isset($data['weight'])) $product->set_weight($data['weight']); if (isset($data['weight'])) $product->set_weight(self::sanitize_number($data['weight']));
if (isset($data['length'])) $product->set_length($data['length']); if (isset($data['length'])) $product->set_length(self::sanitize_number($data['length']));
if (isset($data['width'])) $product->set_width($data['width']); if (isset($data['width'])) $product->set_width(self::sanitize_number($data['width']));
if (isset($data['height'])) $product->set_height($data['height']); if (isset($data['height'])) $product->set_height(self::sanitize_number($data['height']));
// Categories // Categories
if (isset($data['categories'])) { if (isset($data['categories'])) {