CTF中的php弱类型

PHP弱类型在CTF中的应用

如果你经常接触到CTF比赛,那么你一定对php代码审计的题不会陌生。本篇文章将由CTF比赛引申对php弱类型进行一些总结。

基础引入

什么是php弱类型

php在变量的声明时不需要直接声明变量的类型,而变量的类型是由程序的上下文决定的。

数据转换

php中只存在,整型、浮点型、布尔、NULL型变量的转换。但是特别注意,一般在自动转换时通常是后两者转换为前两者。

布尔值参与运算时,TRUE转换为整型1,FALSE转换为整型0运算。

有NULL只参与运算时,NULL转换为整型0进行运算。

有整型与浮点型参与运算时,将整型转换为浮点型进行运算。

有字符串型与数字类型的数据进行运算时,将字符串转换为数字类型进行运算。如:
“123abc”转化为123,”123.456abc”转换为123.456,”abc”转换为整型0。

php中的运算符

首先看如下代码:

<?php
$a = 0;

var_dump( $a > 0);
var_dump( $a < true);
var_dump( $a >= 0.01);
var_dump( $a <= "0.10yuan");
var_dump( $a = 0);
var_dump( $a == 0);
var_dump( $a == "0");
var_dump( $a === "0");
var_dump( $a === 0);
var_dump( $a <> 0);
var_dump( $a != 0);
var_dump( $a != 1);
?>

输出如下:

可见当$a == "0"输出正确,而$a === "0"却是错误的。这是为什么呢?原因就在于php中“==”与“===”的区别。前者是一种模糊等于,只会比较内容的值,不会比较内容的类型,也就是后面提到了松散比较。而后者是一种严格的等于判断,会将内容与内容类型都进行判断。这是php作为弱类型语言的一种特点。

接下来将用一道CTF题来了解下实际情况中常见的php弱类型。

php源码分析

整型与字符串的纠葛

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php
show_source(__FILE__);
$a=0;
$b=0;
$c=0;
$d=0;
if (isset($_GET['x1']))
{
$x1 = $_GET['x1'];
$x1=="1"?die("ha?"):NULL;
switch ($x1)
{
case 0:
case 1:
$a=1;
break;
}
}
$x2=(array)json_decode(@$_GET['x2']);
if(is_array($x2)){
is_numeric(@$x2["x21"])?die("ha?"):NULL;
if(@$x2["x21"]){
($x2["x21"]>2017)?$b=1:NULL;
}
if(is_array(@$x2["x22"])){
if(count($x2["x22"])!==2 OR !is_array($x2["x22"][0])) die("ha?");
$p = array_search("XIPU", $x2["x22"]);
$p===false?die("ha?"):NULL;
foreach($x2["x22"] as $key=>$val){
$val==="XIPU"?die("ha?"):NULL;
}
$c=1;
}
}
$x3 = $_GET['x3'];
if ($x3 != '15562') {
if (strstr($x3, 'XIPU')) {
if (substr(md5($x3),8,16) == substr(md5('15562'),8,16)) {
$d=1;
}
}
}
if($a && $b && $c && $d){
include "flag.php";
echo $flag;
}
?>

首先通读全部代码,这里得到flag的条件是变量$a、$b、$c、$d的值都要等于1。

分步解题

  • $a = 1

要使变量等于1,要经过两层比较。首先在$x1 == 1的松散比较中要不等于”1”,这个很好过,任意不为1的值都可以。然后将NULL赋给$x1。接着进行了一次switch语句的比较。按照官方文档的说法,Switch中为确保精度,建议只使用比较整型和字符串。但由于php弱语言的缘故,所以也可以使用任意类型进行比较。那么这里我们$x1NULL值就会和整型0进行一次松散的双等比较,于是为true。最终$a = 1

  • $b = 1$c = 1

源代码程序继续往下读,首先json_decode将传入的$x2数据进行json格式编码,说明我们需要传入一个数组,用的是json格式。接着is_numeric和与整型2017的判断是php弱类型题中的典型判断。由前面提到的整型与字符串型的比较知识中我们不难构造”2018a”这样的形式来绕过这里的判断使$b = 1。接着使用array_search函数对$x2["x22"]进行匹配。但是同样的,在php函数中array_search函数进行的匹配也是通过松散的双等比较进行的,这里如果数组里有一个0值,字符串”XIPU”就会通过双等自动转换为0,从而匹配成功。综上,所以我们这里构造payload为x2={"x21":"2018a";"x22":[[1],0]}就可以成功绕过了。

  • $c = 1

这里运用的是php中0e弱类型比较,只要是0e开头的字符串在双等的松散判断下都是相等的,这里涉及md5加密,直接截取它的代码,修改下成小脚本跑一下就出来了。

全部的payload为:?x1=a1&x2={"x21":"2018a","x22":[[1],0]}&x3=XIPU18570

小结:以上的弱类型转换中是整型与字符串类型的转换。

数组与字符串的纠葛

看以下源码:

1
2
3
4
5
6
if(!strcmp($c[1],$d) && $c[1]!==$d){
echo "You in,You win";
}
else{
echo "Get out!";
}

源码中使用strcmp函数对变量$c$d进行比较,其特点是将两个变量转换为ascii码后进行前者减后者,并返回一个int类型的相减后的结果。这样我们不难发现,strcmp在比较数字类型与字符串类型时,返回的数据都是正常的,但是一旦遇到其中数组类型的比较呢?答案是NULL
所以我们这里构造数组与其比较就会构造成恒为真,从而绕过判断,进入if中。

补充:php中数组转换成其他类型的后的值:

Array转换整型int/浮点型float会返回元素个数;
转换bool返回Array中是否有元素;转换成string返回’Array’,并抛出warning。

总结

从上面这个例子可以看出php弱类型的成因在其对数据类型的处理上,体现在存在双等松散比较的条件判断与函数判断中。

其实php对字符串的自动转化相当于使用intval()函数对字符串进行强制转换,只要存在字符串与数字类型的双等松散比较中就存在这种强制转换。造成这种现象的原因就在于php内核中对弱类型的封装,其存在于zval结构中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
typedef struct _zval_struct zval;
struct _zval_struct {
/* Variable information */
zvalue_value value; /* value */
zend_uint refcount__gc;
zend_uchar type; /* active type */
zend_uchar is_ref__gc;
};
typedef union _zvalue_value {
long lval; /* long value */
double dval; /* double value */
struct {
char *val;
int len;
} str;
HashTable *ht; /* hash table value */
zend_object_value obj;
} zvalue_value;
文章目录
  1. 1. PHP弱类型在CTF中的应用
    1. 1.1. 基础引入
      1. 1.1.1. 什么是php弱类型
      2. 1.1.2. 数据转换
      3. 1.1.3. php中的运算符
    2. 1.2. php源码分析
      1. 1.2.1. 分步解题
    3. 1.3. 总结