Bypassing the MODX Manager Login II

Second in a series of articles about bypassing the Manager Login process


In the previous article, we saw how to bypass the MODX Manager login if the users are already in the MODX database. In this article, we'll look at the somewhat ugly solution for when the users are not in the database.


The Problem

As we saw in the previous article, before MODX fires the authentication event, it attempts to get the user object from the database and performs several tests to make sure the user is allowed to log in. If the user is not there, the login process is aborted and the authentication even never fires.


Solutions

One solution is to hack the login code to bypass the tests. Not only is this strongly not recommended, it's quite tricky, since you not only have to bypass the tests, you have to create a fake user for MODX to log in.

Luckily, before MODX does anything, it fires the OnBeforeManagerLogin event (or the OnBeforeWebLogin event if you're in the front end). In that event, we have the user's credentials and can create the user so the later tests will pass. There are some built-in security issues with this technique, so be careful how you implement it and be sure to read all of the following guidelines.

In this article, we'll create a separate plugin attached to the OnBeforeManagerLogin event. In the following article, we'll see how to do it with a single plugin, which if more efficient.


The Plugin Code


Important: It's critical that you *not* create the user using both the username and password submitted in the Login form. If you do that, the user will be created automatically with those credentials and every new user who attempts to be logged in will be created in the database and granted permanent access to the Manager.

To prevent this, we'll create a user with the correct username, but the wrong password. We'll make the password random and long to help prevent brute force attacks on the site.


Return Value

The event we used in the previous article OnManagerAuthentication sets the output to true if the user should be allowed to log in. The OnBeforeManagerLogin event works the other way around. It's output is interpreted as $preventLogin. That means we have to set the output to false. Otherwise the login process will be aborted and user won't be logged in.

The following code, in a plugin attached to the OnBeforeManagerLogin System Event will create a new user with a fake password. We'll use the variables

<?php

/* Return if the user is already there ($username is already set in this event) */

$existingUser = $modx->getObject('modUser', array ('username' => $username));
if ($existingUser) {
   $modx->event->_output = false;
   return;
}

/* Generate a random, 50 character password */
$password = "";
for($i=0;$i<50;$i++) {
    $password .= chr( (mt_rand(1, 36) <= 26) ? mt_rand(97, 122) : mt_rand(48, 57 ));
}

/* Create the new user */

$usr = $modx->newObject('modUser');
$usr->fromArray($fields);
if (! $usr->save()) {
    $modx->log(modX::LOG_LEVEL_ERROR, '[OnBeforeManagerLogin] Could not save user');
    $modx->event->_output = true;
    return;
}

$modx->event->_output = false;
return;

We did not send a value for the active field (though we could have). It defaults to false and we could have set the user to active by sending a value of '1', but it's usually better to do that in the other plugin after the user has been authenticated. That way, the users who are not valid users will remain inactive. We'll see how to do that in the next article.


Drawbacks

One down side to this method is that every user who tries to log in will end up as a user in the database (even if the attempted login is from a bot). Those users will remain as inactive and will never be allowed to log in, but they're still there unless you remove them. You could periodically remove them with a simple utility snippet like the one below. You could also remove them in the other plugin if they are not authenticated.

/* Utility snippet to remove inactive users */
$users = $modx->getCollection('modUser', array('active' => false));
foreach ($users as $user) {
    $user->remove();
}

A second drawback is that all the users in the database (except those created before the plugins were implemented) have the same phone email address. You can ask the users to modify their profiles to enter their real email addresses.

You can also add an email field to the login form using the OnManagerLoginFormPrerender event and add it to the $fields array. The email won't be used once the users exist in the database, but the users won't know that unless you tell them, and may be annoyed, thinking they have to enter the field each time they log in. Since the user object doesn't exist at the time that event fires, there's no way to check and eliminate the field if they're in the database.


Coming Up

In the following article, we'll combine our two plugins into a single plugin attached to both OnBeforeWebLogin and OnManagerAuthentication.



Comments (0)


Please login to comment.

  (Login)