Странные ссылки в постраничной навигации (пагинации) движка Joomla

archive view archive save

joomla.jpg На сайте с включённым кэшем были замечены множественные нежелательные и причудливые параметры в ссылках постраничной навигации. Глюк относительно старый, но многими уже малость подзабытый.., и может ли он быть связан с переполнением кэша на сайте?

Несколькими днями ранее мы писали о проблеме с избыточным кэшированием в /cache/com_k2_extended, размер которого достигал 17 ГБ и без остановки на обед или сон продолжал расти прибавляя примерно по 2 ГБ в день!

com_k2_extended - это отдельный клинический случай говнокодинга, про который можно даже книжку написать...

  1. Откуда берутся лишние параметры в ссылках
  2. .htaccess deny unwanted query parameters in request url
  3. Fix pagination.php
  4. robots.txt Disallow
  5. Принятие неизбежного
  6. Результат

Откуда берутся лишние параметры в ссылках

Не исключено, что на ГИГовую проблему с com_k2_extended кэшем оказала влияние и проблема с постраничной навигацией списка материалов (pagination), выраженная в нежелательных параметрах строки запроса $_SERVER['REQUEST_URI'], таких, как например, но не ограничиваясь ими:

/itemlist?external_browser_redirect=true&start=0
/itemlist?3x=3x&start=0
/itemlist?url=/itemlist/tag/ёклмн&start=60
/itemlist?url=/itemlist/tag/??????????%20???????&start=0

Очевидно, что таких параметров, как 3x=3x, явно не существует, и по всем признакам присутствует явная вредоносная активность.

Это мы подметили ещё до начала правки говнокогда связанного с кэшем com_k2_extended.

С проблемой кэша com_k2_extended такие УРЛы никак не связаны, проверено выражением:

sudo grep -rnw '/www/cache/com_k2_extended/' -e 'FAKE_PARAM'
ничего нет!

Кэшируется всё это дело в другом месте, /www/cache/com_k2/, проверено выражением:

sudo grep -rnw '/www/cache/com_k2/' -e 'FAKE_PARAM'
/www/cache/com_k2/5bcecfbf0285810c7684ebdf955539df-cache-com_k2-7f1e0663672edd0231eb46530d3b4835.php:583: <ul class="pagination-list">...<li class="last"><a href="/news/sud?FAKE_PARAM=666&amp;start=1285">>></a></li></ul> </div>
/www/cache/com_k2/5bcecfbf0285810c7684ebdf955539df-cache-com_k2-7f1e0663672edd0231eb46530d3b4835.php:587:";s:4:"head";a:12:{s:5:"title";s:38:"Мой сайт";s:11:"description";s:0:"";s:4:"link";s:0:"";s:8:"metaTags";a:2:{s:10:"http-equiv";a:1:{s:12:"content-type";s:24:"text/html; charset=utf-8";}s:4:"name";a:10:{s:8:"keywords";s:0:"";s:6:"rights";N;s:6:"robots";s:0:"";s:6:"og:url";s:58:"/news/mycat?FAKE_PARAM=666&start=25";s:7:"og:type";s:7:"website";s:8:"og:title";s:38:"Моя категория";s:14:"og:description";s:0:"";

Ещё может быть по адресу /www/cache/page/, если включен плагин Система - Кэш.

Проверить ещё можно и так:

  1. Открываем:
    Техническое обслуживание: Очистка кэша (Сайт) - Мой сайт - Панель управления
    /administrator/index.php?option=com_cache
  2. Удаляем только "Группа кэша" com_k2, com_k2_extended не трогаем, потом сразу после удаления НЕЗАМЕДЛИТЕЛЬНО (пока ссылку с лимитом start=60 не открыл какой-то робот) открываем какой-нибудь УРЛ со своими причудливыми параметрами, например такой:
    /itemlist?FAKE_PARAM=666&start=60
  3. Теперь смотрим (наводя курсор мыши) на ссылки в пагинации (постраничной навигации), где по всем ссылкам (номерам страниц) будем видеть:
    /itemlist?FAKE_PARAM=666&start=10
    ...
    /itemlist?FAKE_PARAM=666&start=100

