RSS
 

Archive for November, 2010

Frequent PHP problems and solutions

30 Nov

I started off with frequent CakePHP problems last month. This month I add some common PHP problems.

Strip additional white spaces

With UTF8 the “normal” preg_replace doesn’t really work anymore. For some cases we could use the following trick:

$str = utf8_decode($str);
$str = preg_replace('/\s\s+/', ' ', $str);
$str = utf8_encode($str);
A better solution, though, is:
$str = preg_replace('/\s\s+/u', ' ', $str);
With the param “u” it’s compatible with Unicode.

Sort arrays in natural order

$array = array('1.txt', '20.txt', '2.txt', '21.txt');
natsort($array); # sorts by reference (returns boolean $success)

Condition + assignment

If you want to save the extra line for $pos = $this->calcPos()) you need to be aware that extra brackets might be needed. Wrong (if $pos is supposed to hold the position and not the result of the comparison):

if ($pos = $this->calcPos() > 0 && ...) {...}
Correct:
if (($pos = $this->calcPos()) > 0 && $pos < 100) {
    # doSomething
    $pos++;
    # doSomethingElseWithPos
}
Note the extra brackets around $pos. They are necessary – otherwise it will assign the result of the operation $this->calcPos()) > 0 (which is a boolean).

Comparison (default and strict)

Since PHP isn’t type safe you should use strict comparison (===) wherever possible. "+010" == "10.0" is TRUE (!) only with "+010" === "10.0" you will get FALSE. This might be expected in this case – usually it’s not, though. So it is not if (strpos($str, 'hello') == 0) {} but if (strpos($str, 'hello') === 0) {}, for example! What happens in the == case? It will return true in 2 cases: If “hello” is not in the string at all or if it is at the very beginning. But we would only want the second case (and therefore need ===).

A complete comparison table for PHP can be found here.

 
4 Comments

Posted in PHP

 

Preventing Brute Force on Login

11 Nov

With default cake login procedures a user could try unlimited passwords to one specific account. That means, if you write a bot that tries every possible combination (thousand times per minute!), this bot could eventually gain access to the account. To cloak it, he could use several bots on several different locations or proxies.

Some websites use the session to count the attempts and display a captcha after x failures. Or they use the IP address to block this user for a few minutes. Both is not very secure. A user could change his session and his IP frequently (depending on the skills of the attacker of course). Then he would still have unlimited trials.

More secure mechanisms

Just some ideas to discuss here – i didn’t yet test the ideas in real life scenarios.

I don’t like the captcha stuff. I would prefer the login timeout. Either way we need to make sure that everybody – no matter if human, bot or both – gets the same result.

Lets say, our “attacker” targets “benni123″ and wants access. He tries several combinations and suddenly he cannot login for a few minutes. No matter what he does. How?

My idea: use the database for it. Store the information based on the record the user wants to validate against. Every failure the database stores it with the User.user_id of “benni123″ and the time-stamp. If the amount is greater as 5 for example, the login is always aborted for this user account. The real user cannot login as well, of course. But after a few minutes everything is fine again – and he even knows somebody tried to access the account.

No bot would ever find the right key. Its just not reasonable to write a bot that only tries to access the account every few minutes. It would need hundreds of years with medium-sized passwords.

You could also display a captcha (google does it this way) after x failures. But they “could” be cracked using a specific captcha reader for your site. once this is in place, they provide not much of security anymore. Meaning: The captcha must be complicated enough for the bot but still readable for humans. reality shows that this is not easy to accomplish. Therefore I tend to use captchas only to slow too active users down (prevent spamming, message flooding, etc).

Additionally we want a php script timeout (wait x seconds to respond) after unsuccessfull attempts. This way the user doesn’t really notice but any brute force script would need remarkably longer for a chain of attempts.

In the end we could do this: Display a captcha after maybe 5 wrong trials. After another 5 correct captcha inputs but incorrect password inputs (important! we dont want this to happen if the catpcha is wrong – otherwise an attacker could easily shut down all accounts) shut the account down for a specific timeout. Of course, if the valid user tries to access the account in that time he needs to get a proper error message.

Implementation

Using the timeout method we extend the core auth component: Component AuthExt:

/**
 * override auth login
 * @override
 */
function login($data = null) {
    if (!$this->loginExt($data)) {
        # security timeout?
     sleep(1); # 1 second for right now
     return $this->_loggedIn;
    }
 
    $model = $this->getModel();
 
    // do final stuff (getting roles and additional session infos, raise login counter, ...)
 
    return $this->_loggedIn;
}
 
function loginExt($data = null) {
    $this->__setDefaults();
    $this->_loggedIn = false;
 
    if (empty($data)) {
        $data = $this->data;
    }
 
    # check against database entries if user is temporary banned
 if ($this->floodProtectionActive($data)) { # TODO: implement
     return $this->_loggedIn;
    }
 
    if ($user = $this->identify($data)) {
 
        // here we can check if other reasons prevent returning true (suspended, account not active yet, ...)
 
        return $this->_loggedIn;
    }
    # seems like we need to log this attempt as a failure
 $this->floodProtectionUpdate($data); # TODO: implement
 
    return $this->_loggedIn;
}

Security Layers

Level 1: Javascript (Timeouts, …) – LOW! – Can easily be bypassed

