Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CakePHP security component blackholing login (data[_Token][key] field not generated)

When I try to login, the request gets blackholed by teh Security component. How can I make it work right?

I have a simple login form

  <div class="container container-login">
    <h2><?php echo __('Login'); ?></h2>
    <div class="wrap-form-signin">
    <?php
    echo $this->Form->create('User', array('action' => 'login', 'class' => 'form-signin'));
    echo $this->Form->input('username', array('label' => '', 'placeholder' => __('Email')));
    echo $this->Form->input('password', array('label' => '', 'placeholder' => __('Password')));
    echo $this->Form->submit(__('Login'));
    echo $this->Form->end();
    ?>
    </div>
</div>

The controller action goes like this:

public function login() {
        if ($this->request->is('post')) {
            if ($this->Auth->login()) {
                return $this->redirect($this->Auth->redirectUrl());             
            } else {
                $this->Session->setFlash(__('Username or password is incorrect'), 'default', array(), 'auth');
            }
        }
}

And the Security component is included in AppController

public $components = array('Security', ... );

In the error.log I get:

2013-03-29 13:40:58 Error: [BadRequestException] The request has been black-holed
Request URL: /users/login
Stack Trace:
#0 C:\wamp\www\cdx\lib\Cake\Controller\Component\SecurityComponent.php(234): SecurityComponent->blackHole(Object(UsersController), 'auth')
#1 [internal function]: SecurityComponent->startup(Object(UsersController))
#2 C:\wamp\www\cdx\lib\Cake\Utility\ObjectCollection.php(131): call_user_func_array(Array, Array)
#3 [internal function]: ObjectCollection->trigger(Object(CakeEvent))
#4 C:\wamp\www\cdx\lib\Cake\Event\CakeEventManager.php(247): call_user_func(Array, Object(CakeEvent))
#5 C:\wamp\www\cdx\lib\Cake\Controller\Controller.php(670): CakeEventManager->dispatch(Object(CakeEvent))
#6 C:\wamp\www\cdx\lib\Cake\Routing\Dispatcher.php(183): Controller->startupProcess()
#7 C:\wamp\www\cdx\lib\Cake\Routing\Dispatcher.php(161): Dispatcher->_invoke(Object(UsersController), Object(CakeRequest), Object(CakeResponse))
#8 C:\wamp\www\cdx\app\webroot\index.php(92): Dispatcher->dispatch(Object(CakeRequest), Object(CakeResponse))
#9 {main}

How can I find what is making my request go to black hole?

When I tried to use a custom blackhole handler, the type of the error was auth. But that's all the information I can get

My version of CakePHP is 2.3.1

EDIT: The login works well without the Security component. After adding it to the AppController, the login stops working.

EDIT2: I don't have any data[_Token][key] fields in the form

EDIT3 THE SOLLUTION: Someone from my team has overriden the HTMLHelper class and it missed "hiddenblock" in the _tags array, which resulted in missing _Token fields. For details, see mine and thaJeztah's answers along with the comments bellow them

like image 362
Elwhis Avatar asked Mar 29 '13 13:03

Elwhis


2 Answers

In what order did you add your components? The security-component should be put before other components that handle form-data in their startup():

"If you are using Security component’s form protection features and other components that process form data in their startup() callbacks, be sure to place Security Component before those components in your $components array."

Security

Because the AuthComponent does handle form data inside the startup(), I think this applies, so be sure that the SecurityComponent is before the AuthComponent in your $components array;

public $components = array(
    'Security',
    'Session',
    'Auth' => array(
        // auth component settings
    )
);

Update

The 'final' answer as posted by the OP made clear that this question could not have been answered. as it turned out, somebody in the team made modifications to the HtmlHelper, causing it to not output 'hidden' blocks, and therefore not outputting the CSRF token.

In normal situations, you should never make modifications to the CakePHP Framework files themselves. CakePHP offers ways to override its functionality (including Helpers) without modifying the 'core' CakePHP files.

Why modifying CakePHP files is bad

Consider, for example, a car. What if a mechanic didn't like the design and decided to 'swap' the brake and accelerator pedals?

Of course, the car is still able to drive if you we're aware of this modification. However, without this important information, any other driver would definitely crash (and wonder what the heck just happened!?)

If the default behavior of the framework doesn't fit your needs, extend those classes. Do not modify the framework files themselves (unless there really is no other option). If modifications to the framework are absolutely nescessary, be sure to discuss this with the team and write documentation on the changes that have been made.

Keep in mind that it will no longer be possible to update the framework to a newer version, without applying the same modifications to the updated version as well. Again, if the changes have not been documented, somebody might update CakePHP and destroy your modifications.

Also, if you override or modify CakePHP, make sure that the overrides are compatible with the default behavior of CakePHP and the unit tests of CakePHP still run correctly (or create new Unit Tests for the modifications)

