We create our own registration page for a multisite instead of the standard wp-signup.php.

In normal WordPress installation the registration page (authorization, password reset) is displayed by the wp-login.php file.

  • /wp-login.php - authorization
  • /wp-login.php?action=register - registration
  • /wp-login.php?action=lostpassword - password reset

There are separate conditions for a multisite in wp-login.php. So, when you follow the link /wp-login.php?action=register on a multisite, WordPress will redirect to the page /wp-signup.php. Many themes don't make the page look very attractive, so we'll make our own.

Main site of the network

By default, WordPress opens the registration page (wp-signup.php) on the main domain (site) of the network. However, you can make a separate registration page for each site in your network, even if they have different themes. We will consider the case where all sites in the network have their own registration page, but the same theme is used and the sites differ only in language. If you use different themes, you will need to write more code.

functions.php?

No. This file name seems to be mentioned in every article about WordPress. In our case, given that the registration functionality is designed for several sites, it makes sense to include it in MU plugins, which are loaded when any site is opened.

Lyrical digression

It is worth noting that MU plugins are loaded before regular plugins and before the WordPress core is fully loaded, so calling some functions can lead to fatal errors in PHP. Such “early” loading also has its advantages. Let’s say that inside any theme you cannot attach to some actions that are triggered even before the functions.php file is loaded from the theme. An example of this is the actions from the Jetpack plugin of the form jetpack_module_loaded_related-posts (related-posts is the name of the module), with the help of which it is possible to monitor the activity of modules in Jetpack. It is impossible to “attach” to this action from the theme file, because the action has already fired before the theme is loaded - plugins are loaded before themes. You can take a look at the general picture of the WordPress loading order on the Action Reference page in the codex.

File order

MU plugins can contain any number of files and any structure that seems logical to you. I stick to something like this hierarchy:

|-mu-plugins |-|-load.php |-|-|-selena-network |-|-|-|-signup |-|-|-|-|-plugin.php |-|-|-| -|-... |-|-|-|-jetpack |-|-|-|-|-plugin.php

The load.php file contains all the necessary “plugins” for our network:

// Load Translates for all addons load_muplugin_textdomain ("selena_network", "/selena-network/languages/"); // Network Signup require WPMU_PLUGIN_DIR . "/selena-network/signup/plugin.php"; // Another plugins // require WPMU_PLUGIN_DIR ...

Inside the selena-network folder, plugin folders are stored, each with its own plugin.php, which we include in load.php. This gives you flexibility and the ability to quickly turn things off and on.

Registration page address

To specify the address of the registration page, use the wp_signup_location filter. It can be found inside the wp-login.php file and is responsible for the redirect to wp-signup.php.

