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! 
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. 
1 Like
I have highlighted the forgotten task. 
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