看文章的时候看到变量覆盖,就去学习了一波。

全局变量覆盖

http://php.net/manual/zh/security.globals.php

php超级全局变量

1
2
3
4
5
6
7
8
9
$GLOBALS, 所有全局变量数组
$_SERVER, 服务器环境变量数组
$_GET,通过GET方法传递给脚本的变量数组
$_POST, 通过POST方法传递给脚本的变量数组
$_COOKIE,cookie变量数组
$_REQUEST,所有用户输入的变量数组,包括$_GET, $_POST和$_COOKIE所包含的输入内容
$_FILES,与文件上传相关得变量数组
$_ENV,环境变量数组
$_SESSION,会话变量数组

当register_global=ON时,变量来源可能是各个不同的地方,比如页面的表单,Cookie等 提交请求URL:http://www.a.com/test.php?auth=1,变量$auth将自动得到赋值

1
2
3
4
5
6
7
<?php
echo "Register_globals: ".(int)ini_get("register_globals")."<br/>";

if ($auth){
   echo "private!";
}
?>

防范办法

register_globals=off 变量初始化

extract()变量覆盖

extract

int extract ( array &$array [, int $flags = EXTR_OVERWRITE [, string $prefix = NULL ]] ) 本函数用来将变量从数组中导入到当前的符号表中

1
2
3
4
5
<?php
$auth=0;
extrace($_GET);
echo $auth;
?>

http://www.a.com/test.php?auth=1 就会注册一个$auth,其值为1,因为默认为EXTR_OVERWRITE,所以就算初始化了也能覆盖

防范方法

在调用extrace时使用EXTR_SKIP,确保已有变量不会被覆盖

遍历初始化变量

1
2
3
4
5
6
7
<?php
$chs = '';
foreach($_GET as $key => $value){
        $$key = $value;
    }
echo $chs;
?>

首先$_GET是一个数组

1
Array ( [chs] => 1 )

然后$key=chs那么$$key=$chs,所以可覆盖$chs的值

防范方法

少写$$k这样的代码(误

parse_str()变量覆盖

parse_str

如果 encoded_string 是 URL 传递入的查询字符串(query string),则将它解析为变量并设置到当前作用域(如果提供了 result 则会设置到该数组里 )

1
2
3
4
<?php
$var='0';
parse_str($_SERVER['QUERY_STRING']);
echo $var;

test.php?var=1输出就会变成1

单独输出$_SERVER['QUERY_STRING']var=1 所以$var=0就被覆盖了

与parse_str()类似的函数还有mb_parse_str()

防范方法

不会Orz…

import_request_variavles变量覆盖

bool import_request_variables ( string $types [, string $prefix ] )

将 GET/POST/Cookie 变量导入到全局作用域中。如果你禁止了 register_globals,但又想用到一些全局变量,那么此函数就很有用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?php
$auth = '0';
import_request_variables('G');

if($auth == 1){
  echo "private!";
}else{
  echo "public!";
}
?>

这里设置的是G,所以GET请求中的变量将会导入到全局中,所以http://www.a.com/test1.php?auth=1,将会导致$auth被覆盖

实例

Stcms的一个sql注射

1
2
3
#u.php
case 'list':
	$totalNum = $mysql->numTable("member", $where);

$where变量没有初始化,所以可以用sql语句来覆盖掉$where变量 u.php?action=list&where={sql}

joomla变量覆盖导致之策提权

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$system = $params->get('new_usertype', 2); //初始用户权限为2
$this->data->groups[] = $system;
... ...
public function register($temp)
{
$config = JFactory::getConfig();
$db= $this->getDbo();
$params = JComponentHelper::getParams('com_users');
// Initialise the table with JUser.
$user = new JUser;
$data = (array)$this->getData(); //这里注意,在遍历用户注册信息前加入了点数据
foreach ($temp as $k => $v) { // 遍历出注册信息
$data[$k] = $v;
}

这里就涉及到了遍历变量覆盖,提交jfrom[groups][]=7$this->data->groups[]就会被改为7,成功注册了个管理员账号 然后我根据自己的理解写了个覆盖原理代码

1
2
3
4
5
6
7
8
<?php
$d = array('groups'=>array(0=>1));
var_dump($d);
$data=$_GET['jfrom'];
foreach ($data as $key=>$value){
    $d[$key] = $value;
}
var_dump($d);

结果为

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
/var/www/html/test.php:3:
array (size=1)
  'groups' =>
    array (size=1)
      0 => int 1

/var/www/html/test.php:9:
array (size=1)
  'groups' =>
    array (size=1)
      0 => string '7' (length=1)

虽然是string类型,感觉php弱类型会把它改为int吧(误

DEDECMS全局变量注册漏洞

部分代码为

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
foreach($_REQUEST as $_k=>$_v)
{
	if( strlen($_k)>0 && eregi('^(cfg_|GLOBALS)',$_k) )
	{
		exit('Request var not allow!');
	}
}
foreach(Array('_GET','_POST','_COOKIE') as $_request)
{
	foreach($$_request as $_k => $_v) ${$_k} = _RunMagicQuotes($_v);
}

经过上面内容的学习,看到foreach就懂了。 正常流程 1. $_REQUEST接受正常请求,将数组名与值分离 2. 判断数组名是否为空,以及数组名是否有黑名单中的字符,若两者都成立,退出并显示警告 3. 上述过程全都没毛病,将各种请求遍历注册为变量 4. 遍历,将请求中的数组名注册为变量并赋值

如果我们的请求为/test.php?_POST[GLOBALS] [cfg_dbhost]=MYSQL外链IP&_POST[GLOBALS] [cfg_dbuser]=MYSQL的账号&_POST[GLOBALS][cfg_dbpwd]=MYSQL的密码& _POST[GLOBALS] [cfg_dbname]=自己的dedecms的数据库

首先第2步所判断的数组名就为_POST,绕过了黑名单判断,并且注册了$_POST 然后第4步时注册了$GLOBALS,并将赋值成数组 然后就更改数据库为我们的数据库

EOF

参考资料: http://www.cnseay.com/1839/ http://blog.csdn.net/hitwangpeng/article/details/45972099