Тоже самое можно было повторить и с пагинацией в категориях, например по ссылке:

  • /news/mycat?FAKE_PARAM=666&start=25

ТАКУЮ КРИВУЮ ССЫЛКУ С ФЕЙКОВЫМ ПАРАМЕТРОМ ОНО ПРОПИСЫВАЕТ ЕЩЁ И В:

<meta property="og:url" content="/news/mycat?FAKE_PARAM=666&amp;start=25" />

Откуда по всей видимости её и берут остальные роботы, после того как её открыл какой-то один из вредоносных роботов, а дальше пошло-поехало...

ОЧЕВИДНО, что не проверяя перед всем этим правильность подставленных в строку запроса параметров, параметры для пагинации тупо копируются из $_SERVER['REQUEST_URI']:

ПОИГРАТЬ С $_SERVER['REQUEST_URI'] можно создав tmp.php:

echo '$_SERVER[\'REQUEST_URI\']: ' . $_SERVER['REQUEST_URI'];

После чего открыть его в браузере по ссылкам:

  • /tmp.php?FAKE_PARAM1=666&start=25
  • /tmp.php?FAKE_PARAM2=999&start=25

Изменяя всё, что расположено после знака вопроса /tmp.php?.., на своё усмотрение.

ПО ВСЕЙ ВИДИМОСТИ ВХОДЯЩИЕ ПАРАМЕТРЫ ПАГИНАЦИИ НЕ ПРОВЕРЯЮТСЯ и в com_k2, как не проверяются они и в:

$ sudo locate pagination|less
/www/layouts/joomla/pagination
/www/layouts/joomla/pagination/index.html
/www/layouts/joomla/pagination/link.php
/www/layouts/joomla/pagination/links.php
/www/layouts/joomla/pagination/list.php
/www/media/akeeba_strapper/less/bootstrap/pagination.less
/www/media/jui/less/pagination.less
/www/templates/my_tpl/html/pagination.php
/www/templates/protostar/html/pagination.php

Итак, приходим к выводу, что:

  1. лишние параметры добавляются в строку запроса роботами (киборгами);
  2. всё это безобразие отправляется в кэш и залипает там на срок его жизни установленный в настройках;
  3. открыть ссылку из уже изуродованного списка постраничной навигации, которая ведёт на следующую ещё не попавшую в кэш страницу, может другой робот и/или условно нормальный посетитель;
  4. так, по принципу домино число отравленного кэша с изуродованными ссылками в постраничной навигации может нарастать в геометрической прогрессии;
  5. вот, где-то как-то оно так.

.htaccess deny unwanted query parameters in request url

Как оперативный вариант затыкания для таких дырок, можно использовать .htaccess:

RewriteEngine On
 
## --- DENY GET CONTENT WITH FAKE PARAM ---
RewriteCond %{THE_REQUEST} ^GET(\ )/itemlist.php [NC]
RewriteCond %{QUERY_STRING} !^(^$|start=) [NC]
RewriteRule ^(.*)$ - [R=404,L]
 
## --- DENY FAKE PARAM FOR MULTIPLE URL ---
RewriteCond %{THE_REQUEST} ^GET(\ )/itemlist [NC]
RewriteCond %{QUERY_STRING} !^start= [NC,OR]
RewriteCond %{THE_REQUEST} ^GET(\ )/news [NC]
RewriteCond %{QUERY_STRING} !^(^$|start=) [NC]
RewriteRule ^(.*)$ - [R=404,L]

В RewriteCond %{THE_REQUEST} для УРЛ можно и регулярных выражений добавить, пример выше дан по-проще шоб слабонервным слоям населения мозг не разорвало.

Как-то так ещё можно:

RewriteCond %{THE_REQUEST} ^GET(\ )/(blog|tags)(/[a-z]+)? [NC]
RewriteCond %{QUERY_STRING} !^(^$|start=) [NC]
RewriteRule ^(.*)$ - [R=404,L]

Однако, исчерпывающий список УРЛ с пагинацией составить довольно проблематично, когда на сайте есть множество категорий (подкатегорий) и иных компонентов её использующих.

