前置知识
isset — 检测变量是否已声明并且其值不为 null
foreach 语法结构提供了遍历数组的简单方式。foreach 仅能够应用于数组和对象,如果尝试应用于其他数据类型的变量,或者未初始化的变量将发出错误信息。有两种语法:
foreach (iterable_expression as $value) statement foreach (iterable_expression as $key => $value) statement
第一种格式遍历给定的 iterable_expression 迭代器。每次循环中,当前单元的值被赋给 $value。
第二种格式做同样的事,只除了当前单元的键名也会在每次循环中被赋给变量 $key。
is_string — 检测变量是否是字符串,如果 var 是 string 则返回 true,否则返回 false。
addslashes — 使用反斜线引用字符串,返回字符串,该字符串为了数据库查询语句等的需要在某些字符前加上了反斜线。这些字符是单引号(’)、双引号(”)、反斜线(\)与 NUL(null 字符)。
is_numeric() 函数用于检测变量是否为数字或数字字符串。
intval() 函数用于获取变量的整数值。intval() 函数通过使用指定的进制 base 转换(默认是十进制),返回变量 var 的 integer 数值。 intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1。成功时返回 var 的 integer 值,失败时返回 0。 空的 array 返回 0,非空的 array 返回 1。最大的值取决于操作系统。 32 位系统最大带符号的 integer 范围是 -2147483648 到 2147483647。举例,在这样的系统上, intval(‘1000000000000’) 会返回 2147483647。64 位系统上,最大带符号的 integer 值是 9223372036854775807。字符串有可能返回 0,虽然取决于字符串最左侧的字符。
strval() 函数用于获取变量的字符串值。
strrev() 函数反转字符串。
intval和is_numeric这两个函数会忽略%0C或者%2B这两个特殊符号。
漏洞测试代码
<?php$info = "";$req = [];$flag="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";ini_set("display_error", false); //为一个配置选项设置值error_reporting(0); //关闭所有PHP错误报告if(!isset($_GET['number'])){header("hint:26966dc52e85af40f59b4fe73d8c323a.txt"); //HTTP头显示hint 26966dc52e85af40f59b4fe73d8c323a.txtdie("have a fun!!"); //die — 等同于 exit()}foreach([$_GET, $_POST] as $global_var) { //foreach 语法结构提供了遍历数组的简单方式foreach($global_var as $key => $value) {$value = trim($value); //trim — 去除字符串首尾处的空白字符(或者其他字符)is_string($value) && $req[$key] = addslashes($value); // is_string — 检测变量是否是字符串,addslashes — 使用反斜线引用字符串}}function is_palindrome_number($number) {$number = strval($number); //strval — 获取变量的字符串值$i = 0;$j = strlen($number) - 1; //strlen — 获取字符串长度while($i < $j) {if($number[$i] !== $number[$j]) {return false;}$i++;$j--;}return true;}if(is_numeric($_REQUEST['number'])) //is_numeric — 检测变量是否为数字或数字字符串{$info="sorry, you cann't input a number!";}elseif($req['number']!=strval(intval($req['number']))) //intval — 获取变量的整数值{$info = "number must be equal to it's integer!! ";}else{$value1 = intval($req["number"]);$value2 = intval(strrev($req["number"]));if($value1!=$value2){$info="no, this is not a palindrome number!";}else{if(is_palindrome_number($req["number"])){$info = "nice! {$value1} is a palindrome number!";}else{$info=$flag;}}}echo $info;
漏洞复现
- 发生以下任意请求,均可绕过限制拿到flag
http://127.0.0.1/php/02%20%E7%BB%95%E8%BF%87%E8%BF%87%E6%BB%A4%E7%9A%84%E7%A9%BA%E7%99%BD%E5%AD%97%E7%AC%A6.php?number=%00%0C303或者http://127.0.0.1/php/02%20%E7%BB%95%E8%BF%87%E8%BF%87%E6%BB%A4%E7%9A%84%E7%A9%BA%E7%99%BD%E5%AD%97%E7%AC%A6.php?number=%00%2B303
漏洞分析
首先需要绕过!isset($_GET[‘number’]这个判断条件,只要get请求参数有个名字叫number就可以了(?number=)。第二个需要绕过is_numeric($_REQUEST[‘number’]这个条件,这个也简单,只要number参数非数字就行(?number=asd)。之后需要绕过第三个条件$req[‘number’]!=strval(intval($req[‘number’])),这个条件需要number参数值经过intval函数处理后还能和原来的值相等才能绕过,这里的话需要%00+数字来绕过intval函数(?number=%00213),再之后需要绕过intval($req[“number”]) != intval(strrev($req[“number”])),这个的话需要number参数值的内容反转之后还是和原值一样,那么数字对称就可以了(?number=%00212),最后需要绕过is_palindrome_number($req[“number”])这个条件。这个is_palindrome_number函数的作用是检测传入的值是否对称,这个刚好和上面的条件相冲,但我们可以再加入特殊符号%0C或者%2B绕过去,intval和is_numeric都会忽略这两个特殊符号,所以加入这两个特殊符号不会影响之前的条件判断,所以经过组合可以绕过is_palindrome_number函数最终得到flag(?number=%00%0C212)
