ID: 47444
Updated by: [email protected]
Reported By: the_djmaze at hotmail dot com
-Status: Feedback
+Status: Bogus
Bug Type: Streams related
Operating System: GNU/Linux
PHP Version: 5.2.9RC2
New Comment:
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.
Previous Comments:
------------------------------------------------------------------------
[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] [email protected]
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