前置知识

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这两个特殊符号。

漏洞测试代码

  1. <?php
  2. $info = "";
  3. $req = [];
  4. $flag="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
  5. ini_set("display_error", false); //为一个配置选项设置值
  6. error_reporting(0); //关闭所有PHP错误报告
  7. if(!isset($_GET['number'])){
  8. header("hint:26966dc52e85af40f59b4fe73d8c323a.txt"); //HTTP头显示hint 26966dc52e85af40f59b4fe73d8c323a.txt
  9. die("have a fun!!"); //die — 等同于 exit()
  10. }
  11. foreach([$_GET, $_POST] as $global_var) { //foreach 语法结构提供了遍历数组的简单方式
  12. foreach($global_var as $key => $value) {
  13. $value = trim($value); //trim — 去除字符串首尾处的空白字符(或者其他字符)
  14. is_string($value) && $req[$key] = addslashes($value); // is_string — 检测变量是否是字符串,addslashes — 使用反斜线引用字符串
  15. }
  16. }
  17. function is_palindrome_number($number) {
  18. $number = strval($number); //strval — 获取变量的字符串值
  19. $i = 0;
  20. $j = strlen($number) - 1; //strlen — 获取字符串长度
  21. while($i < $j) {
  22. if($number[$i] !== $number[$j]) {
  23. return false;
  24. }
  25. $i++;
  26. $j--;
  27. }
  28. return true;
  29. }
  30. if(is_numeric($_REQUEST['number'])) //is_numeric — 检测变量是否为数字或数字字符串
  31. {
  32. $info="sorry, you cann't input a number!";
  33. }
  34. elseif($req['number']!=strval(intval($req['number']))) //intval — 获取变量的整数值
  35. {
  36. $info = "number must be equal to it's integer!! ";
  37. }
  38. else
  39. {
  40. $value1 = intval($req["number"]);
  41. $value2 = intval(strrev($req["number"]));
  42. if($value1!=$value2){
  43. $info="no, this is not a palindrome number!";
  44. }
  45. else
  46. {
  47. if(is_palindrome_number($req["number"])){
  48. $info = "nice! {$value1} is a palindrome number!";
  49. }
  50. else
  51. {
  52. $info=$flag;
  53. }
  54. }
  55. }
  56. echo $info;

漏洞复现

  1. 发生以下任意请求,均可绕过限制拿到flag
    1. 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
    2. 或者
    3. 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
    2022-03-24_231420.png

    漏洞分析

    首先需要绕过!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)