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] [email protected]

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] [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

Reply via email to