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來判斷。

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM