Discuz!X 系列 HTTP_X_FORWARDED_FOR 绕过限制进行密码爆破
Posted 羊小弟
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Discuz!X 系列 HTTP_X_FORWARDED_FOR 绕过限制进行密码爆破相关的知识,希望对你有一定的参考价值。
分析有个不对头的地方:http://wooyun.jozxing.cc/static/bugs/wooyun-2014-080211.html
后面再补
这个漏洞比较简单。
我们看到配置文件来。/include/common.inc.php 第86-94行。
if(getenv(\'HTTP_CLIENT_IP\') && strcasecmp(getenv(\'HTTP_CLIENT_IP\'), \'unknown\')) { $onlineip = getenv(\'HTTP_CLIENT_IP\'); } elseif(getenv(\'HTTP_X_FORWARDED_FOR\') && strcasecmp(getenv(\'HTTP_X_FORWARDED_FOR\'), \'unknown\')) { $onlineip = getenv(\'HTTP_X_FORWARDED_FOR\'); } elseif(getenv(\'REMOTE_ADDR\') && strcasecmp(getenv(\'REMOTE_ADDR\'), \'unknown\')) { $onlineip = getenv(\'REMOTE_ADDR\'); } elseif(isset($_SERVER[\'REMOTE_ADDR\']) && $_SERVER[\'REMOTE_ADDR\'] && strcasecmp($_SERVER[\'REMOTE_ADDR\'], \'unknown\')) { $onlineip = $_SERVER[\'REMOTE_ADDR\']; }
如果获取不到HTTP_CLIENT_IP的环境变量,返回false,往下执行。接着就获取HTTP_X_FORWARDED_FOR的环境变量,这个变量用户是可控的,所以我们可以伪造HTTP_X_FORWARDED_FOR来对$onlineip进行赋值。
接着看到用户登录界面,位于 logging.php 第39-139行
if(!($loginperm = logincheck())) { showmessage(\'login_strike\'); }
我们跟进logincheck()
位于 /include/misc.func.php 第368-381行。
function logincheck() { global $db, $tablepre, $onlineip, $timestamp; $return = 0; $login = $db->fetch_first("SELECT count, lastupdate FROM {$tablepre}failedlogins WHERE ip=\'$onlineip\'"); $return = (!$login || ($timestamp - $login[\'lastupdate\'] > 900)) ? 4 : max(0, 5 - $login[\'count\']); if($return == 4) { $db->query("REPLACE INTO {$tablepre}failedlogins (ip, count, lastupdate) VALUES (\'$onlineip\', \'1\', \'$timestamp\')"); $db->query("DELETE FROM {$tablepre}failedlogins WHERE lastupdate<$timestamp-901", \'UNBUFFERED\'); } return $return; }
先导入全局变量,然后进行查询。在cdb_failedlogins中查询$onlineip,
如果没查询到,!$login 将返回True,又或者当前的时间减去查询到的id上次登录的时间大于900秒 ($timestamp - $login[\'lastupdate\'] > 900)
就返回4,否则返回max(0, 5 - $login[\'count\'])当中最大的值
如果 $return是4的话,往下执行:
REPLACE INTO {$tablepre}failedlogins (ip, count, lastupdate) VALUES (\'$onlineip\', \'1\', \'$timestamp\')
来看下replace的用法:
1.replace into
replace into table (id,name) values(\'1\',\'aa\'),(\'2\',\'bb\')
此语句的作用是向表table中插入两条记录。如果主键id为1或2不存在
就相当于
insert into table (id,name) values(\'1\',\'aa\'),(\'2\',\'bb\')
如果存在相同的值则不会插入数据
也就是在表中记录下 $onlineip 以及登录次数设置为1,还有当前的时间值。
接着就是对表进行delete操作,删除被限制900秒登录的ip,然后把$return赋值给$loginperm
返回logging.php ,如果登录失败的话 会执行这个函数loginfailed($loginperm);
function loginfailed($permission) { global $db, $tablepre, $onlineip, $timestamp; $db->query("UPDATE {$tablepre}failedlogins SET count=count+1, lastupdate=\'$timestamp\' WHERE ip=\'$onlineip\'"); }
所以会对当前的$onlineip的count值进行+1 操作。
综上所述,我们可以利用变化的X-Forwarded-For值进行伪造,绕过同一ip可登录五次的限制。
在管理员登录这里也有个
function init_var() { $this->time = time(); $cip = getenv(\'HTTP_CLIENT_IP\'); $xip = getenv(\'HTTP_X_FORWARDED_FOR\'); $rip = getenv(\'REMOTE_ADDR\'); $srip = $_SERVER[\'REMOTE_ADDR\']; if($cip && strcasecmp($cip, \'unknown\')) { $this->onlineip = $cip; } elseif($xip && strcasecmp($xip, \'unknown\')) { $this->onlineip = $xip; } elseif($rip && strcasecmp($rip, \'unknown\')) { $this->onlineip = $rip; } elseif($srip && strcasecmp($srip, \'unknown\')) { $this->onlineip = $srip;
所以也能绕过ip限制进行爆破,还有一点就是对于验证码的重复使用。
只需要seccodehidden是请求验证码参数时候的seccodehidden ,
/uc_server/admin.php?m=seccode&seccodeauth=edb516z36gQ7e0R0YNxCOpXry3WTyJMf0qr5YKmJBpyWU0I&780815552
看到这里:
/uc_server/control/admin/user.php 第70-77行
$seccodehidden = urldecode(getgpc(\'seccodehidden\', \'P\')); $seccode = strtoupper(getgpc(\'seccode\', \'P\')); $seccodehidden = $this->authcode($seccodehidden, \'DECODE\', $authkey); require UC_ROOT.\'./lib/seccode.class.php\'; seccode::seccodeconvert($seccodehidden); if(empty($seccodehidden) || $seccodehidden != $seccode) { $errorcode = UC_LOGIN_ERROR_SECCODE; }
从post的数据中获取:$seccodehidden $seccode 两个参数,然后对$seccodehidden进行解码
$seccodehidden = $this->authcode($seccodehidden, \'DECODE\', $authkey);
解码$seccodehidden参数
require /lib/seccode.class.php ,然后调用seccodeconvert($seccodehidden)函数
function seccodeconvert(&$seccode) { $s = sprintf(\'%04s\', base_convert($seccode, 10, 20)); $seccodeunits = \'CEFHKLMNOPQRSTUVWXYZ\'; $seccode = \'\'; for($i = 0; $i < 4; $i++) { $unit = ord($s{$i}); $seccode .= ($unit >= 0x30 && $unit <= 0x39) ? $seccodeunits[$unit - 0x30] : $seccodeunits[$unit - 0x57]; } }
对$seccodehidden进行计算,算出$seccodehidden的值,与$seccode的值做对比,如果和计算出来的相同,就进行下一步。
也就是说我们只要第一次验证的时候保持$seccodehidden $seccode 两个参数不变,并且绕过ip限制,这样我们就能对后台进行爆破了。
后台对于返回的提示消息,设计的规范但是不友好。比如用户名错误,他就返回用户名不存在,这样就能爆破用户名了,当用户名正确的时候,他返回:用户名无效,或密码错误, 这样我们就能爆破密码。
如果要修复的话,ip不应该从用户可控的地方来判断,直接从REMOTE_ADDR来判断。
以上是关于Discuz!X 系列 HTTP_X_FORWARDED_FOR 绕过限制进行密码爆破的主要内容,如果未能解决你的问题,请参考以下文章