#47444 [Bgs]: Security issue: allow_url_fopen/allow_url_include are useless
| From: | the_djmaze at hotmail dot com | Date: | Thu, 19 Feb 2009 16:46:43 +0000 |
| Subject: | #47444 [Bgs]: Security issue: allow_url_fopen/allow_url_include are useless | ||
| References: | 1 | Groups: | php.bugs |
| Request: | Send a blank email to php-bugs+get-134005@lists.php.net to get a copy of this message | ||
ID: 47444
User updated by: the_djmaze at hotmail dot com
Reported By: the_djmaze at hotmail dot com
Status: Bogus
Bug Type: Streams related
Operating System: GNU/Linux
PHP Version: 5.2.9RC2
New Comment:
block drive-by abuse?
1. hosts start using allow_url_fopen=off for "security" reasons
2. people start to use above mentioned way to get around it
3. Wouldn't that make the whole option useless?
If so, you should delete this bug report or it might bring people to
bad ideas by not fixing their scripts and use the wrapper.
Previous Comments:
------------------------------------------------------------------------
[2009-02-19 00:59:09] scottmac@php.net
It disables fopen from using a url, you use fsockopen within a wrap
around class with a strema registered on http.
The allow_url_include is the same issue, these ini settings were
designed to block drive-by abuse, where a user had failed to sanitize
something correctly.
Nothing to fix here as its working as advertised.
------------------------------------------------------------------------
[2009-02-18 21:34:59] the_djmaze at hotmail dot com
i did write 10-20 lines. But all other 200 lines will follow below
then:
<?php
class moo_stream_wrapper_http
{
protected $fullurl;
protected $p_url;
protected $conn_id;
protected $flushed;
protected $mode = 4; # read only
protected $defmode;
protected $redirects = 0;
protected $binary;
protected $options;
protected $stat = array(
'dev' => 0,
'ino' => 0,
'mode' => 0,
'nlink' => 1,
'uid' => 0,
'gid' => 0,
'rdev' => -1,
'size' => 0,
'atime' => 0,
'mtime' => 0,
'ctime' => 0,
'blksize' => -1,
'blocks' => 0
);
protected function error($msg='not connected')
{
if ($this->options & STREAM_REPORT_ERRORS) {
trigger_error($msg, E_USER_WARNING); }
return false;
}
public function stream_open($path, $mode, $options, $opened_path)
{
$dbg = debug_backtrace();
switch ($dbg[1]['function'])
{
case 'include':
case 'include_once':
case 'require':
case 'require_once':
trigger_error($dbg[1]['function'].'() URL file-access is
disabled', E_USER_WARNING);
return false;
}
$this->fullurl = $path;
$this->options = $options;
$this->defmode = $mode;
$url = parse_url($path);
if (empty($url['host'])) { return $this->error('missing host
name'); }
$this->conn_id = fsockopen($url['host'], (empty($url['port']) ?
80 : intval($url['port'])), $errno, $errstr, 2);
if (!$this->conn_id) { return false; }
if (empty($url['path'])) { $url['path'] = '/'; }
$this->p_url = $url;
$this->flushed = false;
if ('r' !== $mode[0] || (strpos($mode, '+') !== false)) {
$this->mode += 2; }
$this->binary = (strpos($mode, 'b') !== false);
$c = $this->context();
if (!isset($c['method'])) {
stream_context_set_option($this->context, 'http', 'method', 'GET');
}
if (!isset($c['header'])) {
stream_context_set_option($this->context, 'http', 'header', ''); }
if (!isset($c['user_agent'])) {
stream_context_set_option($this->context, 'http', 'user_agent',
ini_get('user_agent')); }
if (!isset($c['content'])) {
stream_context_set_option($this->context, 'http', 'content', ''); }
if (!isset($c['max_redirects'])) {
stream_context_set_option($this->context, 'http', 'max_redirects', 5);
}
return true;
}
public function stream_close()
{
if ($this->conn_id)
{
fclose($this->conn_id);
$this->conn_id = null;
}
}
public function stream_read($bytes)
{
if (!$this->conn_id) { return $this->error(); }
if (!$this->flushed && !$this->stream_flush()) { return false;
}
if (feof($this->conn_id)) { return ''; }
$bytes = max(1,$bytes);
if ($this->binary) {
return fread($this->conn_id, $bytes);
} else {
return fgets($this->conn_id, $bytes);
}
}
public function stream_write($data)
{
if (!$this->conn_id) { return $this->error(); }
if (!$this->mode & 2) { return $this->error('Stream is in
read-only mode'); }
$c = $this->context();
stream_context_set_option($this->context, 'http', 'method',
(('x' === $this->defmode[0]) ? 'PUT' : 'POST'));
if (stream_context_set_option($this->context, 'http',
'content', $c['content'].$data)) { return strlen($data); }
return 0;
}
public function stream_eof()
{
if (!$this->conn_id) { return true; }
if (!$this->flushed) { return false; }
return feof($this->conn_id);
}
public function stream_seek($offset, $whence)
{
trigger_error("stream_seek($offset, $whence) not yet
supported");
return false;
}
public function stream_tell()
{
trigger_error("stream_tell() not yet supported");
return 0;
}
public function stream_flush()
{
if ($this->flushed) { return false; }
if (!$this->conn_id) { return $this->error(); }
$c = $this->context();
# send the headers
$this->flushed = true;
$RequestHeaders = array(
$c['method'].'
'.$this->p_url['path'].(empty($this->p_url['query']) ? '' :
'?'.$this->p_url['query']).' HTTP/1.1',
'HOST: '.$this->p_url['host'],
'User-Agent: '.$c['user_agent'].' StreamReader'
);
if (!empty($c['header'])) { $RequestHeaders[] = $c['header'];
}
if (!empty($c['content'])) {
#
http://utoronto.ca/webdocs/HTMLdocs/Book/Book-3ed/appb/mimetype.html
if ('PUT' === $c['method']) {
$RequestHeaders[] = 'Content-Type: '.($this->binary ?
'application/octet-stream' : 'text/plain');
} else {
$RequestHeaders[] = 'Content-Type:
application/x-www-form-urlencoded';
}
$RequestHeaders[] = 'Content-Length:
'.strlen($c['content']);
}
$RequestHeaders[] = 'Connection: close';
if (fwrite($this->conn_id, implode("\r\n",
$RequestHeaders)."\r\n\r\n") === false) { return false; }
# send the post data
if (!empty($c['content']) && fwrite($this->conn_id,
$c['content']) === false) { return false; }
# Get response headers
global $http_response_header;
$http_response_header = array(fgets($this->conn_id, 300));
# Check Status Code
w3.org/Protocols/rfc2616/rfc2616-sec10.html
$data = rtrim($http_response_header[0]);
preg_match('#.* ([0-9]+) (.*)#i', $data, $head);
# 301 Moved Permanently, 302 Found, 303 See Other, 307
Temporary Redirect
if (($head[1] >= 301 && $head[1] <= 303) || $head[1] == 307) {
$data = rtrim(fgets($this->conn_id, 300)); # read next
line
while (!empty($data)) {
if (stripos($data, 'Location: ') !== false) {
$new_location = trim(str_ireplace('Location: ', '',
$data));
break;
}
$data = rtrim(fgets($this->conn_id, 300)); # read next
line
}
trigger_error($this->fullurl.' '.$head[2].':
'.$new_location, E_USER_NOTICE);
$this->stream_close();
return ($c['max_redirects'] > $this->redirects++ &&
$this->stream_open($new_location, $this->defmode, $this->options, null)
&& $this->stream_flush());
}
# Read all headers
$data = rtrim(fgets($this->conn_id, 1024)); # read line
while (!empty($data)) {
$http_response_header[] = $data."\r\n";
if (stripos($data, 'Content-Length: ') !== false) {
$this->stat['size'] = trim(str_ireplace('Content-Length: ', '',
$data)); }
else if (stripos($data, 'Date: ') !== false) {
$this->stat['atime'] = strtotime(str_ireplace('Date: ', '',
$data)); }
else if (stripos($data, 'Last-Modified: ') !== false) {
$this->stat['mtime'] = strtotime(str_ireplace('Last-Modified: ',
'',
$data)); }
$data = rtrim(fgets($this->conn_id, 1024)); # read next
line
}
# Client/Server error
if ($head[1] >= 400) {
trigger_error($this->fullurl.' '.$head[2],
E_USER_WARNING);
return false;
}
# file modified?
if ($head[1] == 304) {
trigger_error($this->fullurl.' '.$head[2], E_USER_NOTICE);
return false;
}
return true;
}
public function stream_stat()
{
$this->stream_flush();
return $this->stat;
}
public function dir_opendir($path, $options) { return false; }
public function dir_readdir() { return ''; }
public function dir_rewinddir() { return ''; }
public function dir_closedir() { return; }
public function url_stat($path, $flags) { return array(); }
protected function context()
{
if (!$this->context) { $this->context =
stream_context_create(); }
$c = stream_context_get_options($this->context);
return (isset($c['http']) ? $c['http'] : array());
}
}
?>
------------------------------------------------------------------------
[2009-02-18 21:22:48] jani@php.net
Thank you for this bug report. To properly diagnose the problem, we
need a short but complete example script to be able to reproduce
this bug ourselves.
A proper reproducing script starts with <?php and ends with ?>,
is max. 10-20 lines long and does not require any external
resources such as databases, etc. If the script requires a
database to demonstrate the issue, please make sure it creates
all necessary tables, stored procedures etc.
Please avoid embedding huge scripts into the report.
------------------------------------------------------------------------
[2009-02-18 21:19:06] the_djmaze at hotmail dot com
Description:
------------
I already know this for years but as of now no-one reported it so i
will.
You can override the security settings of allow_url_fopen and
allow_url_include by using the following functions:
http://php.net/stream_wrapper_register
http://php.net/stream_wrapper_unregister
Due to this you can unregister the HTTP wrapper and register your own.
Then with fsockopen or cURL inside that wrapper you completely override
the security settings.
Reproduce code:
---------------
Wrapper class:
http://dragonflycms.org/cvs/html/includes/classes/http_wrapper.php?v=1.1
<?php
if (!ini_get('allow_url_fopen') && !ini_get('allow_url_include'))
{
# Force allow_url_fopen=on and allow_url_include=off
stream_wrapper_unregister('http');
require('http_wrapper.php');
stream_wrapper_register('http', 'moo_stream_wrapper_http');
}
getimagesize('http://www.php.net/images/php.gif');
?>
Expected result:
----------------
Warning: getimagesize() [function.getimagesize]: URL file-access is
disabled in the server configuration
Warning: getimagesize(http://www.php.net/images/php.gif)
[function.getimagesize]: failed to open stream: no suitable wrapper
could be found
Warning: getimagesize() [function.getimagesize]: URL file-access is
disabled in the server configuration
Warning: getimagesize(http://www.php.net/images/php.gif)
[function.getimagesize]: failed to open stream: no suitable wrapper
could be found
Actual result:
--------------
success!
------------------------------------------------------------------------
--
Edit this bug report at http://bugs.php.net/?id=47444&edit=1