函数特性 | 绕过的空白字符

前言

最近在整理php弱类型,发现函数缺陷在弱类型中还是挺有意思的,遂找了is_numeric函数来试试手。挺有收货的,不断补全自己的知识盲区。

使用的空格符替换

控制码
“\0” “%00” (ASCII 0 (0x00)),空字节符。

制表符
“\t” (ASCII 9 (0x09)),水平制表符。

空白字符:
“\n” (ASCII 10 (0x0A)),换行符。
“\v” “\x0b” (ASCII 11 (0x0B)),垂直制表符。
“\f” “%0c” 换页符
“\r” “%0d”(ASCII 13 (0x0D)),回车符。

空格:
“ “ “%20” (ASCII 32 (0x20)),普通空格符。

源码查找方法

现在ext找到方法在文件中的定义名,然后在Zend文件中查找

php-5.6.30/ext/standard/string.c
$ sudo grep -rn "PHP_FUNCTION(is_numeric)"
->standard/type.c:314:PHP_FUNCTION(is_numeric)
->standard/php_type.h:35:PHP_FUNCTION(is_numeric);

php-5.6.30/Zend/zend_operators.c
$ sudo grep -rn "is_numeric_string"

Is_numeric源码分析

Is_numeric只是判别是否为数,而不是修改数。这是大前提,通过源码我们可以得知。

static inline zend_uchar is_numeric_string_ex(const char *str, int length, long *lval, double *dval, int allow_errors, int *oflow_info)
{
	const char *ptr;
	int base = 10, digits = 0, dp_or_e = 0;
	double local_dval = 0.0;
	zend_uchar type;

	if (!length) {
		return 0;
	}

	if (oflow_info != NULL) {
		*oflow_info = 0;
	}

	/* Skip any whitespace
	 * This is much faster than the isspace() function */
	while (*str == ' ' || *str == '\t' || *str == '\n' || *str == '\r' || *str == '\v' || *str == '\f') {
		str++;
		length--;
	}
	ptr = str;
	//判断正负
	if (*ptr == '-' || *ptr == '+') {
		ptr++;
	}

	if (ZEND_IS_DIGIT(*ptr)) {
		/* Handle hex numbers
		 * str is used instead of ptr to disallow signs and keep old behavior */
		//判断十六进制
		if (length > 2 && *str == '0' && (str[1] == 'x' || str[1] == 'X')) {
			base = 16;
			ptr += 2;
		}

		/* Skip any leading 0s */
		while (*ptr == '0') {
			ptr++;
		}

		/* Count the number of digits. If a decimal point/exponent is found,
		 * it's a double. Otherwise, if there's a dval or no need to check for
		 * a full match, stop when there are too many digits for a long */
		for (type = IS_LONG; !(digits >= MAX_LENGTH_OF_LONG && (dval || allow_errors == 1)); digits++, ptr++) {
check_digits:
			if (ZEND_IS_DIGIT(*ptr) || (base == 16 && ZEND_IS_XDIGIT(*ptr))) {...............

分析源代码:

Is_numerci()代码执行流程

  1. 处理空格字符串
  2. 判断传参正负
  3. 十六进制识别
  4. 识别小数和科学计数法

is_numeric while缺陷利用

利用在ctf中展示

## 空格字符处理

Is_numeric() 在执行前会跳过所有空格字符,保留原来空格字符长度增大。这样处理对数值判断是没有影响。

验证Demo:

='0' && ($_GET['num']) <= '9'){ echo "True"; } else{ echo "False"; } ?>


数值判断方法

#define ZEND_IS_DIGIT(c) ((c) >= '0' && (c) <= '9')
如果没有绕过while对字符串的处理那么函数is_numeric返回为FALSE

CTF

<?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.txt
 
   die("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 — 使用反斜线引用字符串
    } //存入数组req中value
} 
 
 
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 — 获取变量的整数值.这里要做的是使得elseif False。空白字符绕过
{
 
     $info = "number must be equal to it's integer!! "; 
 	
}
else
{
 
     $value1 = intval($req["number"]);
     $value2 = intval(strrev($req["number"]));//strrev 反转字符串  
 
     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;

分析代码

  1. 循环遍历数组foreach去除字符串首尾空格,转义字符串。
  2. 方法 is_palindrome_number()判断是否为回文数。
  3. 之后的代码才是重点通过矛盾的if/else语句得到flag

    我们的目标是得到$info=$flag最后一个else语句

    我首先想到是科学计数法(我也不知为什么),但发现不可以,看完之前对is_numeric源码分析,发现判断无关整数的类型。

题目要求回文整数

1. 传入number=191

sorry, you cann't input a number!

2. 分析is_numeric

使得while为False必须绕过’ ’,’\t’,‘\r’,’\v’,’f’

而条件$req['number']!=strval(intval($req['number']因为弱类型会忽略空白格符号自动转换为整形比较数值
通过fuzz可得,可传入参数%00191

nice! 191 is a palindrome number!
//不需要考虑$value1!=$value2影响,因为他们被intval修饰!!!

3. 但是我们需要的是$info=$flag;需要绕过is_palindrome_number($req[“number”]

根据trim源代码分析少去掉的空格符号有\n \r \t \v \0
发现少了个\f url编码为%0c

4. 传入参数?number=%00%0c191

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
成功得到flag

参考链接

Url编码规范:https://www.oschina.net/translate/what-every-web-developer-must-know-about-url-encoding
几期『三个白帽』小竞赛的writeup:https://www.leavesongs.com/PENETRATION/some-sangebaimao-ctf-writeups.html
PHP代码审计分段讲解:https://github.com/bowu678/php_bugs

总结

1.学习了怎么通过分析函数源代码了解函数缺陷
2.第一次分析条件矛盾的ctf,刚开始确实很晕但是理清楚函数之间的特异性。就能理清他为什么要设置这样的矛盾
3.学习了url编码


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!