DDOS ATTACK on category page

Hi all. Last week i had been DDOS attacked. There was 3 million requests in 10 minutes on category pages. Of course server was down. They used ?items_per_page=96000 at the end of the link.

Any idea how to restrict using this? For example when they will try to access more than 48 - they will be redirected to main page or to main category.

THanks

Hello,

This check should and will be added for sure by default by CS-Cart team and meanwhile to prevent this you can simply use the hook “'get_products” to add the code:

        $product_steps = fn_get_product_pagination_steps( Registry::get('settings.Appearance.columns_in_products_list'), Registry::get('settings.Appearance.products_per_page') );
        $max_items_per_page = max($product_steps);

        if (!empty($params['limit'])) {
            $params['limit'] = (int) $params['limit'] > $max_items_per_page ? $max_items_per_page : $params['limit'];
        } elseif (!empty($params['items_per_page'])) {
            $params['items_per_page'] = (int) $params['items_per_page'] > $max_items_per_page ? $max_items_per_page : $params['items_per_page'];
        }

As example I will use the add-on “My Changes”

File: app/addons/my_changes/init.php

<?php

defined('BOOTSTRAP') or die('Access denied');

fn_register_hooks(
    'get_products'
);

File: app/addons/my_changes/func.php

function fn_my_changes_get_products( &$params, $fields, $sortings, $condition, $join, $sorting, $group_by, $lang_code, $having)
{
    if (AREA == 'C'){
        $product_steps = fn_get_product_pagination_steps( Registry::get('settings.Appearance.columns_in_products_list'), Registry::get('settings.Appearance.products_per_page') );
        $max_items_per_page = max($product_steps);

        if (!empty($params['limit'])) {
            $params['limit'] = (int) $params['limit'] > $max_items_per_page ? $max_items_per_page : $params['limit'];
        } elseif (!empty($params['items_per_page'])) {
            $params['items_per_page'] = (int) $params['items_per_page'] > $max_items_per_page ? $max_items_per_page : $params['items_per_page'];
        }
    }
}

I hope the above is useful to you.

Updated: March 29, 2023

5 Likes

That’s a nice modification you have there! :slight_smile:

I want to offer an alternative in case someone ever wants to apply the limits to every entity that might be affected by the items_per_page parameter:

This one example will also use the My changes add-on.

File: app/addons/my_changes/init.php

<?php

if (!defined('BOOTSTRAP')) { die('Access denied'); }

fn_register_hooks(
    'before_dispatch'
);

File: app/addons/my_changes/func.php

<?php

use Tygh\Enum\SiteArea;

if (!defined('BOOTSTRAP')) { die('Access denied'); }

function fn_my_changes_before_dispatch()
{
    if (!SiteArea::isStorefront(AREA)) {
        return;
    }

    if (
        isset($_REQUEST['items_per_page'])
        && (int) $_REQUEST['items_per_page']
    ) {
        if ($_REQUEST['items_per_page'] > 96) {
            $_REQUEST['items_per_page'] = 96;
        }
    }
}

This will limit the items per page to a maximum of 96 for all entities that can use this parameter.

6 Likes

I didnt’ realize one could go more than 96. Limiting it to no more than 96 should be default.

2 Likes

Yes, I agree with you and have created the new feature request for the developers on this case.

1 Like

Thanks everyone for helping me. Awesome community.

1 Like

Apparently this was never implemented. I just had an issue with a bot requesting millions of items_per_page causing the server to crash. I now have your code in my_changes. :wink:

1 Like

I have highlighted the forgotten task. :smiling_face_with_tear:

Thanks for bringing it back to our attention!

1 Like

I had to remove this because it was blocking items per page in admin.

1 Like

Yes, it seems that this part of the code is unnecessary.

I’ve updated the code in my previous message.

Well, it also looks like they are attacking outrageous numbers everywhere…
/?sort_by=679425066&sort_order=asc&layout=short_list&page=433484397
which gave the error
PHP Fatal error: Allowed memory size of 1073741824 bytes exhausted (tried to allocate 8589934600 bytes) in /app/functions/fn.common.php on line 750

With the help of AI, this is what I have now…
/app/addons/my_changes/init.php


if ( !defined('BOOTSTRAP') ) { die('Access denied'); }

fn_register_hooks(
    'before_dispatch',
    'generate_pagination_pre'
);

/app/addons/my_changes/func.php


use Tygh\Enum\SiteArea;

if (!defined('BOOTSTRAP')) { die('Access denied'); }

/**
 * Filter items_per_page and rogue high page requests from the initial query string
 */
function fn_my_changes_before_dispatch()
{
    if (SiteArea::isStorefront(AREA)) {
        // 1. Cap items_per_page to 96
        if (isset($_REQUEST['items_per_page'])) {
            $items_per_page = (int) $_REQUEST['items_per_page'];
            if ($items_per_page > 96 || $items_per_page < 1) {
                $_REQUEST['items_per_page'] = 96;
            }
        }

        // 2. Proactive shield: Drop impossible page counts sent by scrapers/bots
        if (isset($_REQUEST['page'])) {
            $page = (int) $_REQUEST['page'];
            // If page is negative or absurdly high (e.g., over 5,000 pages), reset it to 1
            if ($page < 1 || $page > 5000) {
                $_REQUEST['page'] = 1;
            }
        }
    }
}

/**
 * Final safety shield: Fix math logic errors right before range() executes
 */
function fn_my_changes_generate_pagination_pre(&$params, $area, &$deviation)
{
    if (SiteArea::isStorefront($area)) {
        if (isset($params['items_per_page'])) {
            $current_val = (int) $params['items_per_page'];
            $params['items_per_page'] = ($current_val > 96 || $current_val < 1) ? 96 : $current_val;
        }
    }

    if (isset($params['total_items'])) {
        $params['total_items'] = max(0, (int) $params['total_items']);
    }

    // Explicitly fix the exact crash vector found in the query log
    if (isset($params['page'])) {
        $params['page'] = max(1, (int) $params['page']);
        
        if (!empty($params['items_per_page'])) {
            $total_pages = (int) ceil($params['total_items'] / $params['items_per_page']);
            
            // Critical fix: If the requested page is greater than reality, slam it to the max page
            if ($params['page'] > $total_pages) {
                $params['page'] = max(1, $total_pages);
            }
        }
    }
}
2 Likes