CWebServiceAction problem when Basic Authentication used

I’m using:

Yii 1.1.3

Apache 2.2

Ubuntu Server (2.6.31 kernel).

PHP 5.2.10

Steps to reproduce this bug:

  1. Create a new Yii webapp using ./yii/framework/yiic webapp .

  2. Implement a simple SOAP method using CWebServiceAction.

  3. Test the web service method to ensure it works as expected.

  4. Enable Basic Authentication from the root directory of the project.

  5. Test the web service method again.

The web service I have written has the following method:




  5 class WriteController extends CController

  6 {

  7   public function actions()

  8   {

  9     return array(

 10       'methods' => array(

 11         'class' => 'CWebServiceAction',

 12         'classMap' => array(

 13           'TestComplexClass',

 14         )

 15       ),

 16     );

 17   }

 18

 19

 20   /**

 21    * method: test

 22    * I include a test(int) method in all my web services for testing purposes.

 25    *

 26    * @param int num The number that is to be multipled by two.

 27    * @return int The supplied num param multiplied by two.

 28    * @soap

 29    */

 30   public function test($num)

 31   {

 32     return $num * 2;

 33   }



The client (test.php) contains the following:




  1 <?

  2   $client = new SoapClient("http://api.myhomeserver.com/soap/write/methods",

  3     array('login' => 'someusername', 'password' => 'somepassword'));

  4

  5   $retval = $client->test(123);

  6   echo "Return value: $retval";

  7 ?>



Up to this point everything works as expected. I then enable Basic Authentication by using .htpasswd to create .passwd and adding the following top four lines to my .htaccess:




  1 AuthType Basic

  2 AuthName "Testing Basic Auth with Yii"

  3 AuthUserFile /var/vhosts/api.myhomeserver.com/www/soap/.passwd

  4 Require valid-user

  5

  6 #Options +FollowSymLinks

  7 IndexIgnore */*

  8 RewriteEngine on

  9

 10 # if a directory or a file exists, use it directly

 11 RewriteCond %{REQUEST_FILENAME} !-f

 12 RewriteCond %{REQUEST_FILENAME} !-d

 13

 14 # otherwise forward it to index.php

 15 RewriteRule . index.php



The error returned is:




Fatal error: Uncaught SoapFault exception: [HTTP] Error Fetching http headers in /var/vhosts/api.myhomeserver.com/www/soap/test.php:5

Stack trace:

#0 [internal function]: SoapClient->__doRequest('<?xml version="...', 'http://api.myho...', 'urn:WriteContro...', 1, 0)

#1 [internal function]: SoapClient->__call('test', Array)

#2 /var/vhosts/api.myhomeserver.com/www/soap/test.php(5): SoapClient->test(123)

#3 {main}

  thrown in /var/vhosts/api.myhomeserver.com/www/soap/test.php on line 5



My Apache log file contains the following:




10.20.1.15 - someusername [09/Jul/2010:20:10:10 +0100] "GET /soap/write/methods HTTP/1.0" 200 3690 "-" "-"

10.20.1.15 - - [09/Jul/2010:20:10:10 +0100] "GET /soap/write/methods HTTP/1.0" 401 685 "-" "-"

10.20.1.15 - someusername [09/Jul/2010:20:10:10 +0100] "POST /soap/write/methods?ws=1 HTTP/1.1" 500 4181 "-" "PHP-SOAP/5.2.10-2ubuntu6.4"

10.20.1.15 - someusername [09/Jul/2010:20:10:10 +0100] "GET /soap/test.php HTTP/1.1" 200 538 "-" "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6"



and my Apache error log contains:




[Fri Jul 09 20:13:17 2010] [notice] child pid 5827 exit signal Segmentation fault (11)

[Fri Jul 09 20:13:22 2010] [error] [client 10.20.1.15] ALERT - canary mismatch on erealloc() - heap overflow detected (attacker '10.20.1.15', file '/var/vhosts/api.myhomeserver.com/www/soap/yii/framework/web/services/CWebService.php', line 155)



A "heap overflow" on line 155 of CWebService.php?

I would be very interested to know if there’s anyone else out there who has implemented a web service using Yii and successfully uses Basic Authentication.

Any help on this would be highly appreciated.

Googling for this error, some say, this is caused by suhosin. That’s a PHP protection system, that tries to detect hack attempts and immediately stops your PHP script, if it does. Search for Ubuntu + suhosin, maybe you can disable it somehow and try again.

Hi Mike. Thanks for your reply. I read a little about Suhosin, it appears it tries to detect and block “bad things” in PHP (such as the eval() function for example). From what I have read, it seems that Suhosin is now built in to PHP so the only way to remove it is to rebuild PHP from source after deleting the Suhosin patch directory. This isn’t exactly the path I’d like to go down, especially since there’s no guarantees that disabling Suhosin will fix this problem (does anyone else who is reading this use Yii to create web services protected with Basic Auth?).

Also, I find it difficult to see why my example web service works without Basic Auth enabled if Suhosin is the problem - wouldn’t it also prevent the script from running?

Well, the error message is created by Suhosin. So it’s at least involved somehow.


alert - canary mismatch on erealloc() - heap overflow detected (attacker '10.20.1.15', file '/var/vhosts/api.myhomeserver.com/www/soap/yii/framework/web/services/CWebService.php', line 155)

And it also makes sense, that your auth-free example works, since Suhosin tries to protect you from security related attacks. Authentication is surely in this realm. Also check this similar problem:

http://pecl.php.net/bugs/bug.php?id=16631&edit=1

Maybe you’ve found a bug in the SoapServer implementation. I think it’s valid to open a ticket over at http://bugs.php.net/. But you should try to reproduce it without Yii first (create a SoapServer in a simple php script).

Thank you very much for your help on this. I did as you suggested and created the same simple web service using $server = new SoapServer(…) instead of Yii. The same results: when Basic Auth is used, the service fails. I think you’re right - it seems to be a problem with PHP’s SoapServer(), although I wouldn’t rule out the possibility of the problem being a mis-configuration of my server (I have numerous virtual hosts, self generated SSL certificates, DynDNS etc).

I found a rudimentary work-around: generate the WSDL with Yii, create the actual web service with PHP’s native SOAP support, place the generated WSDL in a directory that does not use Basic Authentication:




<?

 

  class TestSoap

  {

    function test($a) 

    { 

      return $a * 3;

    }

  }


  //

  // methods.wsdl is the WSDL file generated by Yii. It is placed into a directory

  // that does not use Basic Authentication.

  //

  $server = new SoapServer("https://api.myhomeserver.com/public/methods.wsdl");

  $server->setClass("TestSoap");

  $server->handle(); 


?>



While this still exposes the WSDL to the public, it still requires a username and password to actually call the web service methods.

I have verified this.

When the SoapClient issues a HTTP 1.1 GET request for the WSDL there is an Authorisation segment included.

Same thing when the SoapClient issues a HTTP 1.1 POST request to invoke the web service method.

But when the SoapServer (seemingly) issues a HTTP 1.0 GET request for the WSDL (as per parameter i to SoapServer->_construct), no Authorisation segment is included.

A 401 Authorization Required error is returned to the SoapServer, which in turn reports a 500 error to the SoapClient. (In my case I most often can repeat this a couple of times - reported as "DTD are not supported by SOAP" - before the attack detection kicks in.)

/Tommy

Inspired from a comment over at php.net I tried to include credentials in the url.




  $client = new SoapClient('http://user:password@mysite/subdir/soap/testWS', array(

    'login'=>'user',

    'password'=>'password',

  ));



It doesn’t seem to work with the standard framework classes.

If I change line 155 in CWebService.php it does work




$server=new SoapServer('http://user:password@mysite/subdir/soap/testWS',$this->getOptions());



(Directory "subdir" requires Basic Authentication)

Seems like there’s a possible Yii solution.

/Tommy

A possible fix:

Thank you Tri, Mike and Tommy for promptly looking at this issue and identifying the problem. I read through the (very well documented) code for class CWebServiceAction and paid particular attention to the comments for public member variable $serviceOptions. After doing so I (rightly or wrongly) implemented the following fix:

In class CWebService I added the following public member variables:




  /**

   * @var string Basic Authentication username.

   */  

  public $login = "";

  /**

   * @var string Basic Authentication password.

   */

  public $password = "";

  /**

   * @var string Default is http, can set to anything else if necessary.

   */

  public $protocol = "";



By doing so, it provides a way to include Basic Authentication credentials from within the CController descendant. I also added $protocol because I found that CWebService always uses http even if the web service exists in a directory with the Apache SSLRequireSSL directive included.

I commented out line 155 in CWebService.php:




// $server=new SoapServer($this->wsdlUrl,$this->getOptions());



and in its place added the the following (with my very limited PHP experience) to construct the WSDL URL:




list($protocol, $uri) = split("\://", $this->wsdlUrl, 2);

$login = $this->login;

$password = $this->password;

$basicAuthCredentials = ($login && $password) ? "$login:$password@" : "";

$protocol = (!$this->protocol) ? $protocol : $this->protocol;

$server = new SoapServer("$protocol://$basicAuthCredentials$uri", $this->getOptions());



The Basic Auth username and password, along with the protocol, can then be set from within the CController descendant that implements the web service by setting the appropriate $serviceOptions array key/value pairs:




public function actions()

{

  return array(

    'methods' => array(

      'class' => 'CWebServiceAction',

      'classMap' => array(

        'CMyComplexType',

      ),

      'serviceOptions' => array(

        'login' => 'TheBasicAuthUsername',

        'password' => 'TheBasicAuthPassword',

        'protocol' => 'https',

      ),

    ),

  );

}



I have performed some quick tests on the above, and everything seems to work (I added SSLRequireSSL to my .htaccess file and then commented out ‘protocol’ => ‘https’, I removed both the ‘login’ and ‘password’ serviceOptions, and I changed the Basic Auth username and password to something invalid - all tests returned expected results). I don’t know if using $serviceOptions in the way I have will result in side-effects somewhere else as I’m still a “noob” with the Yii framework (and PHP for that matter).

The proper way to do this is to inherit and extend the classes involved (no user changes to the framework itself). I just tried an extended WebServiceAction class (obviously a partial solution due to my limited web development experience). It will take effect if you specify the new class in the controllers actions() method. Your solution should also need a WebService class with a customized run() method and your WebServiceAction class needs to instantiate WebService instead of CWebService (The classes will be autoloaded if you save them in the protected/components directory.)




<?php

class WebServiceAction extends CWebServiceAction

{

  protected function createWebService($provider,$wsdlUrl,$serviceUrl)

  {

    if (isset($_SERVER['AUTH_TYPE']) && $_SERVER['AUTH_TYPE']==='Basic')

    {

      $arr = explode('//', $wsdlUrl);

      if (count($arr) > 1)

        $wsdlUrl = $arr[0] . '//' . $_SERVER['PHP_AUTH_USER'] . ':' . $_SERVER['PHP_AUTH_PW'] . '@' . $arr[1];

    } 

    return new CWebService($provider,$wsdlUrl,$serviceUrl);

  }

}



/Tommy

Update: I have revisited this bug as I have recently upgraded to Yii 1.1.4 and have had to port my projects over. Upon closer inspection of CWebService.php and CWebServiceAction.php, I found a much easier workaround that doesn’t involve hacking the framework. Simply provide the WSDL URL string for the public $wsdlUrl property in the Controller action method, and include the basic authentication username and password:




/**

 * Publish web services so that they are available to clients.

 */

public function actions()

{

  return array(

    'quote' => array(

      'class' => 'CWebServiceAction',

      'wsdlUrl' => 'https://SomeUserName:SomePassword@api.somedomain.com/114/test1/index.php?r=Test/testWsdl',

    ),

  );

}



It would be really nice if a permanent patch/fix was applied to the framework as many people rely on a combination of HTTPS and Basic Auth for numerous web service projects.

Great solution! Solved my basic authentication problem too.

Here is my solution, it uses the IWebServiceProvider included in Yii:




class WebServiceController extends CController implements IWebServiceProvider

{


    /**

     * This method is required by IWebServiceProvider.

     * It checks user credentials before making changes to data.

     * @param CWebService $service

     * @throws CException

     * @return boolean whether the remote method should be executed.

     */

    public function beforeWebMethod($service)

    {

        header('WWW-Authenticate: Basic realm="Authentication needed"');

        if (!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW']))

            throw new CException('No user credentials provided.');




        if ($_SERVER['PHP_AUTH_USER'] == 'user' && $_SERVER['PHP_AUTH_PW'] == 'pass')

            return true;

        else

            throw new CException('Invalid user credentials.');

    }


    /**

     * This method is required by IWebServiceProvider.

     * @param CWebService the currently requested Web service.

     */

    public function afterWebMethod($service)

    {

    }


}



[color="#006400"]/* moved from Bug Discussions */[/color]

Very old thread, but still. In case someone reads it. Please be aware that including authentication info in the URL is not valid for http(s) URLs as defined by the RFC. Other URLs like ftp allow it, but http does not. There is a lot forgiving software out there, so it will probably work for you, even if it does not comply to the RFC. If this is good enough for you, okay. But be aware that it might cause trouble with some software that is less forgiving. I had to learn it the hard way… <_<

iana.org - URI schemes

RFC 2616 - Definition of the http URL