views:

115

answers:

3

I'm writing a class that allows you to bridge HTTP requests with class instances using JSON for data, without any implementation in the class you're bridging to. Basically this is how it works:

// This is just an ordinary class.
$service = new WeatherService();

$jhi = new JsonHttpInterface($service);
$jhi->exec();

The JsonHttpInterface class will check the PATH_INFO of the request and call that method, applying any query string parameters as arguments.

http://example.com/the_above.php/getWeather?state="CA" would translate to
$service->getWeather("CA") (assuming that the name of the first argument is $state).

This is how the method is found and called:

$method = new ReflectionMethod(get_class($this->instance), $action);
/*
... code that matches query string values to arguments of above method...
*/
$response = $method->invokeArgs($this->instance, $args);

Now what I'm wondering is: what are the vulnerabilities of such a system. I've been pretty lenient on error checking, relying on PHP to throw errors when attempting to call non-existent or private/protected methods.

  • Is it possible to cheat the system?
  • Is it possible to pass in an invalid method name that does something other than throw an error?
  • Is it possible to refer to a method in a base class, or any other class?

The full source of JsonHttpInterface is available here: http://blixt.org/js/two-cents.php

A: 

Anthing that allows user-entered PHP code is likely to be open to some exploitation, so it's certainly high risk. If you do keep going with it then one check I would consider including is making any classes that you want accessed in this way implement a particular interface and check for the interface before executing. That would prevent any arbitrary class from being executed in that way and just restrict it to those that you've specifically allowed.

A better solution might be to to auto-generate static files with all the calls in that you need, that way you've explictly allowed particular actions rather than trying to second-guess where all the holes might be.

Karl B
The purpose of this class is to not require any work done in the class being called. It's up to the person making the class "public" to be aware of the fact that all its public members will be accessible by anyone who can perform HTTP calls to the PHP script that calls the `exec()` method. What I'm worried about is holes in the `ReflectionMethod` class.
Blixt
This is a bad design decision then, that's like saying "Hey, I reinvented the wheel, without rubber, or testing it for safety, or anything else, but it's a wheel, so use it." If you can do it, and should do it, then don't be negligent, and do it the right way. My mama always said, if something is worth doing, it's worth doing right.
The idea for this is to be a shell on top of classes designed as services. These classes should not be required to be designed for this simple class (by inheriting from an interface that comes with this class, since PHP has no standard model for service classes). That's why I don't want to affect the classes used as services at all. It's basically a choice for lightweight, stand-alone code, rather than risking spaghetti code.
Blixt
A: 

First of all,

You need to create a white_list.

You get the defined functions of the object, and you check to see if the function name sent to you is in the array, if it is, good, else, forget about it.

You can either do an automatic whitelist, or a manual one that is configured, your choice.

You need to create a munging/washing function for all passed values.

In the example of a state, state can be CA, or California, so essentially, it must preg_match('/\w+/') so you can do some easy checking to verify data.

If you wanted to get fancy, you could allow people to create a method called allowedValues which is a hash map of functionName => /Regex/, and use that as a valid data lookup table, if the function isn't defined in the class, you use a generic one that only allows string/numbers and perhops only of a certain length.

    class MyClass{

    public function allowedArguments() {
        //This acts as a white list, and a cleaner/restricter
        return array(
            'myfunc' => array(
                'pattern' => '/\w+/'
                'length' => 255
            )
        );
    }

    public function myFunc($arg) {
        ... do something
    }
}
The above is kind of SOP for RPC, check out AMFPHP for some great ideas on how to implement RPC type stuff, even if it's REST, you are still basically implementing Remote Procedure Calls.
+1  A: 

You can achieve the same without using the ReflectionXYZ classes

call_user_func( array($this->instance, $action) , $args);

And both are save in the same way, as long as you control what $this->instance is.
$action is a string that is used to search an entry in the object's/class' method hashtable and there's no magic symbol that can escape the object context (switching to another object). And there's no parsing involved like e.g. in sql and sql injections.
Both ReflectionMethod and call_user_func_array() honour the protection level of methods. e.g.

class Foo {
  public function publicfn() {
    echo 'abc';
  }

  protected function protectedfn() {
    echo 'xyz';
  }
}

$obj = new Foo;
call_user_func_array(array($obj, 'publicfn'), array());
call_user_func_array(array($obj, 'protectedfn'), array());
$ro = new ReflectionMethod($obj, 'protectedfn');
$ro->invokeArgs($obj, array());

prints

abc
Warning: call_user_func_array() expects parameter 1 to be a valid callback, cannot access protected method Foo::protectedfn() in blabla on line 14

Fatal error: Uncaught exception 'ReflectionException' with message 'Trying to invoke protected method Foo::protectedfn() from scope ReflectionMethod' in blabla:16
Stack trace:
#0 blabla(16): ReflectionMethod->invokeArgs(Object(Foo), Array)
#1 {main}
  thrown in blabla on line 16

You might want to look up if this was always the case. There's e.g. an entry - MFH Fixed bug #37816 (ReflectionProperty does not throw exception when accessing protected attribute). At least it's true for php 5.3 branch.
You can always access public methods of any base class of $this->instance.
You can access protected methods from within the class context, i.e. if $this and $this->instance are of the same/a derived type protected methods of $this->instance are accessible. e.g.

class Foo {
  protected $instance;
  public function __construct(Foo $instance=null) {
    $this->instance = $instance;
  }
  public function publicfn() {
    if ( !is_null($this->instance)) {
      call_user_func_array( array($this->instance, 'protectedfn'), array());
    }
  }

  protected function protectedfn() {
    echo 'Foo::protectedfn() invoked';
  }
}

class Bar extends Foo {
  protected function protectedfn() {
    echo 'Bar::protectedfn() invoked';
  }
}

$foo = new Foo(new Bar);
$foo->publicfn();

prints Bar::protectedfn() invoked. But that shouldn't be too hard to avoid.

VolkerK
Thanks for the elaborate answer! I think I'll go with `call_user_func_array` instead. =)
Blixt
I guess if you don't use a whitelist, the fascade pattern or something like that to access the object's methods you can call it a POPO (plain old php object) following POJOs, POCOs .... ( http://en.wikipedia.org/wiki/Plain_Old_Java_Object ), which is quite funny for a german developer like me ... somewhat adolescent but yet funny. Popo -> botty.
VolkerK