CVE со скоростью света. Исправляем публичный эксплоит для LiteSpeed Cache

Содержание статьи

  • Немного о ситуации
  • Анализ уязвимости
  • Зачем генерируется хеш?
  • Как генерируется хеш?
  • Что такое seed и почему это важно?
  • Как работает microtime()
  • Эксплоит
  • Выводы

Се­год­ня я раз­беру уяз­вимость в LiteSpeed Cache — популяр­ном пла­гине для уско­рения работы сай­тов. Пла­гин работа­ет с движ­ками WooCommerce, bbPress, ClassicPress и Yoast, на сегод­ня у него более пяти мил­лионов уста­новок. Давай пос­мотрим, как генера­ция недос­таточ­но качес­твен­ных слу­чай­ных чисел при­вела к воз­можнос­ти повысить при­виле­гии до адми­на.

Каж­дый раз пос­ле того, как в новос­тях пуб­лику­ют какую‑то мегак­рутую уяз­вимость, мож­но наб­людать четыре типа людей:

  • Те, кто спе­шить опуб­ликовать бес­плат­ный экс­пло­ит, который будет неп­рименим без дорабо­ток.
  • Те, кто пуб­лику­ет PoC (доказа­тель­ство кон­цепции) на GitHub, YouTube или дру­гих плат­формах, оставляя ссыл­ку для покуп­ки или кон­такты.
  • Те, кто ана­лизи­рует уяз­вимость, под­робно опи­сывая ее опас­ность, но без пре­дос­тавле­ния PoC.
  • Лю­бите­ли пок­ритико­вать всех осталь­ных.
  • Се­год­ня я сов­мещу тре­тий и чет­вертый типы: рас­ска­жу тебе об уяз­вимос­ти и пок­ритикую хакеров из пер­вых двух катего­рий.

     

    Немного о ситуации

    Уяз­вимость, которую мы рас­смот­рим, нашел Джон Блэк­борн, и она получи­ла иден­тифика­тор CVE-2024-28000.

    Ком­пания Patchstack утвер­жда­ет:

    Wordfence (кон­курент) заяв­ляет:

     

    Анализ уязвимости

    Уяз­вимость, по сути, зак­люча­ется в том, что где‑то в нед­рах пла­гина генери­рует­ся хеш, который исполь­зует­ся в качес­тве куки, через которую мож­но соз­дать акка­унт адми­нис­тра­тора. При этом в осно­ве хеша — слу­чай­ное чис­ло, сге­нери­рован­ное на осно­ве долей секун­ды. Чис­ло ком­бинаций для таких чисел огра­ничен­но, что поз­воля­ет подоб­рать хеш.

     

    Зачем генерируется хеш?

    У LiteSpeed Cache есть фун­кция для симуля­ции поль­зовате­ля, внут­ри которой соз­дает­ся и сох­раня­ется хеш. Этот хеш исполь­зует­ся как кука litespeed_hash. Для его генера­ции нуж­но вклю­чить фун­кцию кра­уле­ра. Если ты поищешь в коде get_hash, то уви­дишь, что эта фун­кция вызыва­ется в двух мес­тах. Преж­де чем начать ана­лиз того, как генери­рует­ся хеш, давай раз­берем­ся, в каких слу­чаях эта фун­кция вызыва­ется.

    Фун­кция self_curl пред­назна­чена для выпол­нения HTTP-зап­роса с исполь­зовани­ем биб­лиоте­ки cURL:

    public function self_curl($url, $ua, $uid = false, $accept = false) { // $accept not in use yet $this->_crawler_conf['base'] = home_url(); $this->_crawler_conf['ua'] = $ua; if ($accept) { $this->_crawler_conf['headers'] = array('Accept: ' . $accept); } if ($uid) { $this->_crawler_conf['cookies']['litespeed_role'] = $uid; $this->_crawler_conf['cookies']['litespeed_hash'] = Router::get_hash(); } $options = $this->_get_curl_options(); $options[CURLOPT_HEADER] = false; $options[CURLOPT_FOLLOWLOCATION] = true; $ch = curl_init(); curl_setopt_array($ch, $options); curl_setopt($ch, CURLOPT_URL, $url); $result = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($code != 200) { self::debug('❌ Response code is not 200 in self_curl() [code] ' . var_export($code, true)); return false; } return $result; }

    Эта фун­кция при­нима­ет нес­коль­ко парамет­ров, таких как URL, User-Agent и иден­тифика­тор поль­зовате­ля ($uid). Она уста­нав­лива­ет базовый URL, исполь­зуя метод home_url(), и сох­раня­ет передан­ный User-Agent. Если передан иден­тифика­тор поль­зовате­ля, соз­дают­ся куки litespeed_role и litespeed_hash.

    Де­ло в том, что фун­кция self_curl исполь­зует­ся в фун­кции prepare_html.

    public function prepare_html($request_url, $user_agent, $uid = false) { $html = $this->cls('Crawler')->self_curl(add_query_arg('LSCWP_CTRL', 'before_optm', $request_url), $user_agent, $uid);

    Фун­кция prepare_html исполь­зует­ся в фун­кции _send_req.

    private function _send_req($request_url, $queue_k, $uid, $user_agent, $vary, $url_tag, $type, $is_mobile, $is_webp) { // Check if has credit to push or not $err = false; $allowance = $this->cls('Cloud')->allowance(Cloud::SVC_CCSS, $err); if (!$allowance) { Debug2::debug('[CCSS] ❌ No credit: ' . $err); $err && Admin_Display::error(Error::msg($err)); return 'out_of_quota'; } // Update css request status $this->_summary['curr_request_' . $type] = time(); self::save_summary(); // Gather guest HTML to send $html = $this->prepare_html($request_url, $user_agent, $uid); if (!$html) { return false; }.....

    Фун­кция _send_req вызыва­ется из _cron_handler.

    private function _cron_handler($type, $continue) { $this->_queue = $this->load_queue($type); if (empty($this->_queue)) { return; } $type_tag = strtoupper($type); // For cron, need to check request interval too if (!$continue) { if (!empty($this->_summary['curr_request_' . $type]) && time() - $this->_summary['curr_request_' . $type] < 300 && !$this->conf(self::O_DEBUG)) { Debug2::debug('[' . $type_tag . '] Last request not done'); return; } } $i = 0; $timeoutLimit = ini_get('max_execution_time'); $this->_endts = time() + $timeoutLimit; foreach ($this->_queue as $k => $v) { if (!empty($v['_status'])) { continue; } if (function_exists('set_time_limit')) { $this->_endts += 120; set_time_limit(120); } if ($this->_endts - time() < 10) { // self::debug("🚨 End loop due to timeout limit reached " . $timeoutLimit . "s"); // return; } Debug2::debug('[' . $type_tag . '] cron job [tag] ' . $k . ' [url] ' . $v['url'] . ($v['is_mobile'] ? ' 📱 ' : '') . ' [UA] ' . $v['user_agent']); if ($type == 'ccss' && empty($v['url_tag'])) { unset($this->_queue[$k]); $this->save_queue($type, $this->_queue); Debug2::debug('[CCSS] wrong queue_ccss format'); continue; } if (!isset($v['is_webp'])) { $v['is_webp'] = false; } $i++; $res = $this->_send_req($v['url'], $k, $v['uid'], $v['user_agent'], $v['vary'], $v['url_tag'], $type, $v['is_mobile'], $v['is_webp']); if (!$res) { // Status is wrong, drop this this->_queue unset($this->_queue[$k]); $this->save_queue($type, $this->_queue);.....

    Фун­кция _cron_handler вызыва­ется из cron_ccss.

    public static function cron_ccss($continue = false) { $_instance = self::cls(); return $_instance->_cron_handler('ccss', $continue); }

    Фун­кция cron_ccss слу­жит обер­ткой для запус­ка фун­кции _cron_handler. Она вызыва­ет фун­кцию _cron_handler, переда­вая в качес­тве типа ccss и флаг $continue. Фун­кция _cron_handler заг­ружа­ет оче­редь задач для ука­зан­ного типа (в дан­ном слу­чае — ccss) через метод load_queue. Если оче­редь пус­тая, фун­кция завер­шает выпол­нение, так как нет задач для обра­бот­ки.

    Источник: xakep.ru

    Ответить

    Ваш адрес email не будет опубликован. Обязательные поля помечены *