在安全界里有一句话叫:输入都是有害的。怎么理解这句话?意思是,所有的用户输入,都可能是有害的,如果不加以验证,将会造成严重的灾难性后果。所有输入都是有害的,除非获得证明。
对于开发者,必须对接受的输入进行验证;对于审计者,当然是寻找开发者的疏忽,找到无验证的输入尝试利用。
针对PHP来说,输入主要有显式输入,隐式输入,变量覆盖。
显式输入
只是从 web 上考虑的话,什么样的网站最安全?
很明显,那些只提供静态 Html 页面的网站是最安全的,因为这样的网站不与浏览者进行任何交互,就好比打劫一个密不透风的银行,很难实现,但是对于一个大的论坛或者脚本程序就不一样了。
你登陆的时候需要传递用户名和密码这些变量给服务器,甚至包括你登陆的 Ip 与浏览器等等都是程序抓取的对象,抓取一次与服务器交互的过程如发表帖子等等你就发现浏览器与服务器之间进行的数据传输,你可能看得见的包括提交的表单,地址栏参数等等,你看不见的包括 Cookie,Http 头都是提交数据也就是变量的地方。
这些地方也是服务器处理数据最原始的入口。
大多数漏洞的形成原因主要都是未对输入数据进行安全验证或对输出数据未经过安全处理。那么 php 程序是如何接受变量的呢?所有提交的变量都被 php 保存在了一些数组里,在 PHP 中可由用户输入的变量列表如下:
$_SERVER
$_GET
$_POST
$_COOKIE
$_REQUEST
$_FILES
$_ENV
为了最初的方便与灵活,在 php 的设置里有这么个选项register_globals,当这个选项为 on 的时候,上面出现的那些变量都会成为$GLOBALS 中的一员,在脚本中都不需要再取得就可以直接使用,并且以variables_order的顺序覆盖。
很多程序考虑到了 register_globals = off 的情况,于是在程序初始化的时候使用如下的代码:
@extract(daddslashes($_POST));
@extract(daddslashes($_GET));
这些代码起到了 register_globals 的作用,作用也是将 POST和 GET 的内容释放出去做为全局变量,但是危险可能更大,后面会提到。
隐式输入 上面这些是最原始的,没有经过程序转换的数据,程序很多地方用到的变量都来自这里,但也不表示其他的地方没有变量传递过来,下面有一个数据传递的模式:
用户传递的数据 —> 数据库 —> 程序代码处理 —> 程序代码
这个模式的意思是用户的输入可能先进入了数据库,然后程序从数据库再取得这个输入送入某些危险的函数执行。
一般的程序员都会有一个意识认为从数据库中取得的变量是安全的,但是事实并不如此,只要某些敏感字符最终送入到程序代码中,不管他中间停留在什么地方,都是危险的。
与存储在数据库中类似的情况是,一些程序把用户的输入放到文件中,如缓存文件,然后在必要的时候从里面取得,如果太过相信这些地方来的变量,这样还是会导致问题的。
变量覆盖
具体场景具体应用,用得好还真是一个大杀器。
常见的变量覆盖
extract()
parse_str()
$$
还有很多的时候,程序收到的变量很可能来自他不应该来的地方,譬如 Dz 的代码:
$magic_quotes_gpc=get_magic_quotes_gpc();@extract(daddslashes($_POST)); @extract(daddslashes($_GET)); if(!$magic_quotes_gpc) { $_FILES = daddslashes($_FILES); }
这样之后,你还觉得 $_FILES 是原来的 $_FILES 了么?
如果我们建立一个 _FILES 的表单或者干脆在 url 里加上 xxx.php?_FILES[]=isecer, 这样之后 $_FILES已经完全被覆盖了, 然后你代码里引用的 $_FILES 就不是原来的了。
在 Dz 以前的版本中曾经出现过这个问题。这应该属于变量覆盖的问题,把初始化的那个文件放大来看看吧:
$magic_quotes_gpc= get_magic_quotes_gpc(); @extract(daddslashes($_POST));
@extract(daddslashes($_GET));
if(!$magic_quotes_gpc) {
$_FILES = daddslashes($_FILES);
}
$charset = $dbcharset = ”;
$plugins = $hooks = array();
require_once DISCUZ_ROOT.’./config.inc.php’;
require_once DISCUZ_ROOT.’./include/db_’.$database.’.class.php’;
if($attackevasive) {
require_once DISCUZ_ROOT.’./include/security.inc.php’;
}
这样貌似是没有问题的,但是满足一定的条件的话还是可能出问题,假设register_globals=on的话,我们进入全局的变量不只是 $_GET 和 $_POST,
还包括 $_COOKIE 和 $_FILES 以及 $_SERVER 都是会在全局数组中产生变量的,通过上面的语句,我们提交一个php?_SERVER[PHP_SELF]就可以覆盖掉 _SERVER 数组。
那么整个程序中的 $_SERVER 数组都是不可以相信的了。我也见过这样写的代码:
<?php
require_once ROOT_PATH.'inc/database_config.php';
require_once ROOT_PATH.'inc/dv_spacemain.php';
if(PHP_VERSION < '4.1.0') {
$_GET = &$HTTP_GET_VARS;
$_POST= &$HTTP_POST_VARS;
$_COOKIE = &$HTTP_COOKIE_VARS;
$_SERVER = &$HTTP_SERVER_VARS;
$_ENV = &$HTTP_ENV_VARS;
$_FILES = &$HTTP_POST_FILES;
$_SESSION =& $HTTP_SESSION_VARS;
}
$magic_quotes_gpc= get_magic_quotes_gpc();
$register_globals = @ini_get('register_globals');
if(!$register_globals || !$magic_quotes_gpc) {
@extract(i_addslashes($_POST));
@extract(i_addslashes($_GET));
@extract(i_addslashes($_COOKIE));
if(!$magic_quotes_gpc) {
$_FILES = i_addslashes($_FILES);
}
}
//......
?>
同样是在系统初始化的地方,但是变量的释放是在
require_once ROOT_PATH.’inc/general_funcs.php’;
require_once ROOT_PATH.’inc/dv_spacemain.php’;
这些关键变量初始化之后,那么我们完全可以提交一个 ?$host=xxx.xxx.xxx.xxx 这样的东西覆盖掉系统自己的数据库初始化文件里的数据库地址变量,然后就可以……
还有就是变量覆盖在注入上面的应用。
覆盖掉表前缀
Select * from $pre_admin where xxx
像这种的就覆盖掉 $pre ,然后直接补全语句然后注入
有些简单的后台管理员验证也是可以利用变量覆盖bypass的!
挖掘技巧
全局搜索关键字
extract
parse_str
$$
寻找利用环境
表前缀
Session
系统配置变量$_config[]
未初始化变量带入的 sql 语句
…
Orz
PHP的漏洞基本上都与来源数据有关。程序不能很好地处理验证来源数据、就会产生可利用的BUG。
相关文章
PHP安全编码-Wooyun知识库-瞌睡龙
论PHP常见的漏洞-Wooyun知识库-′ 雨。
转载请注明:Adminxe's Blog » PHP基础代码审计 2.1 危险的来源数据