Например, взял робот да и зашёл на главную с параметром ?user=18 и все ссылки в пагинации с главной превратились в /itemlist?user=18&start=хх, переход по которым, согласно правил выше, закончится ошибкой 404, - тогда для главной страницы добавим другое правило:

## --- DENY GET CONTENT WITH FAKE PARAM ---
RewriteCond %{REQUEST_URI} ^/$ [NC]
RewriteCond %{QUERY_STRING} !^(^$|start=) [NC]
RewriteRule ^(.*)$ - [R=404,L]

Поэтому, одного универсального правила .htaccess в отдельных случаях придумать никак нельзя.

Правила .htaccess запрещающие нежелательные параметры в строке запроса, - можно считать костылём, а может и полноценным решением если УРЛов с постраничной навигацией на сайте немного.

В идеале же наверное будет лучше ещё добавить и некий кусок кода фильтрующий параметры запроса где-то в /www/templates/my_tpl/html/pagination.php, который будет фильтровать постраничную навигацию при любых условиях оставляя для пагинации только нужные параметры.

Но, тут опять же.., если говно-кодеру одного из говно-недо-компонентов придумалось использовать при пагинации какую-нибудь нестандартную переменную, то отфильтровав их ожидаемый от такого компонента функционал не будет фунциклировать - ЭТО НУЖНО ПОМНИТЬ!

.htaccess метод запрета на несуществующие параметры строки запроса хорош тем, что РНР код, веб-сервер и сервер БД не обрабатывает целевую страницу, что разумеется снижает общую нагрузку системы.

Fix pagination.php

Следующим делом пофиксим сам файл дырявой постраничной навигации добавив туда две небольшие PHP функции hrefFilter($href) и dataFix($data):

function hrefFilter($href){
  $allowKeys = [
    'start' => '',
    'format' => '',
    'option' => '',
    'view' => '',
    'layout' => '',
    'tpl' => '',
    'id' => '',
    'Itemid' => '',
  ];
  $href = parse_url(htmlspecialchars_decode($href));
  parse_str($href['query'], $params);
  if (count($params) > 0) {
    foreach ($params as $key => $value) {
      // isset() faster instead array_key_exists()
      if (!isset($allowKeys[$key])) {
        unset($params[$key]);
      }
    }
  }
  $href['query'] = http_build_query($params);
  if(empty(trim($href['query']))){
    return $href['path'];
  }
  return htmlspecialchars($href['path'] . '?' . $href['query']);
}
 
function dataFix($data){
  $data = preg_replace_callback(
    '/<a (.*)href=["\'](.*)[\'"](.*)?>/Ui',
    function($matches) {
      return "<a $matches[1]href=\"" . hrefFilter($matches[2]) . "\"$matches[3]>";
    },
    $data
  );
  return $data;
}

Которые нужно разместить в файле /www/templates/my_tpl/html/pagination.php сразу после defined('_JEXEC') or die;, а строки имеющие вид $page['data'] (содержат элемент списка со ссылкой) завернуть в нашу функцию dataFix($page['data']).

Теперь даже если по какой-то причине правила из .htaccess дадут сбой (перестанут работать), или на сайте появится новый УРЛ с новым разделом/категорийе непопавшим в правила .htaccess, то фильтрацией займётся функция dataFix(), которая найдёт каждую ссылку и отбросит из неё любые переменные из строки запроса не перечисленные в массиве $allowKeys функции hrefFilter().

robots.txt Disallow

robots.txt - это самое последнее, что можно ещё придумать:

User-agent: *
Allow: /blog?start=*
Disallow: /blog?*

Но, по-факту роботы, особенно злое..... зловредные, кладут на правила из robots.txt и продолжают заполонять всю планету.

Принятие неизбежного

5 стадий принятия неизбежного, изменений и управленческих решений:

  1. Отрицание;
  2. Гнев;
  3. Торг;
  4. Депресняк;
  5. Принятие.

Прежде чем ты изменишься, что-то невероятно для тебя важное должно оказаться под угрозой.

