• 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来判断。

      

  • 相关阅读:
    软工作业06
    软工作业05
    软工作业00
    软工作业04
    软工作业03
    软工作业02
    我的随笔
    2020软件工程个人作业06——软件工程实践总结作业
    2020软件工程作业05
    软件工程作业00——问题清单
  • 原文地址:https://www.cnblogs.com/yangxiaodi/p/6917614.html
Copyright © 2020-2023  润新知