Using 'customised' Helpers in CakePHP

If you need to customise the CakePHP Helpers (or other components), there are various options to do so without modifying the CakePHP files;

1. Extend the Helper

class AwesomeHtmlHelper extends HtmlHelper {
    /**
     * enhanced tableHeaders method, outputs tableHeaders in a 'thead' tag
     *
     * {@inheritdoc}
     */
    public function tableHeaders(array $names, array $trOptions = null, array $thOptions = null)
    {
        $output = parent::tableHeaders($names, $trOptions, $thOptions);
        return '<thead>' . $output . '</thead>';
    }
}

Then, use your Helper the regular way:

echo $this->AwesomeHtml->tableHeaders(array('Date', 'Title', 'Active'));

2. 'Drop-in' replacement - use an alias (CakePHP > 2.3)

Since CakePHP 2.3 its possible to use an alias for a Helper. This functionality can be used in situations where (for example) two Helpers with the same name exist in your Application (e.g. Plugin.HtmlHelper).

Also, this allows you override a CakePHP Helper with your own Helper. See the documentation here: Using and Configuring Helpers

Be aware that this will override the Helper everywhere in your application!

public $helpers = array(
    'Html' => array(
        'className' => 'AwesomeHtml'
    )
);

Now, $this->Html will actually refer to the AwesomeHtmlHelper in your views:

echo $this->eHtml->tableHeaders(array('Date', 'Title', 'Active'));

Will output your 'enhanced' table headers

like image 84
thaJeztah Avatar answered Nov 05 '22 19:11

thaJeztah


EDIT: There was actually no error on the

return $this->Html->useTag('hiddenblock', $out);

line, it was rather a team member modification to the HtmlHelper I didn't know about that prevented _Token inputs to be printed in the page. The main issue remains, and it is the absence of data[_Token][key] input in the form, which I had to add


I finally found out what was the problem. There were no _Token fields because of a possible erorr in FormHelper class. I had to edit the secure method that looked like this:

public function secure($fields = array()) {
    if (!isset($this->request['_Token']) || 
            empty($this->request['_Token'])) {
        return;
    }
    $locked = array();
    $unlockedFields = $this->_unlockedFields;

    foreach ($fields as $key => $value) {
        if (!is_int($key)) {
            $locked[$key] = $value;
            unset($fields[$key]);
        }
    }

    sort($unlockedFields, SORT_STRING);
    sort($fields, SORT_STRING);
    ksort($locked, SORT_STRING);
    $fields += $locked;

    $locked = implode(array_keys($locked), '|');
    $unlocked = implode($unlockedFields, '|');
    $fields = Security::hash(serialize($fields) . $unlocked . Configure::read('Security.salt'), 'sha1');

    $out = $this->hidden('_Token.fields', array(
        'value' => urlencode($fields . ':' . $locked),
        'id' => 'TokenFields' . mt_rand()
    ));
    $out .= $this->hidden('_Token.unlocked', array(
        'value' => urlencode($unlocked),
        'id' => 'TokenUnlocked' . mt_rand()
    ));
    return $this->Html->useTag('hiddenblock', $out);
}

This method returned an empty string. So instead of returning the result of useTag I simply returned $out.

One more thing I had to do is add the data[_Token][key] field. In the end, the method looks like this:

public function secure($fields = array()) {
        if (!isset($this->request['_Token']) || 
                empty($this->request['_Token'])) {
            return;
        }
        $locked = array();
        $unlockedFields = $this->_unlockedFields;

        foreach ($fields as $key => $value) {
            if (!is_int($key)) {
                $locked[$key] = $value;
                unset($fields[$key]);
            }
        }

        sort($unlockedFields, SORT_STRING);
        sort($fields, SORT_STRING);
        ksort($locked, SORT_STRING);
        $fields += $locked;

        $locked = implode(array_keys($locked), '|');
        $unlocked = implode($unlockedFields, '|');
        $fields = Security::hash(serialize($fields) . $unlocked . Configure::read('Security.salt'), 'sha1');
        $key = $this->request['_Token']['key'];

        $out = $this->hidden('_Token.fields', array(
            'value' => urlencode($fields . ':' . $locked),
            'id' => 'TokenFields' . mt_rand()
        ));
        $out .= $this->hidden('_Token.key', array(
            'value' => $key,
            'id' => 'TokenKey' . mt_rand()
        ));
        $out .= $this->hidden('_Token.unlocked', array(
            'value' => urlencode($unlocked),
            'id' => 'TokenUnlocked' . mt_rand()
        ));
        return $out;
        //return $this->Html->useTag('hiddenblock', $out);
    }
like image 33
Elwhis Avatar answered Nov 05 '22 17:11

Elwhis