Ричард Бах. Карманный справочник Мессии

ДО ВЕРСИИ движка Joomla 5.х, в страницы постраничной навигации автоматически включались все параметры входящие в строку запроса.

page automatically included all query parameters that are present in the current request, - вот оригинал:

joomla unwanted url query parameters in pagination

J5.x:Pagination class removes query parameters — Joomla! Documentation

So far, the links for the individual pages as well as the "first", "last", "next" and "previous" page automatically included all query parameters that are present in the current request. That behavior create a cache poisoning attack vector.

In order to mitigate that vector, a behavior change had to be introduced. The pagination class will by default only include the following query parameters into the URL generation process:

format
option
view
layout
tpl
id
Itemid

create a cache poisoning attack vector - соответственно ОТРАВЛЯЕТ КЭШ НЕДОКОМПОНЕНТОВ не исключая его переполнения.

Только начиная с 5-й версии говнокодеры движка приняли неизбежное начав фильтровать параметры запроса к пагинации.

ВМЕСТЕ С ТЕМ, КАЖДЫЙ отдельно взятый КОМПОНЕНТ ДОЛЖЕН САМОСТОЯТЕЛЬНО ПРОВЕРЯТЬ ВХОДЯЩИЕ В НЕГО ПАРАМЕТРЫ отбрасывая лишние ПЕРЕД КЭШИРОВАНИЕМ КОНТЕНТА ВО ИЗБЕЖАНИЕ АТАКИ НА ЕГО ПЕРЕПОЛНЕНИЕ!!! О чём очевидно не все говнокодеры задумываются, как например в случае с ГОВНО-кэшем com_k2_extended, где md5-ИД-кэш-файла создавался из входящих в $_SERVER['REQUEST_URI'] параметров $view и $task, а также изменчивых параметров самого АЖ ЦЕЛОГО ОБЪЕКТА материала $items[$i] КОТОРЫЙ ЦЕЛИКОМ ОТПРАВЛЯЛСЯ В КЭШ С ЛОГИНОМ И ОТКРЫТЫМ не зашифрованным ПАРОЛЕМ ОТ БД + ЛОГИН С ХЭШЕМ ПАРОЛЯ ЮЗЕРА СОЗДАВШЕГО МАТЕРИАЛ, что с точки зрения секюрности совсем нефеншуёво!

С ГОВНО-КОДЕРАМИ, КАК ОБЫЧНО, РАЗГОВОР КОРОТКИЙ... ОНИ ПИ..РАСЫ обычно даже не вникают в то, что ты им пишешь и о чём ты там их спрашиваешь! Аргументация у них у всех как по шаблону:

  1. рекомендация протереть монитор;
  2. докупить ресурсов хостинга (новый ПК);
  3. выключить собеседнику микрофон;
  4. потушить свет и приказать всем долго жить;
  5. .., -

как-то так:

На осознание и принятие всего того им нужны десятки-сотни лет.., а бывает и тысячелетиями нихрена недоходит.

Результат

Теперь, когда все дырки прикрыты, можно включать кэш обратно взад, - можно даже сверху ещё и плагин Система - Кэш включить.

Частичное влияние проблемы с пагинацией на проблему с com_k2_extended кэшем полностью исключить нельзя, но этот ФАКТОР УЧИТЫВАЛСЯ путём подстановки вместо переменных $view и $task фиксированных строковых значений, при которых в com_k2_extended продолжали успешно создаваться дубликаты (включая в них логин и пароль от БД в открытом виде!) кешированных файлов, - это при ежечасовой, и даже чаще, полной ручной очистке кэша и времени его жизни в 24 часа.

Наличие кучи дублей (по хэшу md5sum) в кэше проверялось програмой fdupes:

sudo fdupes /var/www/cache/com_k2_extended/ | less

com_k2_extended кэш делали 100%-ные Пи..д.у.расы - это ОДНОЗНАЧНО!!! На то есть отдельная статья, которая на стадии написания - а писать про com_k2_extended там можно много.., начиная с кэшИД почти что из /dev/random до кучи дубликатов в кэше, попутно вспоминая незлым тыхым словом открытые логины/пароли в кэш-файлах и прочую "логику" говнокода...

