蓝帽杯One_Pointer_PHP复现总结

预计阅读时间: 2 分钟

One Pointer PHP

本文是对蓝帽杯One Pointer PHP的赛后复现,题目和环境请见buu

简单分析

题目给了源码,先看一下源码。

#add_api.php
<?php
include "user.php";
if($user=unserialize($_COOKIE["data"])){
   $count[++$user->count]=1;
   if($count[]=1){
      $user->count+=1;
      setcookie("data",serialize($user));
   }else{
      eval($_GET["backdoor"]);
   }
}else{
   $user=new User;
   $user->count=1;
   setcookie("data",serialize($user));
}
?>
#user.php
<?php
class User{
    public $count;
}
?>

可以很清楚地看到有一个后门

image-20210510110958283

而且利用条件也很明显,需要$count[] = 1返回false。但是要注意,这里不是逻辑判断语句,是赋值语句。那赋值语句能返回false么?这个题目在这里暗示的这么明显,(如果出题人没有睿智操作)那肯定就是这里有Trick了,所以我们首先需要找找$count是哪里来的。

往上看,找到

image-20210510111342042

发现先从$_COOKIE里反序列化出一个$user,然后将其count属性自增后,作为$count的索引,将其赋值为1,然后发现后面又将这个$user->count存入cookie了。考虑这个过程,其实就是每点击一次首页的图片,就会把这个cookie值+1,然后将$count数组的这个位置上+1的值赋值为1(加黑,有伏笔)。

触发后门

​ 我们再来仔细看一下利用条件。在我们取数组的值的时候,一般都会指定索引的值来获取。如$count[1] = 1$count['a'] = 123等等,而在利用条件中,数组没有给索引值,那么在这种情况下,会发生什么呢?

​ 首先翻阅php文档,在[数组一节](PHP: Array 数组 - Manual)发现如下描述:

​ key 为可选项。如果未指定,PHP 将自动使用之前用过的最大 integer 键名加上 1 作为新的键名。

​ 在虚拟机上用php -a启动一个交互式php环境,并编写代码进行测试。测试内容和结果如图:

image-20210510113016683

​ 可以看到,一开始$count变量并不存在,然后我们通过$count[] = 1进行赋值之后,$count变成了一个数组,且其索引值为0的位置被赋值为1.重复上次操作,发现其索引值1的位置被赋值为1.......

​ 这种数字会自动增长的问题,从经验角度来看,如果没有上限则容易产生整数溢出的问题(很多薅羊毛类的题目);从计算机的角度来看,数组索引的值很大概率是有一个上限的(要考虑索引的整形上限问题)。于是我们依次测试16位、32位、64位的int的上限。发现在测试到64位时,即9223372036854775807时,达到索引上线。效果如图:

image-20210510121652817

​ 因此,只要我们让COOKIE中的值顶满这个上限,就可以让他返回NULL,进入eval分支;但是这里还要注意一个坑,上面我有提过,他是先自增再赋值的,所以我们还要减去一。

​ 如图,修改COOKIE后访问,成功触发后门。image-20210510122032029

绕过open_basedir && disable_functions

发现是FastCGI跑的服务。然后查看disable_functions:

image-20210510164524392

好家伙,这密密麻麻一片,反正就是把常见的能够利用getshell的函数全ban掉了。然后发现在phpinfo里面,有一个奇奇怪怪的拓展的信息

image-20210510164711912

看起来是要逆向这个拓展。在比赛过程中,我嗯是逆向逆了半天,没看懂,告辞.jpg

后来查看HA1C9ON👴的wp(蓝帽杯_one_Pointer_php – Ha1c9on),发现这道题目其实可以用纯Web的方法做出来。。还是太菜了

后面是在buu上的复现,用的是wp中提到的纯web的做法。流程如下:

​ 首先,在本地linux环境下编译一个恶意so文件

image-20210510223312596

​ 然后将其上传到服务器的/tmp目录下(记得加上目录穿越)。之后使用p神的fpm脚本的魔改版(将端口改为9001,然后将 'PHP_VALUE': 'auto_prepend_file = php://input'改成'PHP_VALUE': 'extension_dir = /tmp\nextension = bad.so')。

​ 这里要考虑到,为什么不直接用原本的脚本,之间执行system呢?注意p神博客中有一句话

这又涉及到PHP-FPM的两个环境变量,PHP_VALUEPHP_ADMIN_VALUE。这两个环境变量就是用来设置PHP配置项的,PHP_VALUE可以设置模式为PHP_INI_USERPHP_INI_ALL的选项,PHP_ADMIN_VALUE可以设置所有选项。(disable_functions除外,这个选项是PHP加载的时候就确定了,在范围内的函数直接不会被加载到PHP上下文中)

​ 因此,原来的php.ini中的disable_functions是不能只用fastcgi绕过的,要配合拓展来绕过。同时也要注意,这里拓展的内容虽然和LD_preload绕过disable_functions一样,但是两者在php层面上的原理是完全不一样的。前者是利用了Linux的环境变量来导入拓展并执行,而后者实际上是利用了php.ini中的extension_dir和extension配置项。这种方法是单纯将我们的恶意so文件作为php的一项拓展引入,在这个过程中执行了我们的命令。虽然看着很像,但并不完全一致。

​ 在拿到shell之后的内容相对平常简单,是因为php存在suid,可以直接用php -a来执行root权限的命令。suid和suid提权的内容不再赘述,可以自行上网查找相关资料。

Refrence:

P神博客 Fastcgi协议分析 && PHP-FPM未授权访问漏洞 && Exp编写 | 离别歌 (leavesongs.com)

Ha1👴博客 蓝帽杯_one_Pointer_php – Ha1c9on

还有一些其他内容参考了很多师傅们的博客,记不太清了所以不再一一列出QAQ。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注