Level 2: Captchas, IP/Session based login monitoring, Sleep-Timeouts – MEDIUM – Can be spoofed/hacked (with some expertise anyway)

Level 3: Database based monitoring (Errors per user and login timeouts) – HIGH – Can be used against members (blocking access for valid users)

Final words

You would need a garbage collector for the database table. The last x+1 records are more than enough (except you want to debug the information or transform them into statistics).

Using only captchas: Assuming that captchas are still as secure as we would like them to be, this is the easiest implementation. After x failures, an additional captcha has to be entered. The real user would get a captcha right away, if an attacker just tried to access his account. So no delay at all. The bot, though, should fail due to the captcha in place.

Using a script timeout: As mentioned above, the script timeout (sleep command/function) is really helpful in order to slow down brute force attempts. A few seconds are more than enough. Normally 100 attempts need 5 seconds, with this timeout they will need more than 200. Assuming that the attacker doesn’t use 100 parallel connections, of course.

Using a block timeout: The real user could be identified by cookies, specific browser fingerprint etc. This in combination with the correct password in the first place could override the above protection mechanism. allowing the user to enter EVEN if the timeout is in place due to too many failures. The attacker should not be able to guess the fingerprint, though. Maybe the “browser user agent” combined with a cookie information (maybe even with the IP, although it could already have changed). Even if the attacker knew that both are used for the override, he would have too many combinations ABOVE the main problem: the correct password. He would need to know the content of the encrypted cookie as well as the exact browser version used from the real user the last time he logged on.

Combining them: This way it is most secure on application level. Even if a working captcha reader is found for your captchas, the attacker will not be able to brute force login.

One advantage of the database log tables: You can easily monitor the fails and get a pretty good picture how many “brute force attacks” have actually been commited on your website.

What do you think? And is the possible “override” maybe the key for vulnerabilities again? At least for a specific target this might be plausible.

 
3 Comments

Posted in CakePHP

 

Loading Classes on the fly

10 Nov

With the LazyLoading Model from Lorenzo (there are other sources as well) we already speed up the application by 20-40% (depending on the application size and amount of models).

Something i never really liked is that components and helpers can only be added to controllers globally in $components and $helpers. Unlike behaviors (attach/detach). What if we need some of them only in a single action? Seems to be overhead to include it in every action of the controller. I used to patch around that problem with App::import() and all kinds of init stuff in the action itself.

Now I wrote this (and suddenly my code is so much cleaner):

class CommonComponent extends Object {
 
    /**
     * for automatic startup
     * for this helper the controller has to be passed as reference
     * 2009-12-19 ms
     */
    function initialize($Controller) {
        $this->Controller = $Controller;
    }
 
    /**
     * add helper just in time (inside actions - only when needed)
     * aware of plugins
     * @param mixed $helpers (single string or multiple array)
     * 2010-10-06 ms
     */
    function loadHelper($helpers = array()) {
        $this->Controller->helpers = array_merge($this->Controller->helpers, (array)$helpers);
    }
 
    /**
     * add lib just in time (inside actions - only when needed)
     * aware of plugins and config array (if passed)
     * ONLY works if constructor consists only of one param!
     * @param mixed $libs (single string or multiple array)
     * 2010-11-10 ms
     */
    function loadLib($libs = array()) {
        foreach ((array)$libs as $lib => $config) {
            if (is_int($lib)) {
                $lib = $config;
                $config = null;
            }
 
            list($plugin, $libName) = pluginSplit($lib);
            if (isset($this->Controller->{$libName})) {
                continue;
            }
            App::import('Lib', $lib);
            $lib = new $libName($config);
            $this->Controller->{$libName} = $lib;
        }
    }
 
    /**
     * add component just in time (inside actions - only when needed)
     * aware of plugins and config array (if passed)
     * @param mixed $helpers (single string or multiple array)
     * 2010-11-08 ms
     */
    function loadComponent($components = array()) {
 
        foreach ((array)$components as $component => $config) {
            if (is_int($component)) {
                $component = $config;
                $config = null;
            }
            list($plugin, $componentName) = pluginSplit($component);
            if (isset($this->Controller->{$componentName})) {
                continue;
            }
            App::import('Component', $component);
 
            $componentFullName = $componentName.'Component';
            $component = new $componentFullName($config);
 
            if (method_exists($component, 'initialize')) {
                $component->initialize($this->Controller);
            }
            if (method_exists($component, 'startup')) {
                $component->startup($this->Controller);
            }
            $this->Controller->{$componentName} = $component;
        }
    }
 
}

Now the following is possible (inside controller actions):

$this->Common->loadComponent('RequestHandler');
$this->Common->loadComponent('Tools.Currency');
$this->Common->loadComponent(array('RequestHandler', 'Test'=>array('x'=>'y'))); # passing options

$this->Common->loadLib('Tools.RandomLib');
$this->Common->loadLib(array('Tools.RandomLib', 'MarkupLib'));
$this->Common->loadLib(array('Tools.RandomLib', 'MarkupLib'=>array('x'=>'y'))); # passing options

$this->Common->loadHelper('Text');
$this->Common->loadHelper(array('Text', 'Tools.Datetime'));
etc.

Maybe without “Common->” or any other helping component some time in the future :)

I hope the enhancement ticket will soon be patched into the core. Feel free to vote it up: cakephp.lighthouseapp.com/projects/42648-cakephp/tickets/1277

 
4 Comments

Posted in CakePHP