Да и говнокодеры самого движка, то ещё те Пи..пуасы..!

Если короче говоря, то одно накладываясь на другое создаёт как раз то, что обычно получается на выходе - ГАВ.., одним словом!

Примерно похожий ..здец происходит не только с говнокодом/сайтами/серверами, но и с людями:

  1. Система образования манипулируя историей подменяет понятия насаждая в головы ложные системы ценностей, безграничное чувство вины и какого-то непонятного долга;
  2. Система здравоЗахоронения разрушает здравие;
  3. ПравоНарушительная и судебная система разрушает справедливость и правосудие;
  4. Зомбоящик вёслами выжирает мозг переполняя внутренние буфера в оперативке (РАМе) мозга;
  5. Современное рабство нагружает фейковыми и безполезными $task(ами);
  6. Оргазм не справляется с таким наплывом дерьма, начинает глюкать, заболевает, и как следствие приходит в полную негодность.

САМОЕ ПЕЧАЛЬНОЕ, что они же почти все сейчас вокруг упоротые (наколотые, зомбокцинированные БАРАНоВаком) с пэрэмогами на всю голову, и взывать к голосу разума, когда его полностью подменил маразм, совершенно безсмысленно, сами понимаете.

И чтобы всем тем долбоё..., скатынякам и падлюкам, дошло, что они капитально неправы, - они обязательно должны пройти все 5 стадий принятия неизбежного: Отрицание; Гнев; Торг; Депрессия; Принятие.

Но, как показывает практика, пройти квест из 5 стадий принятия неизбежного удаётся далеко не всем и не каждому, как и попасть в яйцеклетку при зачатии, а жить (быть художником, программистом или чиновником) можно и без мозга, - это удивительный факт!

Пока же вся та безмозглая (в прямом смысле) упоротая био-масса (ака сообщество) тупого рогатого генетически-модифицированного скота пробивая рогами и копытами дно пытается пройти 5 стадий принятия неизбежного, те немногие вменяемые/сознательные/самокритичные/разумные вынуждены как-то спасаться/избальяться от говнокода самостоятельно, - это иногда сложно, но можно, хоть временами и жутко бесит не так сам говнокод, как его говнокодеры.

P.S. Не все OpenSource разработки одинаково полезны! (с)


Об авторе
Иван Шаман
Меня нет ни в Инстаграмме ни в Фейсбуке, я просто хожу по улицам и рассказываю первым встречным: сколько зарабатываю; с кем дружу; где живу и чем дышу. У меня даже появилось несколько подписчиков: ПСИХоЛОХ и участковый полицай!
Ещё статьи автора

Добавить комментарий

АХТУНГ! Все комменты гостей модерасятся модерастом.
  1. Мессаги исключительно рекламного содержания, либо содержащие только одни оценочные суждения типа "круто" ("отлично", "спасибо", "автор дебил" и т.п.) не публикуются;
  2. Злостным спамерам, пранкерам и прочей сетевой нечисти рекомендуем напрасно не тратить своего времени и удовлетворять свои больные фантазии на специализированных Интернет ресурсах!;
  3. Разумная обоснованная критика, замечания, дополнения приветствуются. Поля помеченные символом * обязательны к заполнению.


Защитный код
Обновить

Комментарии   

Иван Шаман
0 #2 Иван Шаман 10.10.2024 08:23
Цитирую АдМинь БагоИскатель:
isset() пэрэмагает всех разом

Ты чертовски прав мой Фрэнд, при больших нагрузках каждая доля секунды важна, ща так и сделаем. Данкэшон!
Цитировать
АдМинь БагоИскатель
+1 #1 АдМинь БагоИскатель 10.10.2024 06:30
array_key_exists() шустрее чем in_array(), но isset() пэрэмагает всех разом:

так будет феншуёвее:
Код:if (!isset($allowKeys[$key])) {
//if (!array_key_exists($key, $allowKeys)) {
unset($params[$key]);
}
Цитировать
Комментарии в блоге
Новое на форуме