On 28/02/2015 02:37, Yasuo Ohgaki wrote:
Hi Rowan,

On Fri, Feb 27, 2015 at 8:25 PM, Rowan Collins <rowan.coll...@gmail.com <mailto:rowan.coll...@gmail.com>> wrote:

    Yasuo Ohgaki wrote on 27/02/2015 03:44:

        Hi all,

        This is RFC for removing "allow_url_include" INI option. [1]

        During "Script only include" RFC[2] discussion, stream wrapper
        issue is
        raised.
        I was thinking this issue as a separate issue, but it seems
        others are not.


    I'm not convinced by the argument that because "phar://blah" looks
    like a URL, allowing it makes allow_url_include broken. Perhaps it
    would be better named allow_remote_include, but it corresponds to
    masking out your PHP_STREAM_REMOTE flag further down, which is the
    more important protection. If you want to be able to disable
    phar:// access, you could add something like
    allow_local_stream_include for that case without breaking BC.


allow_local_stream_include is one possible solution. It has the same problem with allow_url_include.

Yes, the global vs local setting issue is distinct from the question of "is phar://blah a URL?"


We need to control these flags more precisely. That's the reason why I proposed 2nd parameter for include.
If flags are globals, we cannot control them as it should.

I agree with this part. Global settings are always harder to work with, and in this case more dangerous, than something more local.


URL and URI is the same for most users, I think. (It differs, but their usage is mostly the same) Not
many users expect to work something like

include('new_wrapper://some_file_with_special_content/some_how_php_script_is_accecible_suddenly');

I'm not sure most users would look at that and think "that's a URL" or "that's a URI". They'd think "that's some funky syntax for including a file from inside an archive".


Attackers can write malicious user stream wrapper as back door. Then upload attack file and takeover server. The wrapper will be hard to distinguish if it's legitimate or not. Attack file can be any kind of format. It's impossible to detect as malicious file. URL formed script (stream wrapper) is very flexible/handy/
hard to be detected tool for attackers.

I'm not really clear how this is a "back door". At worst, it seems to be a form of obfuscation, like the classic eval(base64_decode(....)) etc, since it makes the intention of the uploaded file less obvious. But you've still got to activate the stream wrapper, upload the file, and convince the script to include it.


Remote resource is extremely danger. Local resource is very dangerous also. Current phar:// wrapper implementation spoils file extension and ".." (double dot) protection that is deployed by many PHP codes already. User wrapper seems allowed for include(), so malicious user wrapper can help attackers to
exploit PHP servers.

Relying on the fact that no uploaded file can have a name ending ".php", and that therefore any file ending ".php" is safe to include, seems like a very naive check, best remedied with user education. It's also only exploitable if they rely on the include_path setting, rather than prefixing the input with an expected directory (since '/var/www/my_app/modules/phar://foo.phar/bob.php' would not be loadable).

Similarly, the only sensible way I know to protect against ".." is to call realpath() or similar, which would simply return false for anything beginning "phar://", regardless of what path it then pointed to.


    I'm also not at all clear what you mean by "caller" and "callee"
    responsibilities; surely the difference is just between a global
    option (ini_set()) and a local one (extra argument)? And in what
    way does Option #2 require more changes than Option #1, since they
    both require the argument to be present whenever a stream wrapper
    is used?


First question is answered in previous comment.

What I don't understand is who is the "caller" and who is the "callee". All I can see in your examples is "global setting enabled at point X, and exploited at point Y"; there is no caller/callee relationship between those points.


Option #2 will be much more complex than Option #1, since it introduces types of wrapper, force users to have class method return wrapper type and pass flags around functions to handle wrapper types correctly. There may be more.

Why does the user have to pass flags around? I would have thought the vast majority of cases will be replacing "require 'phar://foo.phar/bar.php';" with either "require 'phar://foo.phar/bar.php', 'phar://'; or "require 'phar://foo.phar/bar.php', PHP_STREAM_LOCAL". The burden of upgrading is basically identical.

The only difference would seem to be the fairly rare scenario of implementing a custom stream wrapper, and then using it for script includes, and that would require only a single implementation of getType() on the stream wrapper, which doesn't seem that complex.


I used include('phar://phar_file/script.php'), but it can be include 'phar://phar_file/script.php'. I'm using () since it seemed harder to distinguish parameters and other text as we can
use () for include/require now.

No, we can't; we can use () for string expressions right now, and include/require expect a single string expression. This is an important distinction, not just a matter of terminology.

I actually encountered a bug in real code which looked something like this:

@include('/path/to/some/library.php') && call_some_library_function();

This looks like it executes include(...) in a boolean context, and calls the library function if it succeeds; but it actually evaluates '/path/to/some/library.php' in a boolean context (non-empty string => true), and always attempts to call the library function, all *before* attempting to include anything. It is equivalent to this:

@include ( '/path/to/some/library.php' && call_some_library_function() );


I have no intention to change current include/require syntax, except adding 2nd parameter.

Then you need to remove the parentheses from your examples, since they are incompatible with the current syntax, unless you only treat the parentheses specially if there is also a comma, which seems likely to cause confusion:

include ('foo') . '.php';  // existing syntax; includes 'foo.php'
include ('foo', PHP_STREAM_LOCAL) . '.php';  // new syntax; includes 'foo'?


In case you think all my examples are rather unlikely, consider this perfectly reasonable use of parentheses:

include ($debug_mode ? 'debug_' : 'production_') . $class_name . '.php';

If the parser starts treating the parentheses as part of the include syntax, this will break completely.


Regards,

--
Rowan Collins
[IMSoP]

Reply via email to