Case "register" : if (is_multisite()) ( wp_redirect(apply_filters("wp_signup_location", network_site_url("wp-signup.php"))); exit;

Let's add our function to mu-plugins/selena-network/signup/plugin.php, which will return the address of the registration page on the current site:

Function selena_network_signup_page ($url) ( return home_url () . "/signup/"; ) add_filter ( "wp_signup_location", "selena_network_signup_page", 99);

selena_network is the prefix that I use in the names of all functions inside MU plugins on my site to avoid collisions, it should be replaced with your own unique prefix. The priority of adding a filter is 99, because some plugins, for example bbPress and BuddyPress, can overwrite this address with their own (MU plugins load earlier than regular plugins, see above). Note that home_url() is used instead of network_site_url() to keep the visitor on the same domain. Any URL can be used as an address.

Creating a page

Now let's create a page with the address site.com/signup/ through the normal interface, and in the child theme folder the template for our new page is page-signup.php. Instead of the word "signup" you can use a unique ID.

Inside the new template, you need to call the selena_network_signup_main() function, which will display the registration form.

It's worth noting that the whole template process is optional and you can instead create your own shortcode that will also call the selena_network_signup_main() function.

wp-signup.php and wp-activate.php

Now let's create a function that will display the registration form. To do this, copy the files wp-signup.php and wp-activate.php from the WordPress root to mu-plugings/selena-network/signup/ (and don’t forget to connect them inside mu-plugins/selena-network/signup/plugin.php) . Further manipulations with files are extremely difficult and long to describe, so you will have to do them yourself. I’ll just describe what exactly needs to be done and publish the source files of my project:

  1. At the beginning of the file, remove all require , function calls and other code outside the functions.
  2. Rename all functions by adding unique prefixes to the names.
  3. Wrap the lower part of the wp-signup.php code in the selena_network_signup_main function and at the very beginning write global $active_signup; .
  4. Replace the layout with your own in the right places.

Inside wp-activate.php you need to do approximately the same thing:

  1. Remove all code outside the functions, wrap the layout in a separate function.
  2. Change the layout in places where necessary.

The wp-activate.php file is responsible for the account activation page. As with the registration page, you need to create a separate template for it, inside of which you need to call the function from the wp-activate.php file.

Sending activation letters

The registration page sends the visitor an email with a link to activate their account. By default, this is done by the wpmu_signup_user_notification() function from the ms-functions.php file. You can borrow its functionality for your own function. The reason to avoid using this feature is that it sends the account activation link from wp-activate.php. You can “turn off” this function using the wpmu_signup_user_notification filter, returning it false (if this is not done, the activation letter will be sent twice, okay, actually two different letters).

Function armyofselenagomez_wpmu_signup_user_notification($user, $user_email, $key, $meta = array()) ( // ... // Code from function wpmu_signup_user_notification() wp_mail($user_email, wp_specialchars_decode($subject), $message, $message_headers) ; return false; ) add_filter("wpmu_signup_user_notification", "armyofselenagomez_wpmu_signup_user_notification", 10, 4);

As a result, the registration page in the Selena theme began to look much cleaner and neater.

Conclusion

There are many other not very correct ways on the Internet to do the same thing - Apache redirects, AJAX forms that will not work without Java Script, etc. I didn’t really like all this, so I tried to do it as correctly as possible on my own own website.

I note that you should edit the files carefully and try not to deviate too much from the original ones, so that in the future, if WordPress changes the wp-signup.php and wp-activate.php files, it will be easier to compare them with each other to find changes.

Don't forget to look in source all the functions described above in order to fully understand what and how happens inside the code.

Bonus. Protection from spammers

Even the smallest WordPress sites are often plagued by spam registrations. You can write endless conditions to filter bots, often more like trying to create artificial intelligence🙂 In the case of a multisite, a regular redirect in Apache helped me a lot, with the help of which I asked for a 404 when opening /wp-signup.php and /wp-acitvate.php (I’m not an expert in setting up Apache, so my rules may not be very correct ).

RewriteEngine On RewriteBase / RewriteRule ^wp-signup\.php - RewriteRule ^wp-activate\.php - # BEGIN WordPress # We do not touch the rules from WordPress by default :) # ... # END WordPress

P.S. I try to describe some third-party things in as much detail as possible, because when I started, sometimes there was no one to suggest and explain many things. I also believe that such small tips on other materials will encourage someone to learn something new and expand their area of ​​​​knowledge. RewriteRule entries use regular expressions, they are not complicated at all, for example, the ^ symbol means the beginning of a line.

Themes are usually not functional, but sometimes we developers need to implement some features into our theme to make it a little better and more convenient.

In this tutorial, we'll explore the term "plugin territory" and also learn how to use a fantastic tool written by Thomas Griffin: the TGM Plugin Activation library.

Theme functionality: invading plugin territory

Themes are designed to change the design of a WordPress website. Ideally, the theme should only address the visual aspect. However, in this golden age of WordPress, plugin developers often include functional features in their themes that help them stay competitive in the market.

This is an incursion into plugin territory. We can think of “plugin territory” as some functional sections of code. Any piece of code that changes the functionality of your site should be in the form of a plugin, unless said code is built into WordPress core.

I have already formulated earlier in one of my articles a rule of thumb for “plugin territory:

If a feature is related to the visual presentation of the site, then it should be included in the theme; if it is related to functionality, then it should be presented as a separate plugin.

Quite a simple rule. People still try to code functional bits into their themes, but theme directories (like WordPress.org or ThemeForest) don't accept themes that venture into "plugin territory." Thus, offering functionality in themes has become a bit of a challenge.

Luckily, there is a simple solution that doesn't go against the plugin territory rule.

Introduction to the TGM Plugin Activation Library

Setting up TGM Plugin Activation

Notice the tgmpa() function with two parameters at the very end of the code. The second parameter is the $config variable, which is also an array, like $plugins. As its name suggests, you can customize the TGM Plugin Activation library using of this array. The variable also accepts its own set of options:

  • id (string) – unique id for the TGM Plugin Activation library in your theme. This is very important: if other plugins also use TGM Plugin Activation, different IDs will prevent possible conflicts.
  • default_path (string) – default absolute path for plugins in your theme. Once you install it, you can use the name of the zip file as the source parameter value for your plugin.
  • menu (string) – menu slug for the plugin installation page.
  • has_notices (boolean) – if set to true, admin notifications will be issued for required/recommended plugins.
  • dismissible (boolean) – if set to true, the user can “dismiss” notifications.
  • dismiss_msg (string) – if the dismissible option is set to false, this message will be shown above the admin notice.
  • is_automatic (boolean) – if set to true, plugins will be activated after the user agrees to install them.
  • message (string) – additional HTML output before the plugins table.
  • strings (array) – an array that includes the messages to be displayed. You can specify them as translated strings. Look at the example.php file to see a complete list of all messages.
"mytheme-tgmpa", // your unique TGMPA ID "default_path" => get_stylesheet_directory() . "/lib/plugins/", // default absolute path "menu" => "mytheme-install-required-plugins", // menu slug "has_notices" => true, // Show admin notices "dismissable" => false , // the notices are NOT dismissable "dismiss_msg" => "I really, really need you to install these plugins, okay?", // this message will be output at top of nag "is_automatic" => true, // automatically activate plugins after installation "message" => "", // message to output right before the plugins table "strings" => array(); // The array of message strings that TGM Plugin Activation uses); ?>

Conclusion

As you can see, offer functionality in WordPress themes maybe - you just have to think first about the users who might switch from one topic to another. The TGM Plugin Activation library offers a really smart way to do this.

What do you think about this tool? Have you ever used it, do you plan to use it in the future? Share your thoughts!

Allows you to use one WordPress installation for multiple sites at the same time. In this case, each site gets its own tables in the database with a unique prefix.

Tables with data of registered users are common to all sites on the network. This is a definite plus and by registering once you can get access to several sites. Moreover, on each site the same account can have different rights. For example, on one site a user may be an editor, and on another an administrator.

In a typical WordPress installation, the registration, login, and password reset page is output by the wp-login.php file.

  • wp-login.php - authorization
  • wp-login.php?action=register - registration
  • wp-login.php?action=lostpassword - password reset

In Multisite mode, the WordPress core begins to behave slightly differently and when you follow the link wp-login.php?action=register, a redirect to wp-signup.php will occur. This is your network registration page that comes with WordPress by default.

In addition to registering regular user accounts, you can also create a new website on it if the super administrator has enabled this feature in the network settings (Network Admin → Settings → Network Settings).

In most themes, the registration page doesn't look very good. Many themes use CSS frameworks like Bootstrap and their own custom classes to style different elements on pages, so it's hard to write one HTML that fits everyone.

But don't despair if the page looks untidy. The wp-signup.php file is a great thing at first, when you don’t have time to work through every detail of the site - you can focus on other more important pages and content.

When you're ready to make your own signup page, wp-signup.php is a good example to help you understand the range of features WordPress provides for processing and validating user input and creating new accounts.

Main site of the network

By default, WordPress opens the registration page (wp-signup.php) on the main domain (site) of the network. However, you can create registration pages for each site in your network, even if they have themes.

We will consider the case when all sites in the network use the same theme, but each of them has a registration page. The sites differ in language (English and Russian), so the registration page will be displayed in the “native” language of the site. If sites use different themes, everything will depend on what themes they are, whether the same layout will suit them (an excellent situation that can push you to unify all your themes) or whether it is worth developing pages individually.

Alternative to functions.php

File order

MU plugins can contain any number of files and a structure that seems logical to you. I stick to something like this hierarchy:

| mu-plugins | | load.php | | selena-network | | | signup | | | | plugin.php | | | ... | | | jetpack | | | | plugin.php

The load.php file includes translations and all the necessary “plugins”:

// Loading translations for MU plugins load_muplugin_textdomain("selena_network", "/selena-network/languages/"); // Functionality for the registration page require WPMU_PLUGIN_DIR . "/selena-network/signup/plugin.php"; // Another plugin // require WPMU_PLUGIN_DIR ...

Plugin folders are stored inside the selena-network directory. Each has its own plugin.php, which we include in load.php. This gives you the flexibility and ability to instantly turn off and on individual components on a work project in case of an emergency.

Registration page

Having figured out where and how we will write the code, we can move on to creating a registration page.

Let's create a page with the address example.org/signup/ through the normal interface. You can use any URL that seems appropriate for your project.

Redirect to the desired registration page

To let WordPress know about our new page registration and redirected exactly to it, when you click on the “Register” link, the wp_signup_location filter is used. It can be found inside wp-login.php and is responsible for redirecting to wp-signup.php by default.

Case "register" : if (is_multisite()) ( wp_redirect(apply_filters("wp_signup_location", network_site_url("wp-signup.php"))); exit; // ...

As you remember, by default, the registration page opens on the main domain of the network. This is why network_site_url() is used here.

Let's add our handler to the filter in mu-plugins/selena-network/signup/plugin.php, which will return the address of the registration page on the current site:

Function selena_network_signup_page($url) ( return home_url("signup"); ) add_filter("wp_signup_location", "selena_network_signup_page", 99);

selena_network is the prefix that I use in the names of all functions inside MU plugins on my site to avoid collisions, it should be replaced with your own unique prefix. The priority of adding a filter is 99, because some plugins, for example, bbPress and BuddyPress can overwrite this address with their own (MU plugins load earlier than regular plugins, see above).

Please note that home_url() is used, which, unlike network_site_url(), returns the address of the current site, and not the main site of the network.

Functionality wp-signup.php

The wp-signup.php file contains a large number of functions and code. To see the big picture, you can use code folding. As a rule, in English this is called “code folding”.

At the very beginning of the file, from lines 1 to 80 (in version 4.1.1), various checks are performed and the “start” of the page is output using get_header() .

Next, many methods are declared, and before we start working with them, it’s worth understanding what each function does. Many of them often use other functions with the wpmu_ prefix, all of which are declared in the wp-includes/ms-functions.php file. This section is hard to understand without seeing the code yourself. Below is a short description of the main functions in case you have any difficulties.

  • wpmu_signup_stylesheet() - Outputs additional CSS on the registration page.
  • show_blog_form() - fields for site registration (address, name, visibility for search engines).
  • validate_blog_form() - validates the entered site address and title using wpmu_validate_blog_signup() .
  • show_user_form() - fields for user registration (login and email address).
  • validate_user_form() — checking the entered login and email address. mail using wpmu_validate_user_signup() .
  • signup_another_blog() - fields for registering new sites using show_blog_form() for users who are already registered on the site.
  • validate_another_blog_signup() - checks the site address and title using validate_blog_form() .
  • signup_user() is the main function for displaying the fields of the registration page.
  • validate_user_signup() - checks login and email address. mail using validate_user_form() .
  • signup_blog() - fields for entering the address, name and visibility of the site (second registration step) using show_blog_form() .
  • validate_blog_signup() - checks login, email address. email, address and website name.

At the very bottom of the wp-signup.php file (from line 646 in version 4.1.1) is the main logic of the registration page, which uses all the methods described above. This part of the code is not included in the function. At the end get_footer() is called.

Copy the functionality of wp-signup.php

The following will describe the procedure for copying wp-signup.php into MU plugins and making changes to the “fork”. This may not seem like the best way to go. Instead, you can write your own functions from scratch to validate and display forms using classes rather than regular functions. In my opinion, wp-signup.php already has all the necessary logic for our page, all that remains is to make some small changes.

When WordPress is updated, wp-signup.php also changes from time to time, but this does not mean that you will have to synchronize your “fork” with each release. The functions inside wp-signup.php essentially only deal with HTML output, data verification, creating accounts and sites, and methods with the wpmu_ prefix, declared in ms-functions.php, are involved.

Let's create a function that will display the registration form on the page. To do this, copy wp-signup.php from the WordPress root to mu-plugings/selena-network/signup/ . Let's connect it inside mu-plugins/selena-network/signup/plugin.php).

Require WPMU_PLUGIN_DIR . "/selena-network/signup/wp-signup.php";

Let's remove all require and unnecessary checks from the very beginning of the copied file. In version 4.1.1 this is all the code from lines 1 to 80.

Now we are ready to create the main function to display the registration form. To do this, we will transfer all the logic from line 646 to the very end of the file into a function called selena_network_signup_main. At the very end, we will remove two extra closing

(lines 722 and 723), as well as the get_footer() call.

In the newly created selena_network_signup_main() , at the very beginning we will declare the global active_signup variable, which is used by all other methods from this file. And let's add a call to the before_signup_form event, which we removed from the very beginning of the file.

Function selena_network_signup_main() ( global $active_signup; do_action("before_signup_form"); // ... )

Now all that remains is to change the layout in all places where it is necessary and the registration page is ready.

Output of the registration form

There are at least two options here. More convenient way— create a shortcode and place it on the page through a regular editor.

// Create a shortcode network_signup add_shortcode("network_signup", "selena_network_signup_main");

The second option is to create a page template page-signup.php in your child theme folder. Instead of the word "signup" you can use the unique ID assigned to the page. Inside the template, add the necessary layout and call selena_network_signup_main() in the right place.

As a result, my registration page looked much better and cleaner.

Activation page

By default, WordPress conditionally divides the registration process in Multisite into two steps - filling out a form on the site and activating your account by clicking on the link sent to email. After you fill out the form created in the previous section, WordPress sends an email with short instructions and a link to activate your account.

The wp-activate.php file located in the WordPress root directory is responsible for displaying the activation page. wp-activate.php can also be completely changed. The process is similar to what we already did for wp-signup.php.

Let's create the example.org/activate/ page through the normal interface. For the address, use any URL that seems appropriate to you.

Let's copy the wp-activate.php file to our MU plugins and connect it to mu-plugins/selena-network/signup/plugin.php.

Require WPMU_PLUGIN_DIR . "/selena-network/signup/wp-activate.php";

There is not much content inside, unlike wp-signup.php. The file performs a single operation - it activates the account if the correct key is received and displays an error message or successful completion operations.

Let's remove all unnecessary checks and require - from lines 1 to 69 in WordPress 4.1.1. At the very end, we'll remove the get_footer() call. We will transfer the remaining contents to the selena_network_activate_main() function.

It's interesting to note that here, before loading WordPress (wp-load.php), the constant WP_INSTALLING was declared. Its presence causes WordPress to not load plugins.

As in the case of the registration page, all that remains is to correct the layout where necessary. You can also change the text of the displayed messages (in this case, do not forget to add the text domain of your MU plugins to all translator functions; by default, it is not installed anywhere).

The ready-made function can be used on a pre-created page through a shortcode or a separate template in a child theme.

Activation letters with correct links

The activation page is ready to go, but WordPress doesn't know about it and will still send activation emails with a link to wp-activate.php. Unlike wp-signup.php, there is no filter that would allow you to change the address. Instead, you need to write your own function that will send emails with the correct links.

When you fill out and submit the form on the registration page, WordPress calls wpmu_signup_ user() or wpmu_signup_ blog() depending on the registration type. Both functions create new entry in the wp_signups table, filling it with the necessary content, including the account activation key.

Afterwards, depending on the function, wpmu_signup_ is called user _notification() or wpmu_signup_ blog _notification() . Both functions have similar functionality - they generate and send an email with an activation link, but take different arguments. Both have filters to “intercept” the event.

If (! apply_filters("wpmu_signup_user_notification", $user, $user_email, $key, $meta)) return false;

To activate accounts with blog creation:

If (! apply_filters("wpmu_signup_blog_notification", $domain, $path, $title, $user, $user_email, $key, $meta)) ( return false; )

All that remains is to write your own handlers, inside which send letters via wp_mail() , and at the very end, be sure to return false so that WordPress does not send an activation letter twice - one is yours, the other is the default letter with a link to wp-activate.php .

Function selena_network_wpmu_signup_user_notification($user, $user_email, $key, $meta = array()) ( // Generate the header, text and headers of the letter // ... // Send the letter or add a Cron task to send the letter wp_mail($user_email , wp_specialchars_decode($subject), $message, $message_headers); // Give false so that WordPress does not send the activation email twice return false; ) add_filter("wpmu_signup_user_notification", "selena_network_wpmu_signup_user_notification", 10, 4);

If you send emails via an SMTP server or the number of registrations is very large, you should consider not sending emails instantly. Instead, you can add Cron tasks using WordPress Cron.

We close access to wp-signup.php and wp-activate.php

After creating your own registration and activation pages, you may want to close the "originals". For example, if the registration page has additional fields which must be filled out. Also, many WordPress sites are subject to spam registrations.

You can solve two problems in one action by asking Apache to return a 404 if you try to open these pages. To do this, you just need to add a couple of additional RewriteRules to your configuration file or .htaccess .

RewriteEngine On RewriteBase / # Knowledge regular expressions will never be superfluous :) RewriteRule ^wp-signup\.php - RewriteRule ^wp-activate\.php - # BEGIN WordPress # We do not touch the rules from WordPress by default :) # ... # END WordPress

Conclusion

There are many solutions for this and many other WordPress “problems” on the Internet. For example, to create registration and activation pages, some suggest rewriting the original wp-signup.php and wp-activate.php . This should not be done, because if you update WordPress, you will lose all changes made to the files, and you will also not be able to check the integrity of the core using .

When developing any add-on, theme, or solution, you should spend a little time understanding what's going on inside WordPress. There are many useful debugging tools for this.

P.S.

To automatically assign different roles to new users, you can use the Multisite User Management plugin.

If you have any questions or difficulties during the creation of registration and activation pages after reading the article, leave a comment and we will definitely answer.

27.03.2015 27.03.2015

WordPress developer. Loves order in everything and understanding new tools. Inspired by Symfony component architecture.

  • Today we will look at the exploitation of a critical 1-day vulnerability in the popular CMS Joomla, which exploded on the Internet at the end of October. We will talk about vulnerabilities with numbers CVE-2016-8869, CVE-2016-8870 And CVE-2016-9081. All three come from one piece of code that languished in the depths of the framework for five long years, waiting in the wings, only to then break free and bring with it chaos, hacked sites and the tears of innocent users of this Joomla. Only the most valiant and courageous developers, whose eyes are red from the light of the monitors, and whose keyboards are littered with bread crumbs, were able to challenge the raging evil spirits and lay their heads on the altar of fixes.

    WARNING

    All information is provided for informational purposes only. Neither the editors nor the author are responsible for any possible harm caused by the materials of this article.

    Where it all started

    On October 6, 2016, Demis Palma created a topic on Stack Exchange in which he asked: why, in fact, in Joomla version 3.6 there are two methods for registering users with the same name register()? The first one is in the UsersControllerRegistration controller and the second one is in the UsersControllerUser controller. Damis wanted to know if the UsersControllerUser::register() method was used somewhere, or if it was just an evolutionary anachronism left over from the old logic. His concern was that even if this method is not used by any view, it can be called by a crafted query. To which I received a response from a developer under the nickname itoctopus, who confirmed: the problem really exists. And sent a report to the Joomla developers.

    Then events developed most rapidly. On October 18, Joomla developers accepted the report from Damis, who by that time had drafted a PoC that would allow user registration. He published a note on his website, where he spoke in general terms about the problem he found and his thoughts on this matter. Coming out on the same day a new version Joomla 3.6.3, which still contains vulnerable code.

    After this, Davide Tampellini spins the bug to the point of registering not a simple user, but an administrator. And on October 21, a new case arrives to the Joomla security team. It already talks about increasing privileges. On the same day, an announcement appears on the Joomla website that on Tuesday, October 25, the next version with serial number 3.6.3 will be released, which corrects a critical vulnerability in the system kernel.

    October 25 Joomla Security Strike Team finds the latest problem created by the piece of code discovered by Damis. Then a commit dated October 21 with the inconspicuous name Prepare 3.6.4 Stable Release is pushed into the main branch of the official Joomla repository, which fixes the unfortunate bug.

    After this coming out, numerous interested individuals join the developer community - they begin to promote the vulnerability and prepare exploits.

    On October 27, researcher Harry Roberts uploads a ready-made exploit to the Xiphos Research repository that can upload a PHP file to a server with a vulnerable CMS.

    Details

    Well, the background is over, let's move on to the most interesting part - analysis of the vulnerability. I installed Joomla 3.6.3 as a test version, so all line numbers will be relevant for this version. And all the paths to the files that you will see below will be indicated relative to the root of the installed CMS.

    Thanks to Damis Palma's discovery, we know that there are two methods that perform user registration in the system. The first one is used by the CMS and is located in the file /components/com_users/controllers/registration.php:108. The second one (the one we will need to call) lives in /components/com_users/controllers/user.php:293. Let's take a closer look at it.

    286: /** 287: * Method to register a user. 288: * 289: * @return boolean 290: * 291: * @since 1.6 292: */ 293: public function register() 294: ( 295: JSession::checkToken("post") or jexit(JText::_ ("JINVALID_TOKEN")); ... 300: // Get the form data. 301: $data = $this->input->post->get("user", array(), "array"); . .. 315: $return = $model->validate($form, $data); 316: 317: // Check for errors. 318: if ($return === false) 319: ( ... 345: / / Finish the registration. 346: $return = $model->register($data);

    Here I left only interesting lines. The full version of the vulnerable method can be viewed in the Joomla repository.

    Let's figure out what happens during normal user registration: what data is sent and how it is processed. If user registration is enabled in settings, the form can be found at http://joomla.local/index.php/component/users/?view=registration.


    A legitimate user registration request looks like the following screenshot.


    The com_users component is responsible for working with users. Pay attention to the task parameter in the request. It has the format $controller.$method . Let's look at the file structure.

    Names of scripts in the folder controllers correspond to the names of the called controllers. Since our request now contains $controller = "registration" , the file will be called registration.php and its register() method.

    Attention, question: how to transfer registration processing to a vulnerable place in the code? You probably already guessed it. The names of the vulnerable and real methods are the same (register), so we just need to change the name of the called controller. Where is our vulnerable controller located? That's right, in the file user.php. It turns out $controller = "user" . Putting everything together we get task = user.register . Now the registration request is processed by the method we need.


    The second thing we need to do is send the data in the correct format. Everything is simple here. Legitimate register() expects from us an array called jform , in which we pass registration data - name, login, password, email (see screenshot with the request).

    • /components/com_users/controllers/registration.php: 124: // Get the user data. 125: $requestData = $this->input->post->get("jform", array(), "array");

    Our client gets this data from an array called user.

    • /components/com_users/controllers/user.php: 301: // Get the form data. 302: $data = $this->input->post->get("user", array(), "array");

    Therefore, we change the names of all parameters in the request from jfrom to user .

    Our third step is to find a valid CSRF token, since without it there will be no registration.

    • /components/com_users/controllers/user.php: 296: JSession::checkToken("post") or jexit(JText::_("JINVALID_TOKEN"));

    It looks like an MD5 hash, and you can take it, for example, from the authorization form on the site /index.php/component/users/?view=login.


    Now you can create users using the desired method. If everything worked out, then congratulations - you just exploited a vulnerability CVE-2016-8870"Missing permission check for registering new users."

    This is what it looks like in the “working” register() method from the UsersControllerRegistration controller:

    • /components/com_users/controllers/registration.php: 113: // If registration is disabled - Redirect to login page. 114: if (JComponentHelper::getParams("com_users")->get("allowUserRegistration") == 0) 115: ( 116: $this->setRedirect(JRoute::_("index.php?option=com_users&view= login", false)); 117: 118: return false; 119: )

    And so in vulnerable:

    • /components/com_users/controllers/user.php:

    Yeah, no way.

    To understand the second, much more serious problem, we will send the request we generated and monitor how it is executed in various sections of the code. Here is the piece that is responsible for validating the user submitted data in the worker method:

    Continuation is available only to members

    Option 1. Join the “site” community to read all materials on the site

    Membership in the community within the specified period will give you access to ALL Hacker materials, increase your personal cumulative discount and allow you to accumulate a professional Xakep Score rating!